mirror of https://github.com/yt-dlp/yt-dlp.git
parent
9e49146352
commit
23326151c4
|
@ -453,6 +453,9 @@ class YoutubeDL:
|
||||||
Allowed keys are 'download', 'postprocess',
|
Allowed keys are 'download', 'postprocess',
|
||||||
'download-title' (console title) and 'postprocess-title'.
|
'download-title' (console title) and 'postprocess-title'.
|
||||||
The template is mapped on a dictionary with keys 'progress' and 'info'
|
The template is mapped on a dictionary with keys 'progress' and 'info'
|
||||||
|
retry_sleep_functions: Dictionary of functions that takes the number of attempts
|
||||||
|
as argument and returns the time to sleep in seconds.
|
||||||
|
Allowed keys are 'http', 'fragment', 'file_access'
|
||||||
|
|
||||||
The following parameters are not used by YoutubeDL itself, they are used by
|
The following parameters are not used by YoutubeDL itself, they are used by
|
||||||
the downloader (see yt_dlp/downloader/common.py):
|
the downloader (see yt_dlp/downloader/common.py):
|
||||||
|
|
|
@ -247,6 +247,28 @@ def validate_options(opts):
|
||||||
opts.extractor_retries = parse_retries('extractor', opts.extractor_retries)
|
opts.extractor_retries = parse_retries('extractor', opts.extractor_retries)
|
||||||
opts.file_access_retries = parse_retries('file access', opts.file_access_retries)
|
opts.file_access_retries = parse_retries('file access', opts.file_access_retries)
|
||||||
|
|
||||||
|
# Retry sleep function
|
||||||
|
def parse_sleep_func(expr):
|
||||||
|
NUMBER_RE = r'\d+(?:\.\d+)?'
|
||||||
|
op, start, limit, step, *_ = tuple(re.fullmatch(
|
||||||
|
rf'(?:(linear|exp)=)?({NUMBER_RE})(?::({NUMBER_RE}))?(?::({NUMBER_RE}))?',
|
||||||
|
expr.strip()).groups()) + (None, None)
|
||||||
|
|
||||||
|
if op == 'exp':
|
||||||
|
return lambda n: min(float(start) * (float(step or 2) ** n), float(limit or 'inf'))
|
||||||
|
else:
|
||||||
|
default_step = start if op or limit else 0
|
||||||
|
return lambda n: min(float(start) + float(step or default_step) * n, float(limit or 'inf'))
|
||||||
|
|
||||||
|
for key, expr in opts.retry_sleep.items():
|
||||||
|
if not expr:
|
||||||
|
del opts.retry_sleep[key]
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
opts.retry_sleep[key] = parse_sleep_func(expr)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise ValueError(f'invalid {key} retry sleep expression {expr!r}: {e}')
|
||||||
|
|
||||||
# Bytes
|
# Bytes
|
||||||
def parse_bytes(name, value):
|
def parse_bytes(name, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -694,6 +716,7 @@ def parse_options(argv=None):
|
||||||
'file_access_retries': opts.file_access_retries,
|
'file_access_retries': opts.file_access_retries,
|
||||||
'fragment_retries': opts.fragment_retries,
|
'fragment_retries': opts.fragment_retries,
|
||||||
'extractor_retries': opts.extractor_retries,
|
'extractor_retries': opts.extractor_retries,
|
||||||
|
'retry_sleep_functions': opts.retry_sleep,
|
||||||
'skip_unavailable_fragments': opts.skip_unavailable_fragments,
|
'skip_unavailable_fragments': opts.skip_unavailable_fragments,
|
||||||
'keep_fragments': opts.keep_fragments,
|
'keep_fragments': opts.keep_fragments,
|
||||||
'concurrent_fragment_downloads': opts.concurrent_fragment_downloads,
|
'concurrent_fragment_downloads': opts.concurrent_fragment_downloads,
|
||||||
|
|
|
@ -19,6 +19,7 @@ from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
error_to_compat_str,
|
error_to_compat_str,
|
||||||
format_bytes,
|
format_bytes,
|
||||||
|
int_or_none,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
shell_quote,
|
shell_quote,
|
||||||
timeconvert,
|
timeconvert,
|
||||||
|
@ -64,6 +65,7 @@ class FileDownloader:
|
||||||
useful for bypassing bandwidth throttling imposed by
|
useful for bypassing bandwidth throttling imposed by
|
||||||
a webserver (experimental)
|
a webserver (experimental)
|
||||||
progress_template: See YoutubeDL.py
|
progress_template: See YoutubeDL.py
|
||||||
|
retry_sleep_functions: See YoutubeDL.py
|
||||||
|
|
||||||
Subclasses of this one must re-define the real_download method.
|
Subclasses of this one must re-define the real_download method.
|
||||||
"""
|
"""
|
||||||
|
@ -98,6 +100,8 @@ class FileDownloader:
|
||||||
def to_screen(self, *args, **kargs):
|
def to_screen(self, *args, **kargs):
|
||||||
self.ydl.to_screen(*args, quiet=self.params.get('quiet'), **kargs)
|
self.ydl.to_screen(*args, quiet=self.params.get('quiet'), **kargs)
|
||||||
|
|
||||||
|
__to_screen = to_screen
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def FD_NAME(self):
|
def FD_NAME(self):
|
||||||
return re.sub(r'(?<!^)(?=[A-Z])', '_', type(self).__name__[:-2]).lower()
|
return re.sub(r'(?<!^)(?=[A-Z])', '_', type(self).__name__[:-2]).lower()
|
||||||
|
@ -232,7 +236,8 @@ class FileDownloader:
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
f'[download] Unable to {action} file due to file access error. '
|
f'[download] Unable to {action} file due to file access error. '
|
||||||
f'Retrying (attempt {retry} of {self.format_retries(file_access_retries)}) ...')
|
f'Retrying (attempt {retry} of {self.format_retries(file_access_retries)}) ...')
|
||||||
time.sleep(0.01)
|
if not self.sleep_retry('file_access', retry):
|
||||||
|
time.sleep(0.01)
|
||||||
return inner
|
return inner
|
||||||
return outer
|
return outer
|
||||||
|
|
||||||
|
@ -390,14 +395,23 @@ class FileDownloader:
|
||||||
|
|
||||||
def report_retry(self, err, count, retries):
|
def report_retry(self, err, count, retries):
|
||||||
"""Report retry in case of HTTP error 5xx"""
|
"""Report retry in case of HTTP error 5xx"""
|
||||||
self.to_screen(
|
self.__to_screen(
|
||||||
'[download] Got server HTTP error: %s. Retrying (attempt %d of %s) ...'
|
'[download] Got server HTTP error: %s. Retrying (attempt %d of %s) ...'
|
||||||
% (error_to_compat_str(err), count, self.format_retries(retries)))
|
% (error_to_compat_str(err), count, self.format_retries(retries)))
|
||||||
|
self.sleep_retry('http', count)
|
||||||
|
|
||||||
def report_unable_to_resume(self):
|
def report_unable_to_resume(self):
|
||||||
"""Report it was impossible to resume download."""
|
"""Report it was impossible to resume download."""
|
||||||
self.to_screen('[download] Unable to resume')
|
self.to_screen('[download] Unable to resume')
|
||||||
|
|
||||||
|
def sleep_retry(self, retry_type, count):
|
||||||
|
sleep_func = self.params.get('retry_sleep_functions', {}).get(retry_type)
|
||||||
|
delay = int_or_none(sleep_func(n=count - 1)) if sleep_func else None
|
||||||
|
if delay:
|
||||||
|
self.__to_screen(f'Sleeping {delay} seconds ...')
|
||||||
|
time.sleep(delay)
|
||||||
|
return sleep_func is not None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supports_manifest(manifest):
|
def supports_manifest(manifest):
|
||||||
""" Whether the downloader can download the fragments from the manifest.
|
""" Whether the downloader can download the fragments from the manifest.
|
||||||
|
|
|
@ -142,6 +142,7 @@ class ExternalFD(FragmentFD):
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'[%s] Got error. Retrying fragments (attempt %d of %s)...'
|
'[%s] Got error. Retrying fragments (attempt %d of %s)...'
|
||||||
% (self.get_basename(), count, self.format_retries(fragment_retries)))
|
% (self.get_basename(), count, self.format_retries(fragment_retries)))
|
||||||
|
self.sleep_retry('fragment', count)
|
||||||
if count > fragment_retries:
|
if count > fragment_retries:
|
||||||
if not skip_unavailable_fragments:
|
if not skip_unavailable_fragments:
|
||||||
self.report_error('Giving up after %s fragment retries' % fragment_retries)
|
self.report_error('Giving up after %s fragment retries' % fragment_retries)
|
||||||
|
|
|
@ -25,10 +25,6 @@ class HttpQuietDownloader(HttpFD):
|
||||||
|
|
||||||
console_title = to_screen
|
console_title = to_screen
|
||||||
|
|
||||||
def report_retry(self, err, count, retries):
|
|
||||||
super().to_screen(
|
|
||||||
f'[download] Got server HTTP error: {err}. Retrying (attempt {count} of {self.format_retries(retries)}) ...')
|
|
||||||
|
|
||||||
|
|
||||||
class FragmentFD(FileDownloader):
|
class FragmentFD(FileDownloader):
|
||||||
"""
|
"""
|
||||||
|
@ -70,6 +66,7 @@ class FragmentFD(FileDownloader):
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'\r[download] Got server HTTP error: %s. Retrying fragment %d (attempt %d of %s) ...'
|
'\r[download] Got server HTTP error: %s. Retrying fragment %d (attempt %d of %s) ...'
|
||||||
% (error_to_compat_str(err), frag_index, count, self.format_retries(retries)))
|
% (error_to_compat_str(err), frag_index, count, self.format_retries(retries)))
|
||||||
|
self.sleep_retry('fragment', count)
|
||||||
|
|
||||||
def report_skip_fragment(self, frag_index, err=None):
|
def report_skip_fragment(self, frag_index, err=None):
|
||||||
err = f' {err};' if err else ''
|
err = f' {err};' if err else ''
|
||||||
|
|
|
@ -828,6 +828,18 @@ def create_parser():
|
||||||
'--fragment-retries',
|
'--fragment-retries',
|
||||||
dest='fragment_retries', metavar='RETRIES', default=10,
|
dest='fragment_retries', metavar='RETRIES', default=10,
|
||||||
help='Number of retries for a fragment (default is %default), or "infinite" (DASH, hlsnative and ISM)')
|
help='Number of retries for a fragment (default is %default), or "infinite" (DASH, hlsnative and ISM)')
|
||||||
|
downloader.add_option(
|
||||||
|
'--retry-sleep',
|
||||||
|
dest='retry_sleep', metavar='[TYPE:]EXPR', default={}, type='str',
|
||||||
|
action='callback', callback=_dict_from_options_callback,
|
||||||
|
callback_kwargs={
|
||||||
|
'allowed_keys': 'http|fragment|file_access',
|
||||||
|
'default_key': 'http',
|
||||||
|
}, help=(
|
||||||
|
'An expression for the time to sleep between retries in seconds (optionally) prefixed '
|
||||||
|
'by the type of retry (http (default), fragment, file_access) to apply the sleep to. '
|
||||||
|
'EXPR can be a number, or of the forms linear=START[:END[:STEP=1]] or exp=START[:END[:BASE=2]]. '
|
||||||
|
'Eg: --retry-sleep linear=1::2 --retry-sleep fragment:exp=1:20'))
|
||||||
downloader.add_option(
|
downloader.add_option(
|
||||||
'--skip-unavailable-fragments', '--no-abort-on-unavailable-fragment',
|
'--skip-unavailable-fragments', '--no-abort-on-unavailable-fragment',
|
||||||
action='store_true', dest='skip_unavailable_fragments', default=True,
|
action='store_true', dest='skip_unavailable_fragments', default=True,
|
||||||
|
|
Loading…
Reference in New Issue