+ validate_regex('video remux format', opts.remuxvideo, FFmpegVideoRemuxerPP.FORMAT_RE)
+ if opts.audioquality:
+ opts.audioquality = opts.audioquality.strip('k').strip('K')
+ # int_or_none prevents inf, nan
+ 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.extractor_retries = parse_retries('extractor', opts.extractor_retries)
+ opts.file_access_retries = parse_retries('file access', opts.file_access_retries)
+
+ # Bytes
+ def parse_bytes(name, value):
+ if value is None:
+ return None
+ numeric_limit = FileDownloader.parse_bytes(value)
+ validate(numeric_limit is not None, 'rate limit', value)
+ return numeric_limit
+
+ opts.ratelimit = parse_bytes('rate limit', opts.ratelimit)
+ opts.throttledratelimit = parse_bytes('throttled rate limit', opts.throttledratelimit)
+ opts.min_filesize = parse_bytes('min filesize', opts.min_filesize)
+ opts.max_filesize = parse_bytes('max filesize', opts.max_filesize)
+ opts.buffersize = parse_bytes('buffer size', opts.buffersize)
+ opts.http_chunk_size = parse_bytes('http chunk size', opts.http_chunk_size)
+
+ # Output templates
+ def validate_outtmpl(tmpl, msg):
+ err = YoutubeDL.validate_outtmpl(tmpl)
+ if err:
+ raise ValueError(f'invalid {msg} "{tmpl}": {err}')
+
+ for k, tmpl in opts.outtmpl.items():
+ validate_outtmpl(tmpl, f'{k} output template')
+ for type_, tmpl_list in opts.forceprint.items():
+ for tmpl in tmpl_list:
+ validate_outtmpl(tmpl, f'{type_} print template')
+ for type_, tmpl_list in opts.print_to_file.items():
+ for tmpl, file in tmpl_list:
+ validate_outtmpl(tmpl, f'{type_} print to file template')
+ validate_outtmpl(file, f'{type_} print to file filename')
+ validate_outtmpl(opts.sponsorblock_chapter_title, 'SponsorBlock chapter title')
+ for k, tmpl in opts.progress_template.items():
+ k = f'{k[:-6]} console title' if '-title' in k else f'{k} progress'
+ validate_outtmpl(tmpl, f'{k} template')
+
+ outtmpl_default = opts.outtmpl.get('default')
+ if outtmpl_default == '':
+ opts.skip_download = None
+ del opts.outtmpl['default']
+ if outtmpl_default and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio:
+ raise ValueError(
+ 'Cannot download a video and extract audio into the same file! '
+ f'Use "{outtmpl_default}.%(ext)s" instead of "{outtmpl_default}" as the output template')
+
+ # Remove chapters
+ remove_chapters_patterns, opts.remove_ranges = [], []
+ for regex in opts.remove_chapters or []:
+ if regex.startswith('*'):
+ dur = list(map(parse_duration, regex[1:].split('-')))
+ if len(dur) == 2 and all(t is not None for t in dur):
+ opts.remove_ranges.append(tuple(dur))
+ continue
+ raise ValueError(f'invalid --remove-chapters time range "{regex}". Must be of the form *start-end')
+ try:
+ remove_chapters_patterns.append(re.compile(regex))
+ except re.error as err:
+ raise ValueError(f'invalid --remove-chapters regex "{regex}" - {err}')
+ opts.remove_chapters = remove_chapters_patterns
+
+ # Cookies from browser
+ if opts.cookiesfrombrowser:
+ mobj = re.match(r'(?P<name>[^+:]+)(\s*\+\s*(?P<keyring>[^:]+))?(\s*:(?P<profile>.+))?', opts.cookiesfrombrowser)
+ if mobj is None:
+ raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
+ browser_name, keyring, profile = mobj.group('name', 'keyring', 'profile')
+ browser_name = browser_name.lower()
+ if browser_name not in SUPPORTED_BROWSERS:
+ raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
+ f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}')
+ if keyring is not None:
+ keyring = keyring.upper()
+ if keyring not in SUPPORTED_KEYRINGS:
+ raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
+ f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
+ opts.cookiesfrombrowser = (browser_name, profile, keyring)
+
+ # MetadataParser
+ def metadataparser_actions(f):
+ if isinstance(f, str):
+ cmd = '--parse-metadata %s' % compat_shlex_quote(f)
+ try:
+ actions = [MetadataFromFieldPP.to_action(f)]
+ except Exception as err:
+ raise ValueError(f'{cmd} is invalid; {err}')
+ else:
+ cmd = '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote, f))
+ actions = ((MetadataParserPP.Actions.REPLACE, x, *f[1:]) for x in f[0].split(','))
+
+ for action in actions:
+ try:
+ MetadataParserPP.validate_action(*action)
+ except Exception as err:
+ raise ValueError(f'{cmd} is invalid; {err}')
+ yield action
+
+ parse_metadata = opts.parse_metadata or []
+ if opts.metafromtitle is not None:
+ parse_metadata.append('title:%s' % opts.metafromtitle)
+ opts.parse_metadata = list(itertools.chain(*map(metadataparser_actions, parse_metadata)))
+
+ # Other options
+ geo_bypass_code = opts.geo_bypass_ip_block or opts.geo_bypass_country
+ if geo_bypass_code is not None:
+ try:
+ GeoUtils.random_ipv4(geo_bypass_code)
+ except Exception:
+ raise ValueError('unsupported geo-bypass country or ip-block')
+
+ opts.match_filter = match_filter_func(opts.match_filter)
+
+ if opts.download_archive is not None:
+ opts.download_archive = expand_path(opts.download_archive)
+
+ if opts.user_agent is not None:
+ opts.headers.setdefault('User-Agent', opts.user_agent)
+ if opts.referer is not None:
+ opts.headers.setdefault('Referer', opts.referer)
+
+ if opts.no_sponsorblock:
+ opts.sponsorblock_mark = opts.sponsorblock_remove = set()
+
+ warnings, deprecation_warnings = [], []
+
+ # Common mistake: -f best
+ if opts.format == 'best':
+ warnings.append('.\n '.join((
+ '"-f best" selects the best pre-merged format which is often not the best option',
+ 'To let yt-dlp download and merge the best available formats, simply do not pass any format selection',
+ 'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning')))
+
+ # --(post-processor/downloader)-args without name
+ def report_args_compat(name, value, key1, key2=None):
+ if key1 in value and key2 not in value:
+ warnings.append(f'{name} arguments given without specifying name. The arguments will be given to all {name}s')
+ return True
+ return False
+
+ report_args_compat('external downloader', opts.external_downloader_args, 'default')
+ if report_args_compat('post-processor', opts.postprocessor_args, 'default-compat', 'default'):
+ opts.postprocessor_args['default'] = opts.postprocessor_args.pop('default-compat')
+ opts.postprocessor_args.setdefault('sponskrub', [])
+
+ def report_conflict(arg1, opt1, arg2='--allow-unplayable-formats', opt2='allow_unplayable_formats',
+ val1=NO_DEFAULT, val2=NO_DEFAULT, default=False):
+ if val2 is NO_DEFAULT:
+ val2 = getattr(opts, opt2)
+ if not val2:
+ return
+
+ if val1 is NO_DEFAULT:
+ val1 = getattr(opts, opt1)
+ if val1:
+ warnings.append(f'{arg1} is ignored since {arg2} was given')
+ setattr(opts, opt1, default)
+
+ # Conflicting options
+ report_conflict('--dateafter', 'dateafter', '--date', 'date', default=None)
+ report_conflict('--datebefore', 'datebefore', '--date', 'date', default=None)
+ report_conflict('--exec-before-download', 'exec_before_dl_cmd', '"--exec before_dl:"', 'exec_cmd', opts.exec_cmd.get('before_dl'))
+ report_conflict('--id', 'useid', '--output', 'outtmpl', val2=opts.outtmpl.get('default'))
+ report_conflict('--remux-video', 'remuxvideo', '--recode-video', 'recodevideo')
+ report_conflict('--sponskrub', 'sponskrub', '--remove-chapters', 'remove_chapters')
+ report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-mark', 'sponsorblock_mark')
+ report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-remove', 'sponsorblock_remove')
+ report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters', val1=opts.sponskrub and opts.sponskrub_cut)
+
+ # Conflicts with --allow-unplayable-formats
+ report_conflict('--add-metadata', 'addmetadata')
+ report_conflict('--embed-chapters', 'addchapters')
+ report_conflict('--embed-info-json', 'embed_infojson')
+ report_conflict('--embed-subs', 'embedsubtitles')
+ report_conflict('--embed-thumbnail', 'embedthumbnail')
+ report_conflict('--extract-audio', 'extractaudio')
+ report_conflict('--fixup', 'fixup', val1=(opts.fixup or '').lower() in ('', 'never', 'ignore'), default='never')
+ report_conflict('--recode-video', 'recodevideo')
+ report_conflict('--remove-chapters', 'remove_chapters', default=[])
+ report_conflict('--remux-video', 'remuxvideo')
+ report_conflict('--sponskrub', 'sponskrub')
+ report_conflict('--sponsorblock-remove', 'sponsorblock_remove', default=set())
+ report_conflict('--xattrs', 'xattrs')
+
+ # Fully deprecated options
+ def report_deprecation(val, old, new=None):
+ if not val:
+ return
+ deprecation_warnings.append(
+ f'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
+ else f'{old} is deprecated and may not work as expected')
+
+ report_deprecation(opts.sponskrub, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
+ report_deprecation(not opts.prefer_ffmpeg, '--prefer-avconv', 'ffmpeg')
+ # report_deprecation(opts.include_ads, '--include-ads') # We may re-implement this in future
+ # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
+ # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
+
+ # Dependent options
+ opts.date = DateRange.day(opts.date) if opts.date else DateRange(opts.dateafter, opts.datebefore)
+
+ if opts.exec_before_dl_cmd:
+ opts.exec_cmd['before_dl'] = opts.exec_before_dl_cmd
+
+ if opts.useid: # --id is not deprecated in youtube-dl
+ opts.outtmpl['default'] = '%(id)s.%(ext)s'
+
+ if opts.overwrites: # --force-overwrites implies --no-continue
+ opts.continue_dl = False
+
+ if (opts.addmetadata or opts.sponsorblock_mark) and opts.addchapters is None:
+ # Add chapters when adding metadata or marking sponsors
+ opts.addchapters = True