diff --git a/docs/source/faq/index.rst b/docs/source/faq/index.rst index da43c1cf..bb5f4fe0 100644 --- a/docs/source/faq/index.rst +++ b/docs/source/faq/index.rst @@ -287,3 +287,8 @@ tell yt-dlp to explicitly download English metadata using. :alt: The Plex Agents settings page has Local Media Assets enabled for Personal Media Shows and Movies tabs. + +...ytdl-sub errors when downloading a 360p video with resolution assert +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:ref:`See how to either ignore this specific video or disable resolution assertion entirely here. ` diff --git a/docs/source/prebuilt_presets/helpers.rst b/docs/source/prebuilt_presets/helpers.rst index 83505fff..86752ed4 100644 --- a/docs/source/prebuilt_presets/helpers.rst +++ b/docs/source/prebuilt_presets/helpers.rst @@ -176,3 +176,21 @@ following overrides: enable_resolution_assert: false # Change the resolution below which to assume downloading is throttled: resolution_assert_height_gte: 720 + +.. _resolution assert handling: + +Handling Low Quality Videos +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A side effect from throttle protection's resolution assert is, if the only resolution available is 360p or lower, it will +error. You can either disable resolution assert entirely (see above), or ignore specific titles in the subscription +using the ``resolution_assert_ignore_titles`` variable. Add a subset of the title (case-sensitive) as a list entry +to your subscription, like so: + +.. code-block:: yaml + + # use tilda mode to set override variables to the subscription + "~My Subscription": + url: "https://youtube.com/@channel" + resolution_assert_ignore_titles: + - "This 360p Video Title" diff --git a/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml b/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml index 2f3afee3..1194e004 100644 --- a/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +++ b/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml @@ -47,6 +47,16 @@ presets: -1 ) } + # list variable that contains partial titles to ignore + resolution_assert_ignore_titles: "{ [] }" + resolution_assert_is_ignored: >- + { + %print_if_true( + %concat(title, " has a match in resolution_assert_ignore_titles, skipping resolution assert."), + %contains_any(title, resolution_assert_ignore_titles) + ) + } + resolution_readable: "{width}x{height}" # height is not a guaranteed populated variable, do not assert if it's missing (which defaults to 0) resolution_assert: >- @@ -54,7 +64,8 @@ presets: %if( %and( enable_resolution_assert, - %ne( height, 0 ) + %ne( height, 0 ), + %not(resolution_assert_is_ignored) ), %assert( %gte( height, resolution_assert_height_gte ), diff --git a/tests/integration/plugins/test_throttle_protection.py b/tests/integration/plugins/test_throttle_protection.py index 391ec299..6154f70a 100644 --- a/tests/integration/plugins/test_throttle_protection.py +++ b/tests/integration/plugins/test_throttle_protection.py @@ -290,6 +290,54 @@ class TestResolutionAssert: ): _ = subscription.download(dry_run=True) + def test_ignore_title_low_resolution( + self, + config, + subscription_name, + throttle_subscription_dict, + output_directory, + mock_download_collection_entries, + ): + throttle_subscription_dict["throttle_protection"]["max_downloads_per_subscription"] = { + "max": 1, + "min": 1, + } + throttle_subscription_dict["overrides"]["resolution_assert_ignore_titles"] = [ + "Entry 20-3", + ] + subscription = Subscription.from_dict( + config=config, + preset_name=subscription_name, + preset_dict=throttle_subscription_dict, + ) + + # expected_message = ( + # "Entry Mock Entry 20-3 downloaded at a low resolution (640x360), " + # "you've probably been throttled. " + # "Stopping further downloads, wait a few hours and try again. " + # "Disable using the override variable `enable_resolution_assert: False`" + # ) + + with ( + mock_download_collection_entries( + is_youtube_channel=False, + num_urls=1, + is_extracted_audio=False, + is_dry_run=True, + mock_entry_kwargs={"height": 360, "width": 640}, + ), + assert_logs( + logger=script_print_logger, + expected_message=( + "Mock Entry 20-3 has a match in resolution_assert_ignore_titles, " + "skipping resolution assert." + ), + log_level="info", + expected_occurrences=1, + ), + ): + _ = subscription.download(dry_run=True) + def test_sleep_per_download_supports_entry_variables( self, config,