From c6cc3a8ab2dac238a329a120a705f82e555a1d5a Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Tue, 29 Oct 2024 14:25:52 +0100 Subject: [PATCH 01/11] Add option `--wait-retries` --- README.md | 4 ++++ yt_dlp/YoutubeDL.py | 9 +++++++++ yt_dlp/__init__.py | 2 ++ yt_dlp/options.py | 4 ++++ 4 files changed, 19 insertions(+) diff --git a/README.md b/README.md index 418203eea..6c34ba46d 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,10 @@ ## General Options: available. Pass the minimum number of seconds (or range) to wait between retries --no-wait-for-video Do not wait for scheduled streams (default) + --wait-retries RETRIES Number of retries while waiting for + scheduled streams to become available + (default is infinite). --wait-for-video must + also be set --mark-watched Mark videos watched (even with --simulate) --no-mark-watched Do not mark videos watched (default) --color [STREAM:]POLICY Whether to emit color codes in output, diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index f08a31afa..2dcfde92d 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1620,17 +1620,26 @@ def extract_info(self, url, download=True, ie_key=None, extra_info=None, def _handle_extraction_exceptions(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): + wait_retries = 1 + max_wait_retries = self.params.get('wait_retries') while True: try: return func(self, *args, **kwargs) except (CookieLoadError, DownloadCancelled, LazyList.IndexError, PagedList.IndexError): raise except ReExtractInfo as e: + if wait_retries > max_wait_retries: + if max_wait_retries > 0: + self.report_error(f'Giving up after {wait_retries - 1} {"retries" if wait_retries > 2 else "retry"} while waiting.') + else: + self.report_error(f'Video is still unavailable after waiting.') + raise UserNotLive('Maximum number of retries exceeded while waiting for stream') if e.expected: self.to_screen(f'{e}; Re-extracting data') else: self.to_stderr('\r') self.report_warning(f'{e}; Re-extracting data') + wait_retries += 1 continue except GeoRestrictedError as e: msg = e.msg diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 9b3bd4acd..5dc989873 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -266,6 +266,7 @@ def parse_retries(name, value): opts.retries = parse_retries('download', opts.retries) opts.fragment_retries = parse_retries('fragment', opts.fragment_retries) + opts.wait_retries = parse_retries('waiting', opts.wait_retries) opts.extractor_retries = parse_retries('extractor', opts.extractor_retries) opts.file_access_retries = parse_retries('file access', opts.file_access_retries) @@ -926,6 +927,7 @@ def parse_options(argv=None): 'extract_flat': opts.extract_flat, 'live_from_start': opts.live_from_start, 'wait_for_video': opts.wait_for_video, + 'wait_retries': opts.wait_retries, 'mark_watched': opts.mark_watched, 'merge_output_format': opts.merge_output_format, 'final_ext': final_ext, diff --git a/yt_dlp/options.py b/yt_dlp/options.py index c4d2a7274..e4df4e59e 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -442,6 +442,10 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs): '--no-wait-for-video', dest='wait_for_video', action='store_const', const=None, help='Do not wait for scheduled streams (default)') + general.add_option( + '--wait-retries', + dest='wait_retries', metavar='RETRIES', default="infinite", + help='Number of retries while waiting for scheduled streams to become available (default is %default). --wait-for-video must also be set') general.add_option( '--mark-watched', action='store_true', dest='mark_watched', default=False, From 87ad4d47741c73930c7f87ebec1ab17430f8a428 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Tue, 29 Oct 2024 16:02:57 +0100 Subject: [PATCH 02/11] Fix quotes --- yt_dlp/YoutubeDL.py | 2 +- yt_dlp/options.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 2dcfde92d..754234adc 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1632,7 +1632,7 @@ def wrapper(self, *args, **kwargs): if max_wait_retries > 0: self.report_error(f'Giving up after {wait_retries - 1} {"retries" if wait_retries > 2 else "retry"} while waiting.') else: - self.report_error(f'Video is still unavailable after waiting.') + self.report_error('Video is still unavailable after waiting.') raise UserNotLive('Maximum number of retries exceeded while waiting for stream') if e.expected: self.to_screen(f'{e}; Re-extracting data') diff --git a/yt_dlp/options.py b/yt_dlp/options.py index e4df4e59e..a778f35c0 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -444,7 +444,7 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs): help='Do not wait for scheduled streams (default)') general.add_option( '--wait-retries', - dest='wait_retries', metavar='RETRIES', default="infinite", + dest='wait_retries', metavar='RETRIES', default='infinite', help='Number of retries while waiting for scheduled streams to become available (default is %default). --wait-for-video must also be set') general.add_option( '--mark-watched', From 9438d15dff75e7c2066f1aee1ba7879f748de0ec Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Wed, 30 Oct 2024 13:55:26 +0100 Subject: [PATCH 03/11] Just return nothing on max retries, same as with extractor errors --- yt_dlp/YoutubeDL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 754234adc..919c8f6a5 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1633,7 +1633,7 @@ def wrapper(self, *args, **kwargs): self.report_error(f'Giving up after {wait_retries - 1} {"retries" if wait_retries > 2 else "retry"} while waiting.') else: self.report_error('Video is still unavailable after waiting.') - raise UserNotLive('Maximum number of retries exceeded while waiting for stream') + return if e.expected: self.to_screen(f'{e}; Re-extracting data') else: From 5c4df56d6d70e2a8e0ea50968e6bd6923b5a06d1 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Thu, 31 Oct 2024 14:10:47 +0100 Subject: [PATCH 04/11] Start counting from zero. --- yt_dlp/YoutubeDL.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 919c8f6a5..db173921f 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1620,7 +1620,7 @@ def extract_info(self, url, download=True, ie_key=None, extra_info=None, def _handle_extraction_exceptions(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): - wait_retries = 1 + wait_retries = 0 max_wait_retries = self.params.get('wait_retries') while True: try: @@ -1628,9 +1628,9 @@ def wrapper(self, *args, **kwargs): except (CookieLoadError, DownloadCancelled, LazyList.IndexError, PagedList.IndexError): raise except ReExtractInfo as e: - if wait_retries > max_wait_retries: + if wait_retries >= max_wait_retries: if max_wait_retries > 0: - self.report_error(f'Giving up after {wait_retries - 1} {"retries" if wait_retries > 2 else "retry"} while waiting.') + self.report_error(f'Giving up after {wait_retries} {"retries" if wait_retries > 2 else "retry"} while waiting.') else: self.report_error('Video is still unavailable after waiting.') return From 716972da6b48cc59c71cd11d3dd575b5610512c1 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Thu, 31 Oct 2024 14:31:23 +0100 Subject: [PATCH 05/11] Formatting mistake --- yt_dlp/YoutubeDL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index db173921f..8e9dbfeee 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1630,7 +1630,7 @@ def wrapper(self, *args, **kwargs): except ReExtractInfo as e: if wait_retries >= max_wait_retries: if max_wait_retries > 0: - self.report_error(f'Giving up after {wait_retries} {"retries" if wait_retries > 2 else "retry"} while waiting.') + self.report_error(f'Giving up after {wait_retries} {"retries" if wait_retries > 1 else "retry"} while waiting.') else: self.report_error('Video is still unavailable after waiting.') return From ad808c570be3f88ad5acf4a6b2f09c57f3db641a Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Fri, 27 Dec 2024 18:25:11 +0100 Subject: [PATCH 06/11] Route the retry count through extra_info. --- yt_dlp/YoutubeDL.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index d49833dcf..8c1a6cc2c 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1619,26 +1619,17 @@ def extract_info(self, url, download=True, ie_key=None, extra_info=None, def _handle_extraction_exceptions(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): - wait_retries = 0 - max_wait_retries = self.params.get('wait_retries') while True: try: return func(self, *args, **kwargs) except (CookieLoadError, DownloadCancelled, LazyList.IndexError, PagedList.IndexError): raise except ReExtractInfo as e: - if wait_retries >= max_wait_retries: - if max_wait_retries > 0: - self.report_error(f'Giving up after {wait_retries} {"retries" if wait_retries > 1 else "retry"} while waiting.') - else: - self.report_error('Video is still unavailable after waiting.') - return if e.expected: self.to_screen(f'{e}; Re-extracting data') else: self.to_stderr('\r') self.report_warning(f'{e}; Re-extracting data') - wait_retries += 1 continue except GeoRestrictedError as e: msg = e.msg @@ -1657,12 +1648,21 @@ def wrapper(self, *args, **kwargs): break return wrapper - def _wait_for_video(self, ie_result={}): + def _wait_for_video(self, ie_result={}, extra_info={}): 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 + max_wait_retries = self.params.get('wait_retries') + wait_retries = extra_info.get('__attempt') or 0 + if wait_retries > max_wait_retries: + if max_wait_retries > 0: + raise UserNotLive(f'[wait] Giving up after {wait_retries-1} {"retries" if wait_retries != 2 else "retry"} while waiting.') + else: + raise UserNotLive('[wait] Video is still unavailable after waiting.') + extra_info['__attempt'] = wait_retries + 1 + format_dur = lambda dur: '%02d:%02d:%02d' % timetuple_from_msec(dur * 1000)[:-1] last_msg = '' @@ -1770,7 +1770,7 @@ def __extract_info(self, url, ie, download, extra_info, process): if process: if self.params.get('wait_for_video'): self.report_warning(e) - self._wait_for_video() + self._wait_for_video(extra_info=extra_info) raise if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here) self.report_warning(f'Extractor {ie.IE_NAME} returned nothing{bug_reports_message()}') @@ -1785,7 +1785,7 @@ def __extract_info(self, url, ie, download, extra_info, process): ie_result.setdefault('original_url', extra_info['original_url']) self.add_default_extra_info(ie_result, ie, url) if process: - self._wait_for_video(ie_result) + self._wait_for_video(ie_result, extra_info=extra_info) return self.process_ie_result(ie_result, download, extra_info) else: return ie_result From de4e709220de5fb8c7da048e1f806d9a7c70b725 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Fri, 27 Dec 2024 18:33:59 +0100 Subject: [PATCH 07/11] Communicate needed wait time through an exception. Instead of waiting before throwing `ReExtractInfo`, `_wait_until` will now immediately throw a new exception `ReExtractInfoLater`. The `_handle_extraction_exceptions` decorator will then either bail out if the maximum retry count is exceeded, or wait until the specified waiting period expires. --- yt_dlp/YoutubeDL.py | 78 +++++++++++++++++++++++------------------- yt_dlp/utils/_utils.py | 9 +++++ 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 8c1a6cc2c..afd77765f 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -97,6 +97,7 @@ Popen, PostProcessingError, ReExtractInfo, + ReExtractInfoLater, RejectedVideoReached, SameFileError, UnavailableVideoError, @@ -1619,11 +1620,24 @@ def extract_info(self, url, download=True, ie_key=None, extra_info=None, def _handle_extraction_exceptions(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): + wait_retries = 0 + max_retries = self.params.get('wait_retries') while True: try: return func(self, *args, **kwargs) except (CookieLoadError, DownloadCancelled, LazyList.IndexError, PagedList.IndexError): 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: if e.expected: self.to_screen(f'{e}; Re-extracting data') @@ -1648,24 +1662,9 @@ def wrapper(self, *args, **kwargs): break return wrapper - def _wait_for_video(self, ie_result={}, extra_info={}): - 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 - - max_wait_retries = self.params.get('wait_retries') - wait_retries = extra_info.get('__attempt') or 0 - if wait_retries > max_wait_retries: - if max_wait_retries > 0: - raise UserNotLive(f'[wait] Giving up after {wait_retries-1} {"retries" if wait_retries != 2 else "retry"} while waiting.') - else: - raise UserNotLive('[wait] Video is still unavailable after waiting.') - extra_info['__attempt'] = wait_retries + 1 - + def _wait_until(self, till): format_dur = lambda dur: '%02d:%02d:%02d' % timetuple_from_msec(dur * 1000)[:-1] last_msg = '' - def progress(msg): nonlocal last_msg full_msg = f'{msg}\n' @@ -1676,6 +1675,30 @@ def progress(msg): self.to_screen(full_msg, skip_eol=True) last_msg = msg + 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 as e: + 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()) if diff is None and ie_result.get('live_status') == 'is_upcoming': @@ -1684,24 +1707,7 @@ def progress(msg): elif ie_result and (diff or 0) <= 0: 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')) - self.to_screen(f'[wait] Waiting for {format_dur(diff)} - Press Ctrl+C to try now') - - 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 + raise ReExtractInfoLater(time.time() + diff) def _load_cookies(self, data, *, autoscope=True): """Loads cookies from a `Cookie` header @@ -1770,7 +1776,7 @@ def __extract_info(self, url, ie, download, extra_info, process): if process: if self.params.get('wait_for_video'): self.report_warning(e) - self._wait_for_video(extra_info=extra_info) + self._wait_for_video() raise if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here) self.report_warning(f'Extractor {ie.IE_NAME} returned nothing{bug_reports_message()}') @@ -1785,7 +1791,7 @@ def __extract_info(self, url, ie, download, extra_info, process): ie_result.setdefault('original_url', extra_info['original_url']) self.add_default_extra_info(ie_result, ie, url) if process: - self._wait_for_video(ie_result, extra_info=extra_info) + self._wait_for_video(ie_result) return self.process_ie_result(ie_result, download, extra_info) else: return ie_result diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py index 699bf1e7f..486401f09 100644 --- a/yt_dlp/utils/_utils.py +++ b/yt_dlp/utils/_utils.py @@ -1110,6 +1110,15 @@ def __init__(self, msg, expected=False): 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): """ Download speed below --throttled-rate. """ msg = 'The download speed is below throttle limit' From 1bd564ddabd3a2183cddee8203a0fe3eedeec7da Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Sun, 29 Dec 2024 21:10:46 +0100 Subject: [PATCH 08/11] Integrate the retry count into the `wait-for-video` option. --- yt_dlp/YoutubeDL.py | 7 +++++-- yt_dlp/__init__.py | 46 ++++++++++++++++++++++++++++----------------- yt_dlp/options.py | 10 ++++------ 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index afd77765f..ed4fac7dc 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1621,7 +1621,10 @@ def _handle_extraction_exceptions(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): wait_retries = 0 - max_retries = self.params.get('wait_retries') + 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: try: return func(self, *args, **kwargs) @@ -1699,7 +1702,7 @@ def _wait_for_video(self, ie_result={}): or ie_result.get('formats') or ie_result.get('url')): return - min_wait, max_wait = self.params.get('wait_for_video') + min_wait, max_wait, _ = self.params.get('wait_for_video') diff = try_get(ie_result, lambda x: x['release_timestamp'] - time.time()) 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 --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index f0fe612dd..286f41f50 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -225,12 +225,36 @@ def validate_minmax(min_val, max_val, min_name, max_name=None): else: 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: + return int(value) + except (TypeError, ValueError): + validate(False, f'{name} retry count', 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: - min_wait, max_wait, *_ = map(parse_duration, [*opts.wait_for_video.split('-', 1), None]) - validate(min_wait is not None and not (max_wait is None and '-' in opts.wait_for_video), - 'time range to wait for video', opts.wait_for_video) - validate_minmax(min_wait, max_wait, 'time range to wait for video') - opts.wait_for_video = (min_wait, max_wait) + min_wait, max_wait, wait_retries = parse_range_with_arg( + 'time range to wait for video', 'waiting', opts.wait_for_video) + validate_positive('waiting retry count', wait_retries) + opts.wait_for_video = (min_wait, max_wait, wait_retries) # Format sort for f in opts.format_sort: @@ -255,19 +279,8 @@ 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)) # Retries - def parse_retries(name, value): - if value is None: - return None - elif value in ('inf', 'infinite'): - return float('inf') - try: - return int(value) - except (TypeError, ValueError): - validate(False, f'{name} retry count', value) - opts.retries = parse_retries('download', opts.retries) opts.fragment_retries = parse_retries('fragment', opts.fragment_retries) - opts.wait_retries = parse_retries('waiting', opts.wait_retries) opts.extractor_retries = parse_retries('extractor', opts.extractor_retries) opts.file_access_retries = parse_retries('file access', opts.file_access_retries) @@ -928,7 +941,6 @@ def parse_options(argv=None): 'extract_flat': opts.extract_flat, 'live_from_start': opts.live_from_start, 'wait_for_video': opts.wait_for_video, - 'wait_retries': opts.wait_retries, 'mark_watched': opts.mark_watched, 'merge_output_format': opts.merge_output_format, 'final_ext': final_ext, diff --git a/yt_dlp/options.py b/yt_dlp/options.py index cd1a273f2..c05910a58 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -436,18 +436,16 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs): help='Download livestreams from the current time (default)') general.add_option( '--wait-for-video', - dest='wait_for_video', metavar='MIN[-MAX]', default=None, + dest='wait_for_video', metavar='MIN[-MAX][:RETRIES]', default=None, help=( '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( '--no-wait-for-video', dest='wait_for_video', action='store_const', const=None, help='Do not wait for scheduled streams (default)') - general.add_option( - '--wait-retries', - dest='wait_retries', metavar='RETRIES', default='infinite', - help='Number of retries while waiting for scheduled streams to become available (default is %default). --wait-for-video must also be set') general.add_option( '--mark-watched', action='store_true', dest='mark_watched', default=False, From a23be8bba28c756ad395d3ce035e66739958ff48 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Mon, 30 Dec 2024 16:39:23 +0100 Subject: [PATCH 09/11] Add generated README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ab6123919..062ea6608 100644 --- a/README.md +++ b/README.md @@ -352,14 +352,14 @@ ## General Options: (Experimental) --no-live-from-start Download livestreams from the current time (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 - 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) - --wait-retries RETRIES Number of retries while waiting for - scheduled streams to become available - (default is infinite). --wait-for-video must - also be set --mark-watched Mark videos watched (even with --simulate) --no-mark-watched Do not mark videos watched (default) --color [STREAM:]POLICY Whether to emit color codes in output, From 82a4c500e7a2509ec8dbef77b76bbf8a9120c6d1 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Tue, 7 Jan 2025 14:11:10 +0100 Subject: [PATCH 10/11] Clean up unused exception binding. --- yt_dlp/YoutubeDL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index ed4fac7dc..ab58c1501 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1692,7 +1692,7 @@ def progress(msg): except KeyboardInterrupt: progress('') self.to_screen('[wait] Interrupted by user') - except BaseException as e: + except BaseException: self.to_screen('') raise From e23560b490955658b8455ef2088a45943c7ba734 Mon Sep 17 00:00:00 2001 From: Paul Storkman Date: Sun, 12 Jan 2025 08:26:31 +0100 Subject: [PATCH 11/11] Whitespace according to autopep8 --- yt_dlp/YoutubeDL.py | 3 ++- yt_dlp/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index ea14893ac..bdbff6742 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1643,7 +1643,7 @@ def wrapper(self, *args, **kwargs): 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.') + 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 @@ -1678,6 +1678,7 @@ def wrapper(self, *args, **kwargs): def _wait_until(self, till): format_dur = lambda dur: '%02d:%02d:%02d' % timetuple_from_msec(dur * 1000)[:-1] last_msg = '' + def progress(msg): nonlocal last_msg full_msg = f'{msg}\n' diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index bbe95473c..bb761a9ae 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -238,7 +238,7 @@ def parse_retries(name, value): return int_value def parse_range_with_arg(name, arg_name, value, - parse_limits=parse_duration, parse_arg=parse_retries): + parse_limits=parse_duration, parse_arg=parse_retries): # syntax: MIN[-MAX][:N] m = re.fullmatch(r'([^-:]+)(-[^:]+)?(:.+)?', value) validate(m, name, value) @@ -254,7 +254,7 @@ def parse_range_with_arg(name, arg_name, value, if opts.wait_for_video is not None: min_wait, max_wait, wait_retries = parse_range_with_arg( - 'time range to wait for video', 'waiting', opts.wait_for_video) + 'time range to wait for video', 'waiting', opts.wait_for_video) opts.wait_for_video = (min_wait, max_wait, wait_retries) # Format sort