X-Git-Url: https://jfr.im/git/yt-dlp.git/blobdiff_plain/dd594deb2a0449dd8b145ef0552235f66ee3d454..adbc4ec4bbfbe57842049cf9194384480f534859:/yt_dlp/options.py diff --git a/yt_dlp/options.py b/yt_dlp/options.py index 505160cec..e3d753adf 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -17,6 +17,7 @@ get_executable_path, OUTTMPL_TYPES, preferredencoding, + remove_end, write_string, ) from .cookies import SUPPORTED_BROWSERS @@ -116,19 +117,19 @@ def _format_option_string(option): return ''.join(opts) - def _list_from_options_callback(option, opt_str, value, parser, append=True, delim=','): + def _list_from_options_callback(option, opt_str, value, parser, append=True, delim=',', process=str.strip): # append can be True, False or -1 (prepend) current = getattr(parser.values, option.dest) if append else [] - value = [value] if delim is None else value.split(delim) + value = list(filter(None, [process(value)] if delim is None else map(process, value.split(delim)))) setattr( parser.values, option.dest, current + value if append is True else value + current) def _set_from_options_callback( - option, opt_str, value, parser, - delim=',', allowed_values=None, process=str.lower, aliases={}): + option, opt_str, value, parser, delim=',', allowed_values=None, aliases={}, + process=lambda x: x.lower().strip()): current = getattr(parser.values, option.dest) - values = [process(value)] if delim is None else process(value).split(delim)[::-1] + values = [process(value)] if delim is None else list(map(process, value.split(delim)[::-1])) while values: actual_val = val = values.pop() if val == 'all': @@ -150,25 +151,25 @@ def _set_from_options_callback( def _dict_from_options_callback( option, opt_str, value, parser, - allowed_keys=r'[\w-]+', delimiter=':', default_key=None, process=None, multiple_keys=True): + allowed_keys=r'[\w-]+', delimiter=':', default_key=None, process=None, multiple_keys=True, + process_key=str.lower): out_dict = getattr(parser.values, option.dest) if multiple_keys: allowed_keys = r'(%s)(,(%s))*' % (allowed_keys, allowed_keys) mobj = re.match(r'(?i)(?P%s)%s(?P.*)$' % (allowed_keys, delimiter), value) if mobj is not None: - keys = [k.strip() for k in mobj.group('keys').lower().split(',')] - val = mobj.group('val') + keys, val = mobj.group('keys').split(','), mobj.group('val') elif default_key is not None: keys, val = [default_key], value else: raise optparse.OptionValueError( 'wrong %s formatting; it should be %s, not "%s"' % (opt_str, option.metavar, value)) try: + keys = map(process_key, keys) if process_key else keys val = process(val) if process else val except Exception as err: - raise optparse.OptionValueError( - 'wrong %s formatting; %s' % (opt_str, err)) + raise optparse.OptionValueError(f'wrong {opt_str} formatting; {err}') for key in keys: out_dict[key] = val @@ -206,9 +207,13 @@ def _dict_from_options_callback( action='store_true', dest='update_self', help='Update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)') general.add_option( - '-i', '--ignore-errors', '--no-abort-on-error', - action='store_true', dest='ignoreerrors', default=None, - help='Continue on download errors, for example to skip unavailable videos in a playlist (default) (Alias: --no-abort-on-error)') + '-i', '--ignore-errors', + action='store_true', dest='ignoreerrors', + help='Ignore download and postprocessing errors. The download will be considered successful even if the postprocessing fails') + general.add_option( + '--no-abort-on-error', + action='store_const', dest='ignoreerrors', const='only_download', + help='Continue with next video on download errors; e.g. to skip unavailable videos in a playlist (default)') general.add_option( '--abort-on-error', '--no-ignore-errors', action='store_false', dest='ignoreerrors', @@ -235,7 +240,7 @@ def _dict_from_options_callback( help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for the search term "large apple". Use the value "auto" to let yt-dlp guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching') general.add_option( '--ignore-config', '--no-config', - action='store_true', + action='store_true', dest='ignoreconfig', help=( 'Disable loading any configuration files except the one provided by --config-location. ' 'When given inside a configuration file, no further configuration files are loaded. ' @@ -253,10 +258,28 @@ def _dict_from_options_callback( '--no-flat-playlist', action='store_false', dest='extract_flat', help='Extract the videos of a playlist') + general.add_option( + '--live-from-start', + action='store_true', dest='live_from_start', + help='Download livestreams from the start. Currently only supported for YouTube') + general.add_option( + '--no-live-from-start', + action='store_false', dest='live_from_start', + help='Download livestreams from the current time (default)') + general.add_option( + '--wait-for-video', + dest='wait_for_video', metavar='MIN[-MAX]', default=None, + help=( + 'Wait for scheduled streams to become available. ' + 'Pass the minimum number of seconds (or range) to wait between retries')) + 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( '--mark-watched', action='store_true', dest='mark_watched', default=False, - help='Mark videos watched (YouTube only)') + help='Mark videos watched (even with --simulate). Currently only supported for YouTube') general.add_option( '--no-mark-watched', action='store_false', dest='mark_watched', @@ -273,10 +296,9 @@ def _dict_from_options_callback( 'allowed_values': { 'filename', 'format-sort', 'abort-on-error', 'format-spec', 'no-playlist-metafiles', 'multistreams', 'no-live-chat', 'playlist-index', 'list-formats', 'no-direct-merge', - 'no-youtube-channel-redirect', 'no-youtube-unavailable-videos', 'no-attach-info-json', + 'no-youtube-channel-redirect', 'no-youtube-unavailable-videos', 'no-attach-info-json', 'embed-metadata', 'embed-thumbnail-atomicparsley', 'seperate-video-versions', 'no-clean-infojson', 'no-keep-subs', - }, - 'aliases': { + }, 'aliases': { 'youtube-dl': ['-multistreams', 'all'], 'youtube-dlc': ['-no-youtube-channel-redirect', '-no-live-chat', 'all'], } @@ -363,10 +385,6 @@ def _dict_from_options_callback( '--reject-title', dest='rejecttitle', metavar='REGEX', help=optparse.SUPPRESS_HELP) - selection.add_option( - '--max-downloads', - dest='max_downloads', metavar='NUMBER', type=int, default=None, - help='Abort after downloading NUMBER files') selection.add_option( '--min-filesize', metavar='SIZE', dest='min_filesize', default=None, @@ -379,7 +397,7 @@ def _dict_from_options_callback( '--date', metavar='DATE', dest='date', default=None, help=( - 'Download only videos uploaded in this date. ' + 'Download only videos uploaded on this date. ' 'The date can be "YYYYMMDD" or in the format ' '"(now|today)[+-][0-9](day|week|month|year)(s)?"')) selection.add_option( @@ -437,6 +455,14 @@ def _dict_from_options_callback( '--download-archive', metavar='FILE', dest='download_archive', help='Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it') + selection.add_option( + '--no-download-archive', + dest='download_archive', action="store_const", const=None, + help='Do not use archive file (default)') + selection.add_option( + '--max-downloads', + dest='max_downloads', metavar='NUMBER', type=int, default=None, + help='Abort after downloading NUMBER files') selection.add_option( '--break-on-existing', action='store_true', dest='break_on_existing', default=False, @@ -445,14 +471,18 @@ def _dict_from_options_callback( '--break-on-reject', action='store_true', dest='break_on_reject', default=False, help='Stop the download process when encountering a file that has been filtered out') + selection.add_option( + '--break-per-input', + action='store_true', dest='break_per_url', default=False, + help='Make --break-on-existing and --break-on-reject act only on the current input URL') + selection.add_option( + '--no-break-per-input', + action='store_false', dest='break_per_url', + help='--break-on-existing and --break-on-reject terminates the entire download queue') selection.add_option( '--skip-playlist-after-errors', metavar='N', dest='skip_playlist_after_errors', default=None, type=int, help='Number of allowed failures until the rest of the playlist is skipped') - selection.add_option( - '--no-download-archive', - dest='download_archive', action="store_const", const=None, - help='Do not use archive file (default)') selection.add_option( '--include-ads', dest='include_ads', action='store_true', @@ -479,6 +509,10 @@ def _dict_from_options_callback( '-n', '--netrc', action='store_true', dest='usenetrc', default=False, help='Use .netrc authentication data') + authentication.add_option( + '--netrc-location', + dest='netrc_location', metavar='PATH', + help='Location of .netrc authentication data; either the path or its containing directory. Defaults to ~/.netrc') authentication.add_option( '--video-password', dest='videopassword', metavar='PASSWORD', @@ -554,12 +588,16 @@ def _dict_from_options_callback( help="Don't give any special preference to free containers (default)") video_format.add_option( '--check-formats', - action='store_true', dest='check_formats', default=None, - help='Check that the formats selected are actually downloadable') + action='store_const', const='selected', dest='check_formats', default=None, + help='Check that the selected formats are actually downloadable') + video_format.add_option( + '--check-all-formats', + action='store_true', dest='check_formats', + help='Check all formats for whether they are actually downloadable') video_format.add_option( '--no-check-formats', action='store_false', dest='check_formats', - help='Do not check that the formats selected are actually downloadable') + help='Do not check that the formats are actually downloadable') video_format.add_option( '-F', '--list-formats', action='store_true', dest='listformats', @@ -622,7 +660,7 @@ def _dict_from_options_callback( action='callback', dest='subtitleslangs', metavar='LANGS', type='str', default=[], callback=_list_from_options_callback, help=( - 'Languages of the subtitles to download (can be regex) or "all" separated by commas. (Eg: --sub-langs en.*,ja) ' + 'Languages of the subtitles to download (can be regex) or "all" separated by commas. (Eg: --sub-langs "en.*,ja") ' 'You can prefix the language code with a "-" to exempt it from the requested languages. (Eg: --sub-langs all,-live_chat) ' 'Use --list-subs for a list of available language tags')) @@ -760,7 +798,7 @@ def _dict_from_options_callback( dest='encoding', metavar='ENCODING', help='Force the specified encoding (experimental)') workarounds.add_option( - '--no-check-certificate', + '--no-check-certificates', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation') workarounds.add_option( @@ -780,7 +818,7 @@ def _dict_from_options_callback( '--add-header', metavar='FIELD:VALUE', dest='headers', default={}, type='str', action='callback', callback=_dict_from_options_callback, - callback_kwargs={'multiple_keys': False}, + callback_kwargs={'multiple_keys': False, 'process_key': None}, help='Specify a custom HTTP header and its value, separated by a colon ":". You can use this option multiple times', ) workarounds.add_option( @@ -828,7 +866,7 @@ def _dict_from_options_callback( '--ignore-no-formats-error', action='store_true', dest='ignore_no_formats_error', default=False, help=( - 'Ignore "No video formats" error. Usefull for extracting metadata ' + 'Ignore "No video formats" error. Useful for extracting metadata ' 'even if the videos are not actually available for download (experimental)')) verbosity.add_option( '--no-ignore-no-formats-error', @@ -902,12 +940,30 @@ def _dict_from_options_callback( help='Output progress bar as new lines') verbosity.add_option( '--no-progress', - action='store_true', dest='noprogress', default=False, + action='store_true', dest='noprogress', default=None, help='Do not print progress bar') + verbosity.add_option( + '--progress', + action='store_false', dest='noprogress', + help='Show progress bar, even if in quiet mode') verbosity.add_option( '--console-title', action='store_true', dest='consoletitle', default=False, help='Display progress in console titlebar') + verbosity.add_option( + '--progress-template', + metavar='[TYPES:]TEMPLATE', dest='progress_template', default={}, type='str', + action='callback', callback=_dict_from_options_callback, + callback_kwargs={ + 'allowed_keys': '(download|postprocess)(-title)?', + 'default_key': 'download' + }, help=( + 'Template for progress outputs, optionally prefixed with one of "download:" (default), ' + '"download-title:" (the console title), "postprocess:", or "postprocess-title:". ' + 'The video\'s fields are accessible under the "info" key and ' + 'the progress attributes are accessible under "progress" key. E.g.: ' + # TODO: Document the fields inside "progress" + '--console-title --progress-template "download-title:%(info.id)s-%(progress.eta)s"')) verbosity.add_option( '-v', '--verbose', action='store_true', dest='verbose', default=False, @@ -943,8 +999,13 @@ def _dict_from_options_callback( filesystem.add_option( '-a', '--batch-file', dest='batchfile', metavar='FILE', - help="File containing URLs to download ('-' for stdin), one URL per line. " - "Lines starting with '#', ';' or ']' are considered as comments and ignored") + help=( + 'File containing URLs to download ("-" for stdin), one URL per line. ' + 'Lines starting with "#", ";" or "]" are considered as comments and ignored')) + filesystem.add_option( + '--no-batch-file', + dest='batchfile', action='store_const', const=None, + help='Do not read URLs from batch file (default)') filesystem.add_option( '--id', default=False, action='store_true', dest='useid', help=optparse.SUPPRESS_HELP) @@ -994,27 +1055,15 @@ def _dict_from_options_callback( filesystem.add_option( '--windows-filenames', action='store_true', dest='windowsfilenames', default=False, - help='Force filenames to be windows compatible') + help='Force filenames to be Windows-compatible') filesystem.add_option( '--no-windows-filenames', action='store_false', dest='windowsfilenames', - help='Make filenames windows compatible only if using windows (default)') + help='Make filenames Windows-compatible only if using Windows (default)') filesystem.add_option( '--trim-filenames', '--trim-file-names', metavar='LENGTH', dest='trim_file_name', default=0, type=int, help='Limit the filename length (excluding extension) to the specified number of characters') - filesystem.add_option( - '--auto-number', - action='store_true', dest='autonumber', default=False, - help=optparse.SUPPRESS_HELP) - filesystem.add_option( - '--title', - action='store_true', dest='usetitle', default=False, - help=optparse.SUPPRESS_HELP) - filesystem.add_option( - '--literal', default=False, - action='store_true', dest='usetitle', - help=optparse.SUPPRESS_HELP) filesystem.add_option( '-w', '--no-overwrites', action='store_false', dest='overwrites', default=None, @@ -1114,7 +1163,7 @@ def _dict_from_options_callback( filesystem.add_option( '--cookies', dest='cookiefile', metavar='FILE', - help='File to read cookies from and dump cookie jar in') + help='Netscape formatted file to read cookies from and dump cookie jar in') filesystem.add_option( '--no-cookies', action='store_const', const=None, dest='cookiefile', metavar='FILE', @@ -1128,7 +1177,7 @@ def _dict_from_options_callback( 'You can specify the user profile name or directory using ' '"BROWSER:PROFILE_NAME" or "BROWSER:PROFILE_PATH". ' 'If no profile is given, the most recently accessed one is used'.format( - '|'.join(sorted(SUPPORTED_BROWSERS))))) + ', '.join(sorted(SUPPORTED_BROWSERS))))) filesystem.add_option( '--no-cookies-from-browser', action='store_const', const=None, dest='cookiesfrombrowser', @@ -1147,7 +1196,10 @@ def _dict_from_options_callback( thumbnail = optparse.OptionGroup(parser, 'Thumbnail Options') thumbnail.add_option( '--write-thumbnail', - action='store_true', dest='writethumbnail', default=False, + action='callback', dest='writethumbnail', default=False, + # Should override --no-write-thumbnail, but not --write-all-thumbnail + callback=lambda option, _, __, parser: setattr( + parser.values, option.dest, getattr(parser.values, option.dest) or True), help='Write thumbnail image to disk') thumbnail.add_option( '--no-write-thumbnail', @@ -1155,7 +1207,7 @@ def _dict_from_options_callback( help='Do not write thumbnail image to disk (default)') thumbnail.add_option( '--write-all-thumbnails', - action='store_true', dest='write_all_thumbnails', default=False, + action='store_const', dest='writethumbnail', const='all', help='Write all thumbnail image formats to disk') thumbnail.add_option( '--list-thumbnails', @@ -1193,7 +1245,7 @@ def _dict_from_options_callback( postproc.add_option( '--audio-quality', metavar='QUALITY', dest='audioquality', default='5', - help='Specify ffmpeg audio quality, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default %default)') + help='Specify ffmpeg audio quality, insert a value between 0 (best) and 10 (worst) for VBR or a specific bitrate like 128K (default %default)') postproc.add_option( '--remux-video', metavar='FORMAT', dest='remuxvideo', default=None, @@ -1265,7 +1317,9 @@ def _dict_from_options_callback( postproc.add_option( '--embed-metadata', '--add-metadata', action='store_true', dest='addmetadata', default=False, - help='Embed metadata to the video file. Also adds chapters to file unless --no-add-chapters is used (Alias: --add-metadata)') + help=( + 'Embed metadata to the video file. Also embeds chapters/infojson if present ' + 'unless --no-embed-chapters/--no-embed-info-json are used (Alias: --add-metadata)')) postproc.add_option( '--no-embed-metadata', '--no-add-metadata', action='store_false', dest='addmetadata', @@ -1278,6 +1332,14 @@ def _dict_from_options_callback( '--no-embed-chapters', '--no-add-chapters', action='store_false', dest='addchapters', help='Do not add chapter markers (default) (Alias: --no-add-chapters)') + postproc.add_option( + '--embed-info-json', + action='store_true', dest='embed_infojson', default=None, + help='Embed the infojson as an attachment to mkv/mka video files') + postproc.add_option( + '--no-embed-info-json', + action='store_false', dest='embed_infojson', + help='Do not embed the infojson as an attachment to the video file') postproc.add_option( '--metadata-from-title', metavar='FORMAT', dest='metafromtitle', @@ -1304,7 +1366,7 @@ def _dict_from_options_callback( 'Automatically correct known faults of the file. ' 'One of never (do nothing), warn (only emit a warning), ' 'detect_or_warn (the default; fix file if we can, warn otherwise), ' - 'force (try fixing even if file already exists')) + 'force (try fixing even if file already exists)')) postproc.add_option( '--prefer-avconv', '--no-prefer-ffmpeg', action='store_false', dest='prefer_ffmpeg', @@ -1367,7 +1429,11 @@ def _dict_from_options_callback( postproc.add_option( '--remove-chapters', metavar='REGEX', dest='remove_chapters', action='append', - help='Remove chapters whose title matches the given regular expression. This option can be used multiple times') + help=( + 'Remove chapters whose title matches the given regular expression. ' + 'Time ranges prefixed by a "*" can also be used in place of chapters to remove the specified range. ' + 'Eg: --remove-chapters "*10:15-15:00" --remove-chapters "intro". ' + 'This option can be used multiple times')) postproc.add_option( '--no-remove-chapters', dest='remove_chapters', action='store_const', const=None, help='Do not remove any chapters from the file (default)') @@ -1376,12 +1442,31 @@ def _dict_from_options_callback( action='store_true', dest='force_keyframes_at_cuts', default=False, help=( 'Force keyframes around the chapters before removing/splitting them. ' - 'Requires a reencode and thus is very slow, but the resulting video ' + 'Requires a re-encode and thus is very slow, but the resulting video ' 'may have fewer artifacts around the cuts')) postproc.add_option( '--no-force-keyframes-at-cuts', action='store_false', dest='force_keyframes_at_cuts', help='Do not force keyframes around the chapters when cutting/splitting (default)') + _postprocessor_opts_parser = lambda key, val='': ( + *(item.split('=', 1) for item in (val.split(';') if val else [])), + ('key', remove_end(key, 'PP'))) + postproc.add_option( + '--use-postprocessor', + metavar='NAME[:ARGS]', dest='add_postprocessors', default=[], type='str', + action='callback', callback=_list_from_options_callback, + callback_kwargs={ + 'delim': None, + 'process': lambda val: dict(_postprocessor_opts_parser(*val.split(':', 1))) + }, help=( + 'The (case sensitive) name of plugin postprocessors to be enabled, ' + 'and (optionally) arguments to be passed to it, separated by a colon ":". ' + 'ARGS are a semicolon ";" delimited list of NAME=VALUE. ' + 'The "when" argument determines when the postprocessor is invoked. ' + 'It can be one of "pre_process" (after extraction), ' + '"before_dl" (before video download), "post_process" (after video download; default) ' + 'or "after_move" (after moving file to their final locations). ' + 'This option can be used multiple times to add different postprocessors')) sponsorblock = optparse.OptionGroup(parser, 'SponsorBlock Options', description=( 'Make chapter entries for, or remove various segments (sponsor, introductions, etc.) ' @@ -1389,20 +1474,29 @@ def _dict_from_options_callback( sponsorblock.add_option( '--sponsorblock-mark', metavar='CATS', dest='sponsorblock_mark', default=set(), action='callback', type='str', - callback=_set_from_options_callback, callback_kwargs={'allowed_values': SponsorBlockPP.CATEGORIES.keys()}, - help=( + callback=_set_from_options_callback, callback_kwargs={ + 'allowed_values': SponsorBlockPP.CATEGORIES.keys(), + 'aliases': {'default': ['all']} + }, help=( 'SponsorBlock categories to create chapters for, separated by commas. ' - 'Available categories are all, %s. You can prefix the category with a "-" to exempt it. ' - 'See https://wiki.sponsor.ajay.app/index.php/Segment_Categories for description of the categories. ' - 'Eg: --sponsorblock-query all,-preview' % ', '.join(SponsorBlockPP.CATEGORIES.keys()))) + f'Available categories are all, default(=all), {", ".join(SponsorBlockPP.CATEGORIES.keys())}. ' + 'You can prefix the category with a "-" to exempt it. See [1] for description of the categories. ' + 'Eg: --sponsorblock-mark all,-preview [1] https://wiki.sponsor.ajay.app/w/Segment_Categories')) sponsorblock.add_option( '--sponsorblock-remove', metavar='CATS', dest='sponsorblock_remove', default=set(), action='callback', type='str', - callback=_set_from_options_callback, callback_kwargs={'allowed_values': SponsorBlockPP.CATEGORIES.keys()}, - help=( + callback=_set_from_options_callback, callback_kwargs={ + 'allowed_values': set(SponsorBlockPP.CATEGORIES.keys()) - set(SponsorBlockPP.POI_CATEGORIES.keys()), + # Note: From https://wiki.sponsor.ajay.app/w/Types: + # The filler category is very aggressive. + # It is strongly recommended to not use this in a client by default. + 'aliases': {'default': ['all', '-filler']} + }, help=( 'SponsorBlock categories to be removed from the video file, separated by commas. ' 'If a category is present in both mark and remove, remove takes precedence. ' - 'The syntax and available categories are the same as for --sponsorblock-mark')) + 'The syntax and available categories are the same as for --sponsorblock-mark ' + 'except that "default" refers to "all,-filler" ' + f'and {", ".join(SponsorBlockPP.POI_CATEGORIES.keys())} is not available')) sponsorblock.add_option( '--sponsorblock-chapter-title', metavar='TEMPLATE', default=DEFAULT_SPONSORBLOCK_CHAPTER_TITLE, dest='sponsorblock_chapter_title', @@ -1421,7 +1515,7 @@ def _dict_from_options_callback( sponsorblock.add_option( '--sponskrub', - action='store_true', dest='sponskrub', default=None, + action='store_true', dest='sponskrub', default=False, help=optparse.SUPPRESS_HELP) sponsorblock.add_option( '--no-sponskrub', @@ -1533,69 +1627,61 @@ def compat_conf(conf): 'command-line': compat_conf(sys.argv[1:]), 'custom': [], 'home': [], 'portable': [], 'user': [], 'system': []} paths = {'command-line': False} - opts, args = parser.parse_args(configs['command-line']) + + def read_options(name, path, user=False): + ''' loads config files and returns ignoreconfig ''' + # Multiple package names can be given here + # Eg: ('yt-dlp', 'youtube-dlc', 'youtube-dl') will look for + # the configuration file of any of these three packages + for package in ('yt-dlp',): + if user: + config, current_path = _readUserConf(package, default=None) + else: + current_path = os.path.join(path, '%s.conf' % package) + config = _readOptions(current_path, default=None) + if config is not None: + current_path = os.path.realpath(current_path) + if current_path in paths.values(): + return False + configs[name], paths[name] = config, current_path + return parser.parse_args(config)[0].ignoreconfig + return False def get_configs(): - if '--config-location' in configs['command-line']: + opts, _ = parser.parse_args(configs['command-line']) + if opts.config_location is not None: location = compat_expanduser(opts.config_location) if os.path.isdir(location): location = os.path.join(location, 'yt-dlp.conf') if not os.path.exists(location): parser.error('config-location %s does not exist.' % location) - configs['custom'] = _readOptions(location, default=None) - if configs['custom'] is None: - configs['custom'] = [] - else: - paths['custom'] = location - if '--ignore-config' in configs['command-line']: + config = _readOptions(location, default=None) + if config: + configs['custom'], paths['custom'] = config, location + + if opts.ignoreconfig: return - if '--ignore-config' in configs['custom']: + if parser.parse_args(configs['custom'])[0].ignoreconfig: return - - def read_options(path, user=False): - # Multiple package names can be given here - # Eg: ('yt-dlp', 'youtube-dlc', 'youtube-dl') will look for - # the configuration file of any of these three packages - for package in ('yt-dlp',): - if user: - config, current_path = _readUserConf(package, default=None) - else: - current_path = os.path.join(path, '%s.conf' % package) - config = _readOptions(current_path, default=None) - if config is not None: - return config, current_path - return [], None - - configs['portable'], paths['portable'] = read_options(get_executable_path()) - if '--ignore-config' in configs['portable']: + if read_options('portable', get_executable_path()): return - - def get_home_path(): - opts = parser.parse_args(configs['portable'] + configs['custom'] + configs['command-line'])[0] - return expand_path(opts.paths.get('home', '')).strip() - - configs['home'], paths['home'] = read_options(get_home_path()) - if '--ignore-config' in configs['home']: + opts, _ = parser.parse_args(configs['portable'] + configs['custom'] + configs['command-line']) + if read_options('home', expand_path(opts.paths.get('home', '')).strip()): return - - configs['system'], paths['system'] = read_options('/etc') - if '--ignore-config' in configs['system']: + if read_options('system', '/etc'): return - - configs['user'], paths['user'] = read_options('', True) - if '--ignore-config' in configs['user']: + if read_options('user', None, user=True): configs['system'], paths['system'] = [], None get_configs() argv = configs['system'] + configs['user'] + configs['home'] + configs['portable'] + configs['custom'] + configs['command-line'] opts, args = parser.parse_args(argv) if opts.verbose: - for label in ('System', 'User', 'Portable', 'Home', 'Custom', 'Command-line'): + for label in ('Command-line', 'Custom', 'Portable', 'Home', 'User', 'System'): key = label.lower() - if paths.get(key) is None: - continue - if paths[key]: - write_string('[debug] %s config file: %s\n' % (label, paths[key])) - write_string('[debug] %s config: %s\n' % (label, repr(_hide_login_info(configs[key])))) + if paths.get(key): + write_string(f'[debug] {label} config file: {paths[key]}\n') + if paths.get(key) is not None: + write_string(f'[debug] {label} config: {_hide_login_info(configs[key])!r}\n') return parser, opts, args