updated with name, default, better docs
This commit is contained in:
parent
9646a3bd65
commit
142d87d14e
4 changed files with 66 additions and 48 deletions
|
|
@ -192,6 +192,17 @@ nfo_output_directory
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
regex
|
||||||
|
'''''
|
||||||
|
.. autoclass:: ytdl_sub.plugins.regex.RegexOptions()
|
||||||
|
:members: skip_if_match_fails
|
||||||
|
|
||||||
|
.. autoclass:: ytdl_sub.plugins.regex.SourceVariableRegex()
|
||||||
|
:members: match, capture_group_names, capture_group_defaults
|
||||||
|
:member-order: bysource
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
.. _subscription_yaml:
|
.. _subscription_yaml:
|
||||||
|
|
||||||
subscription.yaml
|
subscription.yaml
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ logger = Logger.get(name="regex")
|
||||||
class SourceVariableRegex(StrictDictValidator):
|
class SourceVariableRegex(StrictDictValidator):
|
||||||
|
|
||||||
_required_keys = {"match"}
|
_required_keys = {"match"}
|
||||||
_optional_keys = {"defaults", "capture_group_names"}
|
_optional_keys = {"capture_group_defaults", "capture_group_names"}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self._match = self._validate_key(key="match", validator=RegexListValidator)
|
self._match = self._validate_key(key="match", validator=RegexListValidator)
|
||||||
self._defaults = self._validate_key_if_present(
|
self._capture_group_defaults = self._validate_key_if_present(
|
||||||
key="defaults", validator=ListFormatterValidator
|
key="capture_group_defaults", validator=ListFormatterValidator
|
||||||
)
|
)
|
||||||
self._capture_group_names = self._validate_key_if_present(
|
self._capture_group_names = self._validate_key_if_present(
|
||||||
key="capture_group_names", validator=SourceVariableNameListValidator, default=[]
|
key="capture_group_names", validator=SourceVariableNameListValidator, default=[]
|
||||||
|
|
@ -34,12 +34,12 @@ class SourceVariableRegex(StrictDictValidator):
|
||||||
|
|
||||||
# If defaults are to be used, ensure there are the same number of defaults as there are
|
# If defaults are to be used, ensure there are the same number of defaults as there are
|
||||||
# capture groups
|
# capture groups
|
||||||
if self._defaults is not None and self._match.num_capture_groups != len(
|
if self._capture_group_defaults is not None and self._match.num_capture_groups != len(
|
||||||
self._defaults.list
|
self._capture_group_defaults.list
|
||||||
):
|
):
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"number of defaults must match number of capture groups, "
|
f"number of defaults must match number of capture groups, "
|
||||||
f"{len(self._defaults.list)} != {self._match.num_capture_groups}"
|
f"{len(self._capture_group_defaults.list)} != {self._match.num_capture_groups}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# If there are capture groups, ensure there are capture group names
|
# If there are capture groups, ensure there are capture group names
|
||||||
|
|
@ -50,14 +50,31 @@ class SourceVariableRegex(StrictDictValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def capture_list(self) -> RegexListValidator:
|
def match(self) -> RegexListValidator:
|
||||||
"""
|
"""
|
||||||
Returns
|
Required. List of regex strings to try to match against a source variable. Each regex
|
||||||
-------
|
string must have the same number of capture groups.
|
||||||
List of regex captures
|
|
||||||
"""
|
"""
|
||||||
return self._match
|
return self._match
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capture_group_names(self) -> Optional[List[str]]:
|
||||||
|
"""
|
||||||
|
Optional (only when no capture groups are in the regex string). List of names to store the
|
||||||
|
capture group values to. These and ``_sanitized`` versions will be available to use as
|
||||||
|
source variables. The list's length must be equal to the number of match capture groups.
|
||||||
|
"""
|
||||||
|
return [validator.value for validator in self._capture_group_names.list]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capture_group_defaults(self) -> Optional[List[StringFormatterValidator]]:
|
||||||
|
"""
|
||||||
|
Optional. List of string format validators to use for capture group defaults if a
|
||||||
|
source variable cannot be matched. The list's length must be equal to the number of match
|
||||||
|
capture groups.
|
||||||
|
"""
|
||||||
|
return self._capture_group_defaults.list if self.has_defaults else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_defaults(self) -> bool:
|
def has_defaults(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
@ -65,25 +82,7 @@ class SourceVariableRegex(StrictDictValidator):
|
||||||
-------
|
-------
|
||||||
True if a validation exception should be raised if not captured. False otherwise.
|
True if a validation exception should be raised if not captured. False otherwise.
|
||||||
"""
|
"""
|
||||||
return self._defaults is not None
|
return self._capture_group_defaults is not None
|
||||||
|
|
||||||
@property
|
|
||||||
def defaults(self) -> Optional[List[StringFormatterValidator]]:
|
|
||||||
"""
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
List of string format validators to use for the defaults
|
|
||||||
"""
|
|
||||||
return self._defaults.list if self.has_defaults else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def capture_group_names(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
List of new capture group names
|
|
||||||
"""
|
|
||||||
return [validator.value for validator in self._capture_group_names.list]
|
|
||||||
|
|
||||||
|
|
||||||
class FromSourceVariablesRegex(StrictDictValidator):
|
class FromSourceVariablesRegex(StrictDictValidator):
|
||||||
|
|
@ -115,16 +114,19 @@ class RegexOptions(PluginOptions):
|
||||||
skip_if_match_fails: True
|
skip_if_match_fails: True
|
||||||
|
|
||||||
from:
|
from:
|
||||||
|
# For each entry's `title` value...
|
||||||
title:
|
title:
|
||||||
# Match with capture groups act as a filter.
|
# Perform this regex match on it to act as a filter.
|
||||||
# This will only download videos with "Official Video" in it.
|
# This will only download videos with "Official Video" in it.
|
||||||
match: "\[Official Video\]"
|
match: '\[Official Video\]'
|
||||||
|
# For each entry's `description` value...
|
||||||
description:
|
description:
|
||||||
# Match with capture groups and defaults.
|
# Match with capture groups and defaults.
|
||||||
# This tries to scrape a date from the description and produce new source variables
|
# This tries to scrape a date from the description and produce new source variables
|
||||||
match: "([0-9]{4})-([0-9]{2})-([0-9]{2})"
|
match: "([0-9]{4})-([0-9]{2})-([0-9]{2})"
|
||||||
|
|
||||||
# Each capture group creates these new source variables, respectively
|
# Each capture group creates these new source variables, respectively, as well
|
||||||
|
# a sanitized version, i.e. `sanitized_captured_upload_year`
|
||||||
capture_group_names:
|
capture_group_names:
|
||||||
- "captured_upload_year"
|
- "captured_upload_year"
|
||||||
- "captured_upload_month"
|
- "captured_upload_month"
|
||||||
|
|
@ -144,10 +146,18 @@ class RegexOptions(PluginOptions):
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self._from = self._validate_key(key="from", validator=FromSourceVariablesRegex)
|
self._from = self._validate_key(key="from", validator=FromSourceVariablesRegex)
|
||||||
self.skip_if_match_fails: bool = self._validate_key_if_present(
|
self._skip_if_match_fails: bool = self._validate_key_if_present(
|
||||||
key="skip_if_match_fails", validator=BoolValidator, default=False
|
key="skip_if_match_fails", validator=BoolValidator, default=True
|
||||||
).value
|
).value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_if_match_fails(self) -> Optional[bool]:
|
||||||
|
"""
|
||||||
|
Defaults to True. If True, when any match fails and has no defaults, the entry will be
|
||||||
|
skipped. If False, ytdl-sub will error and all downloads will not proceed.
|
||||||
|
"""
|
||||||
|
return self._skip_if_match_fails
|
||||||
|
|
||||||
def validate_with_variables(
|
def validate_with_variables(
|
||||||
self, source_variables: List[str], override_variables: List[str]
|
self, source_variables: List[str], override_variables: List[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -226,9 +236,7 @@ class RegexPlugin(Plugin[RegexOptions]):
|
||||||
|
|
||||||
# Iterate each source var to capture and add to the entry
|
# Iterate each source var to capture and add to the entry
|
||||||
for source_var, regex_options in self.plugin_options.source_variable_capture_dict.items():
|
for source_var, regex_options in self.plugin_options.source_variable_capture_dict.items():
|
||||||
maybe_capture = regex_options.capture_list.match_any(
|
maybe_capture = regex_options.match.match_any(input_str=entry_variable_dict[source_var])
|
||||||
input_str=entry_variable_dict[source_var]
|
|
||||||
)
|
|
||||||
|
|
||||||
# If no capture
|
# If no capture
|
||||||
if maybe_capture is None:
|
if maybe_capture is None:
|
||||||
|
|
@ -257,7 +265,7 @@ class RegexPlugin(Plugin[RegexOptions]):
|
||||||
regex_options.capture_group_names[i]: default.apply_formatter(
|
regex_options.capture_group_names[i]: default.apply_formatter(
|
||||||
variable_dict=source_variables_and_overrides_dict
|
variable_dict=source_variables_and_overrides_dict
|
||||||
)
|
)
|
||||||
for i, default in enumerate(regex_options.defaults)
|
for i, default in enumerate(regex_options.capture_group_defaults)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# There is a capture, add the source variables to the entry as
|
# There is a capture, add the source variables to the entry as
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ def regex_subscription_dict(output_directory):
|
||||||
"format": "best[height<=480]",
|
"format": "best[height<=480]",
|
||||||
},
|
},
|
||||||
"regex": {
|
"regex": {
|
||||||
"skip_if_match_fails": True,
|
# tests that skip_if_match_fails defaults to True
|
||||||
"from": {
|
"from": {
|
||||||
"title": {
|
"title": {
|
||||||
"match": [
|
"match": [
|
||||||
|
|
@ -41,7 +41,7 @@ def regex_subscription_dict(output_directory):
|
||||||
"upload_captured_year",
|
"upload_captured_year",
|
||||||
"upload_captured_month",
|
"upload_captured_month",
|
||||||
],
|
],
|
||||||
"defaults": [
|
"capture_group_defaults": [
|
||||||
"First",
|
"First",
|
||||||
"Second containing {in_regex_default}",
|
"Second containing {in_regex_default}",
|
||||||
],
|
],
|
||||||
|
|
@ -49,7 +49,7 @@ def regex_subscription_dict(output_directory):
|
||||||
"artist": {
|
"artist": {
|
||||||
"match": ["Never (.*) capture"],
|
"match": ["Never (.*) capture"],
|
||||||
"capture_group_names": ["always_default"],
|
"capture_group_names": ["always_default"],
|
||||||
"defaults": ["Always default"],
|
"capture_group_defaults": ["Always default"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -72,8 +72,7 @@ def regex_subscription_dict(output_directory):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def regex_subscription_dict_no_match_fails(regex_subscription_dict):
|
def regex_subscription_dict_no_match_fails(regex_subscription_dict):
|
||||||
# tests that skip_if_match_fails defaults to False
|
regex_subscription_dict["regex"]["skip_if_match_fails"] = False
|
||||||
del regex_subscription_dict["regex"]["skip_if_match_fails"]
|
|
||||||
return regex_subscription_dict
|
return regex_subscription_dict
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -177,7 +176,7 @@ class TestRegex:
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_regex_fails_unequal_defaults(self, regex_subscription_dict, music_video_config):
|
def test_regex_fails_unequal_defaults(self, regex_subscription_dict, music_video_config):
|
||||||
regex_subscription_dict["regex"]["from"]["title"]["defaults"] = ["1 != 2"]
|
regex_subscription_dict["regex"]["from"]["title"]["capture_group_defaults"] = ["1 != 2"]
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ValidationException,
|
ValidationException,
|
||||||
match=re.escape("number of defaults must match number of capture groups, 1 != 2"),
|
match=re.escape("number of defaults must match number of capture groups, 1 != 2"),
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ def expected_playlist_download():
|
||||||
return ExpectedDownloads(
|
return ExpectedDownloads(
|
||||||
expected_downloads=[
|
expected_downloads=[
|
||||||
# Download mapping
|
# Download mapping
|
||||||
ExpectedDownloadFile(path=Path(".ytdl-sub-jmc-download-archive.json"), md5="9f785c29194a6ecfba6a6b4018763ddc"),
|
ExpectedDownloadFile(path=Path(".ytdl-sub-music_video_playlist_test-download-archive.json"), md5="9f785c29194a6ecfba6a6b4018763ddc"),
|
||||||
|
|
||||||
# Entry files
|
# Entry files
|
||||||
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg"), md5="b232d253df621aa770b780c1301d364d"),
|
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg"), md5="b232d253df621aa770b780c1301d364d"),
|
||||||
|
|
@ -87,16 +87,16 @@ def single_video_subscription_dict(subscription_dict):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def single_video_subscription(config, single_video_subscription_dict):
|
def single_video_subscription(music_video_config, single_video_subscription_dict):
|
||||||
single_video_preset = Preset.from_dict(
|
single_video_preset = Preset.from_dict(
|
||||||
config=config,
|
config=music_video_config,
|
||||||
preset_name="music_video_single_video_test",
|
preset_name="music_video_single_video_test",
|
||||||
preset_dict=single_video_subscription_dict,
|
preset_dict=single_video_subscription_dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Subscription.from_preset(
|
return Subscription.from_preset(
|
||||||
preset=single_video_preset,
|
preset=single_video_preset,
|
||||||
config=config,
|
config=music_video_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue