[BACKEND] Enable throttle protection by default (#1257)

To protect new users, all prebuilt preset (excluding Soundcloud and Bandcamp) will include throttle protection by default.


> How do I disable?

Set the override variable `enable_throttle_protection: False`.
This can be done on a per-subscription basis and/or in the top __preset__ section to apply to all presets:

```
__preset__:
  overrides:
    enable_throttle_protection: False
```

> What if I already use throttle protection?

Your settings will override whatever values are set in the defaults.

Closes:
- https://github.com/jmbannon/ytdl-sub/issues/1121
- https://github.com/jmbannon/ytdl-sub/issues/1186
This commit is contained in:
Jesse Bannon 2025-07-03 00:34:37 -07:00 committed by GitHub
parent a055c9b07a
commit 6c180c9478
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 64 additions and 10 deletions

View file

@ -185,6 +185,13 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
class ThrottleProtectionPlugin(Plugin[ThrottleProtectionOptions]):
plugin_options_type = ThrottleProtectionOptions
@classmethod
def perform_sleep(cls, sleep_time: float) -> None:
"""
Wrapper to be able to mock
"""
time.sleep(sleep_time)
def __init__(
self,
options: ThrottleProtectionOptions,
@ -239,7 +246,7 @@ class ThrottleProtectionPlugin(Plugin[ThrottleProtectionOptions]):
self._subscription_max_downloads is not None
and self._subscription_download_counter == 0
):
logger.debug(
logger.info(
"Setting subscription max downloads to %d", self._subscription_max_downloads
)
@ -248,8 +255,8 @@ class ThrottleProtectionPlugin(Plugin[ThrottleProtectionOptions]):
if self.plugin_options.sleep_per_download_s:
sleep_time = self.plugin_options.sleep_per_download_s.randomized_float()
logger.debug("Sleeping between downloads for %0.2f seconds", sleep_time)
time.sleep(sleep_time)
logger.info("Sleeping between downloads for %0.2f seconds", sleep_time)
self.perform_sleep(sleep_time)
return None
@ -265,5 +272,5 @@ class ThrottleProtectionPlugin(Plugin[ThrottleProtectionOptions]):
if self.plugin_options.sleep_per_subscription_s:
sleep_time = self.plugin_options.sleep_per_subscription_s.randomized_float()
logger.debug("Sleeping between subscriptions for %0.2f seconds", sleep_time)
time.sleep(sleep_time)
logger.info("Sleeping between subscriptions for %0.2f seconds", sleep_time)
self.perform_sleep(sleep_time)

View file

@ -0,0 +1,26 @@
presets:
_throttle_protection:
throttle_protection:
enable: >-
{
%print(
%if(
enable_throttle_protection,
"Throttle protection is enabled. Disable using the override variable `enable_throttle_protection: False`",
"Throttle protection is disabled. Use at your own risk!"
),
enable_throttle_protection
)
}
sleep_per_request_s:
min: 3.5
max: 3.5
sleep_per_download_s:
min: 13.8
max: 28.4
sleep_per_subscription_s:
min: 16.3
max: 26.1
overrides:
enable_throttle_protection: True

View file

@ -22,4 +22,5 @@ presets:
"YouTube Full Albums":
preset:
- "_albums_from_chapters"
- "_albums_from_chapters"
- "_throttle_protection"

View file

@ -18,6 +18,7 @@ presets:
"YouTube Releases":
preset:
- "_albums_from_playlists"
- "_throttle_protection"
"Bandcamp":
preset:

View file

@ -2,6 +2,7 @@ presets:
_music_base:
output_options:
output_directory: "{music_directory}"
file_name: "{track_full_path}"

View file

@ -2,6 +2,9 @@ presets:
_music_video_base:
preset:
# ytdl-sub includes throttle prootection by default or all prebuilt presets.
# Can be disabled with override variable `enable_throttle_protection: False`
- "_throttle_protection"
- "_url_categorized"
output_options:

View file

@ -3,6 +3,11 @@ presets:
####################################################################################################
_episode_base:
preset:
# ytdl-sub includes throttle prootection by default or all prebuilt presets.
# Can be disabled with override variable `enable_throttle_protection: False`
- "_throttle_protection"
output_options:
output_directory: "{tv_show_directory}/{tv_show_name_sanitized}"
file_name: "{episode_file_path}.{ext}"

View file

@ -9,6 +9,9 @@ logger = Logger.get(name="preset")
def _log(message: AnyArgument, level: Optional[Integer]) -> None:
if not str(message):
return
if level is None:
logger.info(str(message))
return

View file

@ -15,6 +15,7 @@ from ytdl_sub.downloaders.url.downloader import MultiUrlDownloader
from ytdl_sub.downloaders.ytdlp import YTDLP
from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
from ytdl_sub.plugins.throttle_protection import ThrottleProtectionPlugin
v: VariableDefinitions = VARIABLES
@ -264,6 +265,9 @@ def mock_download_collection_entries(
patch.object(
MultiUrlDownloader, "_extract_entry_info_with_retry", new=lambda _, entry: entry
),
# Throttle protection is included in all prebuilt presets. Mock the sleep avoid
# actual sleeps
patch.object(ThrottleProtectionPlugin, "perform_sleep", new=lambda _1, _2: None),
):
# Stub out metadata. TODO: update this if we do metadata plugins
yield

View file

@ -55,7 +55,7 @@ class TestThrottleProtectionPlugin:
assert_logs(
logger=throttle_protection_logger,
expected_message="Sleeping between downloads for %0.2f seconds",
log_level="debug",
log_level="info",
expected_occurrences=4,
),
):
@ -68,7 +68,7 @@ class TestThrottleProtectionPlugin:
assert_logs(
logger=throttle_protection_logger,
expected_message="Sleeping between subscriptions for %0.2f seconds",
log_level="debug",
log_level="info",
expected_occurrences=1,
),
):
@ -92,7 +92,7 @@ class TestThrottleProtectionPlugin:
mock_download_collection_entries,
disable_value,
):
throttle_subscription_dict["throttle_protection"]["enable"] = disable_value
throttle_subscription_dict["overrides"]["enable_throttle_protection"] = disable_value
subscription = Subscription.from_dict(
config=config,
preset_name=subscription_name,
@ -106,7 +106,7 @@ class TestThrottleProtectionPlugin:
assert_logs(
logger=throttle_protection_logger,
expected_message="Sleeping between downloads for %0.2f seconds",
log_level="debug",
log_level="info",
expected_occurrences=0,
),
):

View file

@ -15,12 +15,15 @@ class TestPrintFunctions:
("{%print('hi mom', True)}", "hi mom", True),
("{%print('this is great', [1, 2, 3])}", "this is great", [1, 2, 3]),
("{%print([1, 2], [3, 4])}", "[1, 2]", [3, 4]),
("{%print('', True)}", None, True),
# print_if_true
("{%print_if_true('hi mom', True)}", "hi mom", True),
("{%print_if_true('hi mom', False)}", None, False),
("{%print_if_true('', True)}", None, True),
# print_if_false
("{%print_if_false('hi mom', True)}", None, True),
("{%print_if_false('hi mom', False)}", "hi mom", False),
("{%print_if_false('', True)}", None, True),
],
)
def test_print_functions(