mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-03-09 12:50:23 -05:00
Merge f418dff724
into 05c8023a27
This commit is contained in:
commit
d4d58d0170
5 changed files with 93 additions and 46 deletions
|
@ -352,9 +352,13 @@ ## General Options:
|
||||||
(Experimental)
|
(Experimental)
|
||||||
--no-live-from-start Download livestreams from the current time
|
--no-live-from-start Download livestreams from the current time
|
||||||
(default)
|
(default)
|
||||||
--wait-for-video MIN[-MAX] Wait for scheduled streams to become
|
--wait-for-video MIN[-MAX][:RETRIES]
|
||||||
|
Wait for scheduled streams to become
|
||||||
available. Pass the minimum number of
|
available. Pass the minimum number of
|
||||||
seconds (or range) to wait between retries
|
seconds (or range) to wait between retries.
|
||||||
|
RETRIES is the maximum number of additional
|
||||||
|
attempts if the video is still unavailable
|
||||||
|
after waiting (default is infinite)
|
||||||
--no-wait-for-video Do not wait for scheduled streams (default)
|
--no-wait-for-video Do not wait for scheduled streams (default)
|
||||||
--mark-watched Mark videos watched (even with --simulate)
|
--mark-watched Mark videos watched (even with --simulate)
|
||||||
--no-mark-watched Do not mark videos watched (default)
|
--no-mark-watched Do not mark videos watched (default)
|
||||||
|
|
|
@ -105,6 +105,7 @@
|
||||||
Popen,
|
Popen,
|
||||||
PostProcessingError,
|
PostProcessingError,
|
||||||
ReExtractInfo,
|
ReExtractInfo,
|
||||||
|
ReExtractInfoLater,
|
||||||
RejectedVideoReached,
|
RejectedVideoReached,
|
||||||
SameFileError,
|
SameFileError,
|
||||||
UnavailableVideoError,
|
UnavailableVideoError,
|
||||||
|
@ -1646,11 +1647,27 @@ def extract_info(self, url, download=True, ie_key=None, extra_info=None,
|
||||||
def _handle_extraction_exceptions(func):
|
def _handle_extraction_exceptions(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
|
wait_retries = 0
|
||||||
|
wait_range = self.params.get('wait_for_video')
|
||||||
|
max_retries = float('inf')
|
||||||
|
if wait_range and wait_range[2] is not None:
|
||||||
|
max_retries = wait_range[2]
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
return func(self, *args, **kwargs)
|
return func(self, *args, **kwargs)
|
||||||
except (CookieLoadError, DownloadCancelled, LazyList.IndexError, PagedList.IndexError):
|
except (CookieLoadError, DownloadCancelled, LazyList.IndexError, PagedList.IndexError):
|
||||||
raise
|
raise
|
||||||
|
except ReExtractInfoLater as e:
|
||||||
|
if wait_retries > max_retries:
|
||||||
|
if max_retries > 0:
|
||||||
|
self.report_error(f'[wait] Giving up after {wait_retries - 1} {"retries" if wait_retries != 2 else "retry"} while waiting.')
|
||||||
|
else:
|
||||||
|
self.report_error('[wait] Video is still unavailable after waiting.')
|
||||||
|
return
|
||||||
|
self._wait_until(e.time)
|
||||||
|
wait_retries += 1
|
||||||
|
self.to_screen('[wait] Re-extracting data')
|
||||||
|
continue
|
||||||
except ReExtractInfo as e:
|
except ReExtractInfo as e:
|
||||||
if e.expected:
|
if e.expected:
|
||||||
self.to_screen(f'{e}; Re-extracting data')
|
self.to_screen(f'{e}; Re-extracting data')
|
||||||
|
@ -1675,12 +1692,7 @@ def wrapper(self, *args, **kwargs):
|
||||||
break
|
break
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def _wait_for_video(self, ie_result={}):
|
def _wait_until(self, till):
|
||||||
if (not self.params.get('wait_for_video')
|
|
||||||
or ie_result.get('_type', 'video') != 'video'
|
|
||||||
or ie_result.get('formats') or ie_result.get('url')):
|
|
||||||
return
|
|
||||||
|
|
||||||
format_dur = lambda dur: '%02d:%02d:%02d' % timetuple_from_msec(dur * 1000)[:-1]
|
format_dur = lambda dur: '%02d:%02d:%02d' % timetuple_from_msec(dur * 1000)[:-1]
|
||||||
last_msg = ''
|
last_msg = ''
|
||||||
|
|
||||||
|
@ -1694,7 +1706,31 @@ def progress(msg):
|
||||||
self.to_screen(full_msg, skip_eol=True)
|
self.to_screen(full_msg, skip_eol=True)
|
||||||
last_msg = msg
|
last_msg = msg
|
||||||
|
|
||||||
min_wait, max_wait = self.params.get('wait_for_video')
|
diff = till - time.time()
|
||||||
|
self.to_screen(f'[wait] Waiting for {format_dur(diff)} - Press Ctrl+C to interrupt')
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
diff = till - time.time()
|
||||||
|
if diff <= 0:
|
||||||
|
progress('')
|
||||||
|
self.to_screen('[wait] Wait period ended')
|
||||||
|
return
|
||||||
|
progress(f'[wait] Remaining time until next attempt: {self._format_screen(format_dur(diff), self.Styles.EMPHASIS)}')
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
progress('')
|
||||||
|
self.to_screen('[wait] Interrupted by user')
|
||||||
|
except BaseException:
|
||||||
|
self.to_screen('')
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _wait_for_video(self, ie_result={}):
|
||||||
|
if (not self.params.get('wait_for_video')
|
||||||
|
or ie_result.get('_type', 'video') != 'video'
|
||||||
|
or ie_result.get('formats') or ie_result.get('url')):
|
||||||
|
return
|
||||||
|
|
||||||
|
min_wait, max_wait, _ = self.params.get('wait_for_video')
|
||||||
diff = try_get(ie_result, lambda x: x['release_timestamp'] - time.time())
|
diff = try_get(ie_result, lambda x: x['release_timestamp'] - time.time())
|
||||||
if diff is None and ie_result.get('live_status') == 'is_upcoming':
|
if diff is None and ie_result.get('live_status') == 'is_upcoming':
|
||||||
diff = round(random.uniform(min_wait, max_wait) if (max_wait and min_wait) else (max_wait or min_wait), 0)
|
diff = round(random.uniform(min_wait, max_wait) if (max_wait and min_wait) else (max_wait or min_wait), 0)
|
||||||
|
@ -1702,24 +1738,7 @@ def progress(msg):
|
||||||
elif ie_result and (diff or 0) <= 0:
|
elif ie_result and (diff or 0) <= 0:
|
||||||
self.report_warning('Video should already be available according to extracted info')
|
self.report_warning('Video should already be available according to extracted info')
|
||||||
diff = min(max(diff or 0, min_wait or 0), max_wait or float('inf'))
|
diff = min(max(diff or 0, min_wait or 0), max_wait or float('inf'))
|
||||||
self.to_screen(f'[wait] Waiting for {format_dur(diff)} - Press Ctrl+C to try now')
|
raise ReExtractInfoLater(time.time() + diff)
|
||||||
|
|
||||||
wait_till = time.time() + diff
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
diff = wait_till - time.time()
|
|
||||||
if diff <= 0:
|
|
||||||
progress('')
|
|
||||||
raise ReExtractInfo('[wait] Wait period ended', expected=True)
|
|
||||||
progress(f'[wait] Remaining time until next attempt: {self._format_screen(format_dur(diff), self.Styles.EMPHASIS)}')
|
|
||||||
time.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
progress('')
|
|
||||||
raise ReExtractInfo('[wait] Interrupted by user', expected=True)
|
|
||||||
except BaseException as e:
|
|
||||||
if not isinstance(e, ReExtractInfo):
|
|
||||||
self.to_screen('')
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _load_cookies(self, data, *, autoscope=True):
|
def _load_cookies(self, data, *, autoscope=True):
|
||||||
"""Loads cookies from a `Cookie` header
|
"""Loads cookies from a `Cookie` header
|
||||||
|
|
|
@ -224,12 +224,37 @@ def validate_minmax(min_val, max_val, min_name, max_name=None):
|
||||||
else:
|
else:
|
||||||
validate_minmax(opts.sleep_interval, opts.max_sleep_interval, 'sleep interval')
|
validate_minmax(opts.sleep_interval, opts.max_sleep_interval, 'sleep interval')
|
||||||
|
|
||||||
|
def parse_retries(name, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
elif value in ('inf', 'infinite'):
|
||||||
|
return float('inf')
|
||||||
|
try:
|
||||||
|
int_value = int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
validate(False, f'{name} retry count', value)
|
||||||
|
validate_positive(f'{name} retry count', int_value)
|
||||||
|
return int_value
|
||||||
|
|
||||||
|
def parse_range_with_arg(name, arg_name, value,
|
||||||
|
parse_limits=parse_duration, parse_arg=parse_retries):
|
||||||
|
# syntax: MIN[-MAX][:N]
|
||||||
|
m = re.fullmatch(r'([^-:]+)(-[^:]+)?(:.+)?', value)
|
||||||
|
validate(m, name, value)
|
||||||
|
min_val, max_val, arg_val = m.groups()
|
||||||
|
|
||||||
|
min_lim, max_lim = map(parse_limits, [min_val, (max_val and max_val[1:])])
|
||||||
|
validate(min_lim is not None, name, value)
|
||||||
|
validate(max_val is None or max_lim is not None, name, value)
|
||||||
|
validate_minmax(min_lim, max_lim, name)
|
||||||
|
|
||||||
|
parsed_arg = parse_arg(arg_name, arg_val and arg_val[1:])
|
||||||
|
return (min_lim, max_lim, parsed_arg)
|
||||||
|
|
||||||
if opts.wait_for_video is not None:
|
if opts.wait_for_video is not None:
|
||||||
min_wait, max_wait, *_ = map(parse_duration, [*opts.wait_for_video.split('-', 1), None])
|
min_wait, max_wait, wait_retries = parse_range_with_arg(
|
||||||
validate(min_wait is not None and not (max_wait is None and '-' in opts.wait_for_video),
|
'time range to wait for video', 'waiting', opts.wait_for_video)
|
||||||
'time range to wait for video', opts.wait_for_video)
|
opts.wait_for_video = (min_wait, max_wait, wait_retries)
|
||||||
validate_minmax(min_wait, max_wait, 'time range to wait for video')
|
|
||||||
opts.wait_for_video = (min_wait, max_wait)
|
|
||||||
|
|
||||||
# Format sort
|
# Format sort
|
||||||
for f in opts.format_sort:
|
for f in opts.format_sort:
|
||||||
|
@ -254,18 +279,6 @@ def validate_minmax(min_val, max_val, min_name, max_name=None):
|
||||||
validate_positive('audio quality', int_or_none(float_or_none(opts.audioquality), default=0))
|
validate_positive('audio quality', int_or_none(float_or_none(opts.audioquality), default=0))
|
||||||
|
|
||||||
# Retries
|
# Retries
|
||||||
def parse_retries(name, value):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
elif value in ('inf', 'infinite'):
|
|
||||||
return float('inf')
|
|
||||||
try:
|
|
||||||
int_value = int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
validate(False, f'{name} retry count', value)
|
|
||||||
validate_positive(f'{name} retry count', int_value)
|
|
||||||
return int_value
|
|
||||||
|
|
||||||
opts.retries = parse_retries('download', opts.retries)
|
opts.retries = parse_retries('download', opts.retries)
|
||||||
opts.fragment_retries = parse_retries('fragment', opts.fragment_retries)
|
opts.fragment_retries = parse_retries('fragment', opts.fragment_retries)
|
||||||
opts.extractor_retries = parse_retries('extractor', opts.extractor_retries)
|
opts.extractor_retries = parse_retries('extractor', opts.extractor_retries)
|
||||||
|
|
|
@ -445,10 +445,12 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
|
||||||
help='Download livestreams from the current time (default)')
|
help='Download livestreams from the current time (default)')
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--wait-for-video',
|
'--wait-for-video',
|
||||||
dest='wait_for_video', metavar='MIN[-MAX]', default=None,
|
dest='wait_for_video', metavar='MIN[-MAX][:RETRIES]', default=None,
|
||||||
help=(
|
help=(
|
||||||
'Wait for scheduled streams to become available. '
|
'Wait for scheduled streams to become available. '
|
||||||
'Pass the minimum number of seconds (or range) to wait between retries'))
|
'Pass the minimum number of seconds (or range) to wait between retries. '
|
||||||
|
'RETRIES is the maximum number of additional attempts if the video '
|
||||||
|
'is still unavailable after waiting (default is infinite)'))
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--no-wait-for-video',
|
'--no-wait-for-video',
|
||||||
dest='wait_for_video', action='store_const', const=None,
|
dest='wait_for_video', action='store_const', const=None,
|
||||||
|
|
|
@ -1115,6 +1115,15 @@ def __init__(self, msg, expected=False):
|
||||||
self.expected = expected
|
self.expected = expected
|
||||||
|
|
||||||
|
|
||||||
|
class ReExtractInfoLater(ReExtractInfo):
|
||||||
|
""" Video info needs to be re-extracted after a waiting period. """
|
||||||
|
msg = 'Video is not available yet'
|
||||||
|
|
||||||
|
def __init__(self, time):
|
||||||
|
super().__init__(self.msg, expected=True)
|
||||||
|
self.time = time
|
||||||
|
|
||||||
|
|
||||||
class ThrottledDownload(ReExtractInfo):
|
class ThrottledDownload(ReExtractInfo):
|
||||||
""" Download speed below --throttled-rate. """
|
""" Download speed below --throttled-rate. """
|
||||||
msg = 'The download speed is below throttle limit'
|
msg = 'The download speed is below throttle limit'
|
||||||
|
|
Loading…
Reference in a new issue