[FEATURE] resolution_assert_ignore_titles to ignore specific low-resolution videos (#1336)

Closes https://github.com/jmbannon/ytdl-sub/issues/1313

Adds `resolution_assert_ignore_titles` variable to be able to ignore specific videos in a subscription where 360p is suspected to be the only option.

Ideally, it would be nice if ytdl-sub could determine whether 360p is actually the only resolution or not automatically, and download if it's true. We need to do more research to determine if this is possible.

In the meantime, the above variable should help manually get around this. Usage:

```
   # 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"
```
This commit is contained in:
Jesse Bannon 2025-09-13 11:32:54 -07:00 committed by GitHub
parent 0c34f819de
commit 45c155c039
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 83 additions and 1 deletions

View file

@ -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. <resolution assert handling>`

View file

@ -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"

View file

@ -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 ),

View file

@ -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,