[FEATURE] Toggle date_range to break or not (#904)
Adds a the new field `breaking` to the `date_range` plugin, to toggle whether an entry breaks subsequent metadata pulls. This is useful to disable if you are grabbing a playlist that may have videos out of order, but still want to apply a date range to it.
This commit is contained in:
parent
c7823a40ad
commit
f0b3991e3e
9 changed files with 47 additions and 23 deletions
|
|
@ -166,6 +166,14 @@ granularity possible.
|
||||||
Only download videos before this datetime.
|
Only download videos before this datetime.
|
||||||
|
|
||||||
|
|
||||||
|
``breaks``
|
||||||
|
|
||||||
|
:expected type: Optional[OverridesFormatter]
|
||||||
|
:description:
|
||||||
|
Toggle to enable breaking subsequent metadata downloads if an entry's upload date
|
||||||
|
is out of range. Defaults to True.
|
||||||
|
|
||||||
|
|
||||||
``enable``
|
``enable``
|
||||||
|
|
||||||
:expected type: Optional[OverridesFormatter]
|
:expected type: Optional[OverridesFormatter]
|
||||||
|
|
@ -1079,9 +1087,6 @@ for more details.
|
||||||
# Stop downloading additional metadata/videos if it
|
# Stop downloading additional metadata/videos if it
|
||||||
# exists in your download archive
|
# exists in your download archive
|
||||||
break_on_existing: True
|
break_on_existing: True
|
||||||
# Stop downloading additional metadata/videos if it
|
|
||||||
# is out of your date range
|
|
||||||
break_on_reject: True
|
|
||||||
# Path to your YouTube cookies file to download 18+ restricted content
|
# Path to your YouTube cookies file to download 18+ restricted content
|
||||||
cookiefile: "/path/to/cookies/file.txt"
|
cookiefile: "/path/to/cookies/file.txt"
|
||||||
# Only download this number of videos/audio
|
# Only download this number of videos/audio
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,6 @@ class YTDLOptions(LiteralDictValidator):
|
||||||
# Stop downloading additional metadata/videos if it
|
# Stop downloading additional metadata/videos if it
|
||||||
# exists in your download archive
|
# exists in your download archive
|
||||||
break_on_existing: True
|
break_on_existing: True
|
||||||
# Stop downloading additional metadata/videos if it
|
|
||||||
# is out of your date range
|
|
||||||
break_on_reject: True
|
|
||||||
# Path to your YouTube cookies file to download 18+ restricted content
|
# Path to your YouTube cookies file to download 18+ restricted content
|
||||||
cookiefile: "/path/to/cookies/file.txt"
|
cookiefile: "/path/to/cookies/file.txt"
|
||||||
# Only download this number of videos/audio
|
# Only download this number of videos/audio
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ class YTDLP:
|
||||||
except RejectedVideoReached:
|
except RejectedVideoReached:
|
||||||
cls.logger.debug(
|
cls.logger.debug(
|
||||||
"RejectedVideoReached, stopping additional downloads "
|
"RejectedVideoReached, stopping additional downloads "
|
||||||
"(Can be disable by setting `ytdl_options.break_on_reject` to False)."
|
"(Can be disable by setting `date_range.breaking` to False)."
|
||||||
)
|
)
|
||||||
except ExistingVideoReached:
|
except ExistingVideoReached:
|
||||||
cls.logger.debug(
|
cls.logger.debug(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ from typing import Tuple
|
||||||
from ytdl_sub.config.plugin.plugin import Plugin
|
from ytdl_sub.config.plugin.plugin import Plugin
|
||||||
from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator
|
from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator
|
||||||
from ytdl_sub.utils.datetime import to_date_str
|
from ytdl_sub.utils.datetime import to_date_str
|
||||||
|
from ytdl_sub.utils.script import ScriptUtils
|
||||||
from ytdl_sub.validators.string_datetime import StringDatetimeValidator
|
from ytdl_sub.validators.string_datetime import StringDatetimeValidator
|
||||||
|
from ytdl_sub.validators.string_formatter_validators import OverridesBooleanFormatterValidator
|
||||||
|
|
||||||
|
|
||||||
class DateRangeOptions(ToggleableOptionsDictValidator):
|
class DateRangeOptions(ToggleableOptionsDictValidator):
|
||||||
|
|
@ -31,12 +33,15 @@ class DateRangeOptions(ToggleableOptionsDictValidator):
|
||||||
after: "today-2weeks"
|
after: "today-2weeks"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_optional_keys = {"enable", "before", "after"}
|
_optional_keys = {"enable", "before", "after", "breaks"}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self._before = self._validate_key_if_present("before", StringDatetimeValidator)
|
self._before = self._validate_key_if_present("before", StringDatetimeValidator)
|
||||||
self._after = self._validate_key_if_present("after", StringDatetimeValidator)
|
self._after = self._validate_key_if_present("after", StringDatetimeValidator)
|
||||||
|
self._breaks = self._validate_key_if_present(
|
||||||
|
"breaks", OverridesBooleanFormatterValidator, default="True"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def before(self) -> Optional[StringDatetimeValidator]:
|
def before(self) -> Optional[StringDatetimeValidator]:
|
||||||
|
|
@ -56,6 +61,16 @@ class DateRangeOptions(ToggleableOptionsDictValidator):
|
||||||
"""
|
"""
|
||||||
return self._after
|
return self._after
|
||||||
|
|
||||||
|
@property
|
||||||
|
def breaks(self) -> OverridesBooleanFormatterValidator:
|
||||||
|
"""
|
||||||
|
:expected type: Optional[OverridesFormatter]
|
||||||
|
:description:
|
||||||
|
Toggle to enable breaking subsequent metadata downloads if an entry's upload date
|
||||||
|
is out of range. Defaults to True.
|
||||||
|
"""
|
||||||
|
return self._breaks
|
||||||
|
|
||||||
|
|
||||||
class DateRangePlugin(Plugin[DateRangeOptions]):
|
class DateRangePlugin(Plugin[DateRangeOptions]):
|
||||||
plugin_options_type = DateRangeOptions
|
plugin_options_type = DateRangeOptions
|
||||||
|
|
@ -79,6 +94,12 @@ class DateRangePlugin(Plugin[DateRangeOptions]):
|
||||||
after_str = to_date_str(
|
after_str = to_date_str(
|
||||||
date_validator=self.plugin_options.after, overrides=self.overrides
|
date_validator=self.plugin_options.after, overrides=self.overrides
|
||||||
)
|
)
|
||||||
breaking_match_filters.append(f"upload_date >= {after_str}")
|
after_filter = f"upload_date >= {after_str}"
|
||||||
|
if ScriptUtils.bool_formatter_output(
|
||||||
|
self.overrides.apply_formatter(self.plugin_options.breaks)
|
||||||
|
):
|
||||||
|
breaking_match_filters.append(after_filter)
|
||||||
|
else:
|
||||||
|
match_filters.append(after_filter)
|
||||||
|
|
||||||
return match_filters, breaking_match_filters
|
return match_filters, breaking_match_filters
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,12 @@ def combine_filters(filters: List[str], to_combine: List[str]) -> List[str]:
|
||||||
if not filters:
|
if not filters:
|
||||||
return copy.deepcopy(to_combine)
|
return copy.deepcopy(to_combine)
|
||||||
|
|
||||||
if len(to_combine) > 1:
|
|
||||||
raise ValueError("Match-filters to combine only supports 1 at this time")
|
|
||||||
|
|
||||||
output_filters: List[str] = []
|
output_filters: List[str] = []
|
||||||
filter_to_combine: str = to_combine[0]
|
|
||||||
|
|
||||||
for match_filter in filters:
|
for match_filter in filters:
|
||||||
output_filters.append(f"{match_filter} & {filter_to_combine}")
|
output_filters.append(match_filter)
|
||||||
|
for filter_combine in to_combine:
|
||||||
|
output_filters[-1] += f" & {filter_combine}"
|
||||||
|
|
||||||
return output_filters
|
return output_filters
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ presets:
|
||||||
max_downloads: 20
|
max_downloads: 20
|
||||||
playlistreverse: True
|
playlistreverse: True
|
||||||
break_on_existing: False
|
break_on_existing: False
|
||||||
break_on_reject: True
|
|
||||||
|
|
||||||
"Download in Chunks":
|
"Download in Chunks":
|
||||||
preset:
|
preset:
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,6 @@ class SubscriptionYTDLOptions:
|
||||||
"extract_flat": "discard", # do not store info.json in mem since its in file
|
"extract_flat": "discard", # do not store info.json in mem since its in file
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
|
||||||
def _download_only_options(self) -> Dict:
|
|
||||||
return {"break_on_reject": True}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _output_options(self) -> Dict:
|
def _output_options(self) -> Dict:
|
||||||
ytdl_options = {}
|
ytdl_options = {}
|
||||||
|
|
@ -199,7 +195,6 @@ class SubscriptionYTDLOptions:
|
||||||
self._plugin_ytdl_options(FormatPlugin),
|
self._plugin_ytdl_options(FormatPlugin),
|
||||||
self._plugin_ytdl_options(AudioExtractPlugin), # will override format
|
self._plugin_ytdl_options(AudioExtractPlugin), # will override format
|
||||||
self._user_ytdl_options, # user ytdl options...
|
self._user_ytdl_options, # user ytdl options...
|
||||||
self._download_only_options, # then download_only options
|
|
||||||
)
|
)
|
||||||
# Add dry run options last if enabled
|
# Add dry run options last if enabled
|
||||||
if self._dry_run:
|
if self._dry_run:
|
||||||
|
|
|
||||||
|
|
@ -40,20 +40,30 @@ def rolling_recent_channel_preset_dict(recent_preset_dict):
|
||||||
|
|
||||||
class TestDateRange:
|
class TestDateRange:
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
|
@pytest.mark.parametrize("date_range_breaks", [True, False])
|
||||||
def test_recent_channel_download(
|
def test_recent_channel_download(
|
||||||
self,
|
self,
|
||||||
recent_preset_dict,
|
recent_preset_dict,
|
||||||
tv_show_config,
|
tv_show_config,
|
||||||
output_directory,
|
output_directory,
|
||||||
dry_run,
|
dry_run: bool,
|
||||||
|
date_range_breaks: bool,
|
||||||
):
|
):
|
||||||
|
recent_preset_dict["date_range"]["breaks"] = date_range_breaks
|
||||||
recent_channel_subscription = Subscription.from_dict(
|
recent_channel_subscription = Subscription.from_dict(
|
||||||
config=tv_show_config,
|
config=tv_show_config,
|
||||||
preset_name="recent",
|
preset_name="recent",
|
||||||
preset_dict=recent_preset_dict,
|
preset_dict=recent_preset_dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
transaction_log = recent_channel_subscription.download(dry_run=dry_run)
|
with assert_logs(
|
||||||
|
logger=YTDLP.logger,
|
||||||
|
expected_message="RejectedVideoReached, stopping additional downloads",
|
||||||
|
log_level="debug",
|
||||||
|
expected_occurrences=1 if date_range_breaks else 0,
|
||||||
|
):
|
||||||
|
transaction_log = recent_channel_subscription.download(dry_run=dry_run)
|
||||||
|
|
||||||
assert_transaction_log_matches(
|
assert_transaction_log_matches(
|
||||||
output_directory=output_directory,
|
output_directory=output_directory,
|
||||||
transaction_log=transaction_log,
|
transaction_log=transaction_log,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ def channel_preset_dict(output_directory):
|
||||||
"format": "worst[ext=mp4]", # download the worst format so it is fast
|
"format": "worst[ext=mp4]", # download the worst format so it is fast
|
||||||
"ytdl_options": {
|
"ytdl_options": {
|
||||||
"max_views": 100000, # do not download the popular PJ concert
|
"max_views": 100000, # do not download the popular PJ concert
|
||||||
"break_on_reject": False, # do not break from max views
|
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"subtitles_name": "{episode_file_path}.{lang}.{subtitles_ext}",
|
"subtitles_name": "{episode_file_path}.{lang}.{subtitles_ext}",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue