[FEATURE] Throttle protection per request (#1229)

Allows adding a sleep inbetween every request during metadata scrape.

```
throttle_protection:
  sleep_per_request_s:
    min: 4
    max: 6
```

Closes https://github.com/jmbannon/ytdl-sub/issues/1222
This commit is contained in:
Jesse Bannon 2025-05-31 09:41:14 -07:00 committed by GitHub
parent 987b1cd028
commit bc15fe09bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 63 additions and 1 deletions

View file

@ -903,6 +903,9 @@ scripted.
presets: presets:
my_example_preset: my_example_preset:
throttle_protection: throttle_protection:
sleep_per_request_s:
min: 5.5
max: 10.4
sleep_per_download_s: sleep_per_download_s:
min: 2.2 min: 2.2
max: 10.8 max: 10.8
@ -938,6 +941,16 @@ scripted.
ytdl-sub to perform post-processing. ytdl-sub to perform post-processing.
``sleep_per_request_s``
:expected type: Optional[Range]
:description:
Number in seconds to sleep between each request during metadata download. Note that
metadata download refers to the initial info.json download, not the actual audio/video
download for the entry. Also, yt-dlp only supports a single value at this time for this,
so will always use the max value.
``sleep_per_subscription_s`` ``sleep_per_subscription_s``
:expected type: Optional[Range] :expected type: Optional[Range]

View file

@ -146,7 +146,12 @@ class SubtitlesPlugin(Plugin[SubtitleOptions]):
plugin_options_type = SubtitleOptions plugin_options_type = SubtitleOptions
def ytdl_options(self) -> Optional[Dict]: def ytdl_options(self) -> Optional[Dict]:
builder = YTDLOptionsBuilder().add({"writesubtitles": True}) builder = YTDLOptionsBuilder().add(
{
"writesubtitles": True,
"sleep_interval_subtitles": 5.367, # for safe measure
}
)
if self.plugin_options.embed_subtitles: if self.plugin_options.embed_subtitles:
builder.add( builder.add(

View file

@ -1,5 +1,6 @@
import random import random
import time import time
from typing import Dict
from typing import Optional from typing import Optional
from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.overrides import Overrides
@ -40,6 +41,22 @@ class RandomizedRangeValidator(StrictDictValidator):
f"max ({self._max}) must be greater than or equal to min ({self._min})" f"max ({self._max}) must be greater than or equal to min ({self._min})"
) )
def min_value(self) -> float:
"""
Returns
-------
Minimum value
"""
return self._min
def max_value(self) -> float:
"""
Returns
-------
Maximum value
"""
return self._max
def randomized_float(self) -> float: def randomized_float(self) -> float:
""" """
Returns Returns
@ -70,6 +87,9 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
presets: presets:
my_example_preset: my_example_preset:
throttle_protection: throttle_protection:
sleep_per_request_s:
min: 5.5
max: 10.4
sleep_per_download_s: sleep_per_download_s:
min: 2.2 min: 2.2
max: 10.8 max: 10.8
@ -84,6 +104,7 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
_optional_keys = { _optional_keys = {
"enable", "enable",
"sleep_per_request_s",
"sleep_per_download_s", "sleep_per_download_s",
"sleep_per_subscription_s", "sleep_per_subscription_s",
"max_downloads_per_subscription", "max_downloads_per_subscription",
@ -93,6 +114,9 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
def __init__(self, name, value): def __init__(self, name, value):
super().__init__(name, value) super().__init__(name, value)
self._sleep_per_request_s = self._validate_key_if_present(
key="sleep_per_request_s", validator=RandomizedRangeValidator
)
self._sleep_per_download_s = self._validate_key_if_present( self._sleep_per_download_s = self._validate_key_if_present(
key="sleep_per_download_s", validator=RandomizedRangeValidator key="sleep_per_download_s", validator=RandomizedRangeValidator
) )
@ -106,6 +130,18 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
key="subscription_download_probability", validator=ProbabilityValidator key="subscription_download_probability", validator=ProbabilityValidator
) )
@property
def sleep_per_request_s(self) -> Optional[RandomizedRangeValidator]:
"""
:expected type: Optional[Range]
:description:
Number in seconds to sleep between each request during metadata download. Note that
metadata download refers to the initial info.json download, not the actual audio/video
download for the entry. Also, yt-dlp only supports a single value at this time for this,
so will always use the max value.
"""
return self._sleep_per_request_s
@property @property
def sleep_per_download_s(self) -> Optional[RandomizedRangeValidator]: def sleep_per_download_s(self) -> Optional[RandomizedRangeValidator]:
""" """
@ -165,6 +201,11 @@ class ThrottleProtectionPlugin(Plugin[ThrottleProtectionOptions]):
self.plugin_options.max_downloads_per_subscription.randomized_int() self.plugin_options.max_downloads_per_subscription.randomized_int()
) )
def ytdl_options(self) -> Optional[Dict]:
if self.plugin_options.sleep_per_request_s is not None:
return {"sleep_interval_requests": self.plugin_options.sleep_per_request_s.max_value()}
return {}
def initialize_subscription(self) -> bool: def initialize_subscription(self) -> bool:
if self.plugin_options.subscription_download_probability: if self.plugin_options.subscription_download_probability:
proba = self.plugin_options.subscription_download_probability.value proba = self.plugin_options.subscription_download_probability.value

View file

@ -19,6 +19,7 @@ from ytdl_sub.plugins.match_filters import MatchFiltersPlugin
from ytdl_sub.plugins.match_filters import combine_filters from ytdl_sub.plugins.match_filters import combine_filters
from ytdl_sub.plugins.match_filters import default_filters from ytdl_sub.plugins.match_filters import default_filters
from ytdl_sub.plugins.subtitles import SubtitlesPlugin from ytdl_sub.plugins.subtitles import SubtitlesPlugin
from ytdl_sub.plugins.throttle_protection import ThrottleProtectionPlugin
from ytdl_sub.utils.ffmpeg import FFMPEG from ytdl_sub.utils.ffmpeg import FFMPEG
from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.logger import Logger
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
@ -175,6 +176,7 @@ class SubscriptionYTDLOptions:
self._global_options, self._global_options,
self._output_options, self._output_options,
self._plugin_match_filters, self._plugin_match_filters,
self._plugin_ytdl_options(ThrottleProtectionPlugin),
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...

View file

@ -19,6 +19,7 @@ def subscription_dict(output_directory):
"subscription_indent_1": "Acoustic", "subscription_indent_1": "Acoustic",
"music_directory": output_directory, "music_directory": output_directory,
}, },
"throttle_protection": {"sleep_per_request_s": {"min": 0.1, "max": 0.5}},
} }