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,