-from __future__ import unicode_literals
-
-import os.path
import optparse
+import os.path
import re
+import shlex
import sys
-from .compat import (
- compat_expanduser,
- compat_get_terminal_size,
- compat_getenv,
- compat_kwargs,
- compat_shlex_split,
-)
-from .utils import (
- Config,
- expand_path,
- get_executable_path,
- OUTTMPL_TYPES,
- POSTPROCESS_WHEN,
- remove_end,
- write_string,
-)
+from .compat import compat_expanduser, compat_get_terminal_size, compat_getenv
from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS
-from .version import __version__
-
from .downloader.external import list_external_downloaders
from .postprocessor import (
FFmpegExtractAudioPP,
SponsorBlockPP,
)
from .postprocessor.modify_chapters import DEFAULT_SPONSORBLOCK_CHAPTER_TITLE
+from .utils import (
+ OUTTMPL_TYPES,
+ POSTPROCESS_WHEN,
+ Config,
+ expand_path,
+ get_executable_path,
+ join_nonempty,
+ remove_end,
+ write_string,
+)
+from .version import __version__
def parseOpts(overrideArguments=None, ignore_config_files='if_override'):
return parser, opts, args
+class _YoutubeDLHelpFormatter(optparse.IndentedHelpFormatter):
+ def __init__(self):
+ # No need to wrap help messages if we're on a wide console
+ max_width = compat_get_terminal_size().columns or 80
+ # 47% is chosen because that is how README.md is currently formatted
+ # and moving help text even further to the right is undesirable.
+ # This can be reduced in the future to get a prettier output
+ super().__init__(width=max_width, max_help_position=int(0.47 * max_width))
+
+ @staticmethod
+ def format_option_strings(option):
+ """ ('-o', '--option') -> -o, --format METAVAR """
+ opts = join_nonempty(
+ option._short_opts and option._short_opts[0],
+ option._long_opts and option._long_opts[0],
+ delim=', ')
+ if option.takes_value():
+ opts += f' {option.metavar}'
+ return opts
+
+
class _YoutubeDLOptionParser(optparse.OptionParser):
# optparse is deprecated since python 3.2. So assume a stable interface even for private methods
+ def __init__(self):
+ super().__init__(
+ prog='yt-dlp',
+ version=__version__,
+ usage='%prog [OPTIONS] URL [URL...]',
+ epilog='See full documentation at https://github.com/yt-dlp/yt-dlp#readme',
+ formatter=_YoutubeDLHelpFormatter(),
+ conflict_handler='resolve',
+ )
+
+ def _get_args(self, args):
+ return sys.argv[1:] if args is None else list(args)
+
def _match_long_opt(self, opt):
"""Improve ambigious argument resolution by comparing option objects instead of argument strings"""
try:
return super()._match_long_opt(opt)
except optparse.AmbiguousOptionError as e:
- if len(set(self._long_opt[p] for p in e.possibilities)) == 1:
+ if len({self._long_opt[p] for p in e.possibilities}) == 1:
return e.possibilities[0]
raise
def create_parser():
- def _format_option_string(option):
- ''' ('-o', '--option') -> -o, --format METAVAR'''
-
- opts = []
-
- if option._short_opts:
- opts.append(option._short_opts[0])
- if option._long_opts:
- opts.append(option._long_opts[0])
- if len(opts) > 1:
- opts.insert(1, ', ')
-
- if option.takes_value():
- opts.append(' %s' % option.metavar)
-
- return ''.join(opts)
-
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 = list(getattr(parser.values, option.dest)) if append else []
values = [process(value)] if delim is None else list(map(process, value.split(delim)[::-1]))
while values:
actual_val = val = values.pop()
+ if not val:
+ raise optparse.OptionValueError(f'Invalid {option.metavar} for {opt_str}: {value}')
if val == 'all':
current.update(allowed_values)
elif val == '-all':
out_dict = dict(getattr(parser.values, option.dest))
multiple_args = not isinstance(value, str)
if multiple_keys:
- allowed_keys = r'(%s)(,(%s))*' % (allowed_keys, allowed_keys)
+ allowed_keys = fr'({allowed_keys})(,({allowed_keys}))*'
mobj = re.match(
- r'(?i)(?P<keys>%s)%s(?P<val>.*)$' % (allowed_keys, delimiter),
+ fr'(?i)(?P<keys>{allowed_keys}){delimiter}(?P<val>.*)$',
value[0] if multiple_args else value)
if mobj is not None:
keys, val = mobj.group('keys').split(','), mobj.group('val')
keys, val = [default_key], value
else:
raise optparse.OptionValueError(
- 'wrong %s formatting; it should be %s, not "%s"' % (opt_str, option.metavar, value))
+ f'wrong {opt_str} formatting; it should be {option.metavar}, not "{value}"')
try:
keys = map(process_key, keys) if process_key else keys
val = process(val) if process else val
out_dict[key] = out_dict.get(key, []) + [val] if append else val
setattr(parser.values, option.dest, out_dict)
- # No need to wrap help messages if we're on a wide console
- columns = compat_get_terminal_size().columns
- max_width = columns if columns else 80
- # 47% is chosen because that is how README.md is currently formatted
- # and moving help text even further to the right is undesirable.
- # This can be reduced in the future to get a prettier output
- max_help_position = int(0.47 * max_width)
-
- fmt = optparse.IndentedHelpFormatter(width=max_width, max_help_position=max_help_position)
- fmt.format_option_strings = _format_option_string
-
- kw = {
- 'version': __version__,
- 'formatter': fmt,
- 'usage': '%prog [OPTIONS] URL [URL...]',
- 'conflict_handler': 'resolve',
- }
-
- parser = _YoutubeDLOptionParser(**compat_kwargs(kw))
+ parser = _YoutubeDLOptionParser()
general = optparse.OptionGroup(parser, 'General Options')
general.add_option(
general.add_option(
'--mark-watched',
action='store_true', dest='mark_watched', default=False,
- help='Mark videos watched (even with --simulate). Currently only supported for YouTube')
+ help='Mark videos watched (even with --simulate)')
general.add_option(
'--no-mark-watched',
action='store_false', dest='mark_watched',
action='callback', callback=_set_from_options_callback,
callback_kwargs={
'allowed_values': {
- 'filename', 'format-sort', 'abort-on-error', 'format-spec', 'no-playlist-metafiles',
+ 'filename', 'filename-sanitization', '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', 'embed-metadata',
- 'embed-thumbnail-atomicparsley', 'seperate-video-versions', 'no-clean-infojson', 'no-keep-subs',
+ 'embed-thumbnail-atomicparsley', 'seperate-video-versions', 'no-clean-infojson', 'no-keep-subs', 'no-certifi',
}, 'aliases': {
'youtube-dl': ['-multistreams', 'all'],
'youtube-dlc': ['-no-youtube-channel-redirect', '-no-live-chat', 'all'],
metavar='COUNT', dest='max_views', default=None, type=int,
help=optparse.SUPPRESS_HELP)
selection.add_option(
- '--match-filter',
- metavar='FILTER', dest='match_filter', default=None,
+ '--match-filters',
+ metavar='FILTER', dest='match_filter', action='append',
help=(
'Generic video filter. Any field (see "OUTPUT TEMPLATE") can be compared with a '
'number or a string using the operators defined in "Filtering formats". '
- 'You can also simply specify a field to match if the field is present '
- 'and "!field" to check if the field is not present. In addition, '
- 'Python style regular expression matching can be done using "~=", '
- 'and multiple filters can be checked with "&". '
- 'Use a "\\" to escape "&" or quotes if needed. Eg: --match-filter '
- '"!is_live & like_count>?100 & description~=\'(?i)\\bcats \\& dogs\\b\'" '
- 'matches only videos that are not live, has a like count more than 100 '
- '(or the like field is not available), and also has a description '
- 'that contains the phrase "cats & dogs" (ignoring case)'))
+ 'You can also simply specify a field to match if the field is present, '
+ 'use "!field" to check if the field is not present, and "&" to check multiple conditions. '
+ 'Use a "\\" to escape "&" or quotes if needed. If used multiple times, '
+ 'the filter matches if atleast one of the conditions are met. Eg: --match-filter '
+ '!is_live --match-filter "like_count>?100 & description~=\'(?i)\\bcats \\& dogs\\b\'" '
+ 'matches only videos that are not live OR those that have a like count more than 100 '
+ '(or the like field is not available) and also has a description '
+ 'that contains the phrase "cats & dogs" (ignoring case). '
+ 'Use "--match-filter -" to interactively ask whether to download each video'))
selection.add_option(
'--no-match-filter',
metavar='FILTER', dest='match_filter', action='store_const', const=None,
video_format.add_option(
'--check-formats',
action='store_const', const='selected', dest='check_formats', default=None,
- help='Check that the selected formats are actually downloadable')
+ help='Make sure formats are selected only from those that are actually downloadable')
video_format.add_option(
'--check-all-formats',
action='store_true', dest='check_formats',
help='Number of retries (default is %default), or "infinite"')
downloader.add_option(
'--file-access-retries',
- dest='file_access_retries', metavar='RETRIES', default=10,
+ dest='file_access_retries', metavar='RETRIES', default=3,
help='Number of times to retry on file access error (default is %default), or "infinite"')
downloader.add_option(
'--fragment-retries',
}, help=(
'Name or path of the external downloader to use (optionally) prefixed by '
'the protocols (http, ftp, m3u8, dash, rstp, rtmp, mms) to use it for. '
- 'Currently supports native, %s (Recommended: aria2c). '
+ f'Currently supports native, {", ".join(list_external_downloaders())}. '
'You can use this option multiple times to set different downloaders for different protocols. '
'For example, --downloader aria2c --downloader "dash,m3u8:native" will use '
'aria2c for http/ftp downloads, and the native downloader for dash/m3u8 downloads '
- '(Alias: --external-downloader)' % ', '.join(list_external_downloaders())))
+ '(Alias: --external-downloader)'))
downloader.add_option(
'--downloader-args', '--external-downloader-args',
metavar='NAME:ARGS', dest='external_downloader_args', default={}, type='str',
callback_kwargs={
'allowed_keys': r'ffmpeg_[io]\d*|%s' % '|'.join(map(re.escape, list_external_downloaders())),
'default_key': 'default',
- 'process': compat_shlex_split
+ 'process': shlex.split
}, help=(
'Give these arguments to the external downloader. '
'Specify the downloader name and the arguments separated by a colon ":". '
workarounds.add_option(
'--user-agent',
metavar='UA', dest='user_agent',
- help='Specify a custom user agent')
+ help=optparse.SUPPRESS_HELP)
workarounds.add_option(
'--referer',
metavar='URL', dest='referer', default=None,
- help='Specify a custom referer, use if the video access is restricted to one domain',
- )
+ help=optparse.SUPPRESS_HELP)
workarounds.add_option(
'--add-header',
metavar='FIELD:VALUE', dest='headers', default={}, type='str',
action='callback', callback=_dict_from_options_callback,
- callback_kwargs={'multiple_keys': False, 'process_key': None},
+ callback_kwargs={'multiple_keys': False},
help='Specify a custom HTTP header and its value, separated by a colon ":". You can use this option multiple times',
)
workarounds.add_option(
}, help=(
'Field name or output template to print to screen, optionally prefixed with when to print it, separated by a ":". '
'Supported values of "WHEN" are the same as that of --use-postprocessor, and "video" (default). '
- 'Implies --quiet and --simulate (unless --no-simulate is used). This option can be used multiple times'))
+ 'Implies --quiet. Implies --simulate unless --no-simulate or later stages of WHEN are used. '
+ 'This option can be used multiple times'))
verbosity.add_option(
'--print-to-file',
metavar='[WHEN:]TEMPLATE FILE', dest='print_to_file', default={}, type='str', nargs=2,
verbosity.add_option(
'-C', '--call-home',
dest='call_home', action='store_true', default=False,
- # help='[Broken] Contact the yt-dlp server for debugging')
+ # help='Contact the yt-dlp server for debugging')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--no-call-home',
help='Do not write video description (default)')
filesystem.add_option(
'--write-info-json',
- action='store_true', dest='writeinfojson', default=False,
+ action='store_true', dest='writeinfojson', default=None,
help='Write video metadata to a .info.json file (this may contain personal information)')
filesystem.add_option(
'--no-write-info-json',
action='store_false', dest='allow_playlist_files',
help='Do not write playlist metadata when using --write-info-json, --write-description etc.')
filesystem.add_option(
- '--clean-infojson',
+ '--clean-info-json', '--clean-infojson',
action='store_true', dest='clean_infojson', default=None,
help=(
'Remove some private fields such as filenames from the infojson. '
'Note that it could still contain some personal information (default)'))
filesystem.add_option(
- '--no-clean-infojson',
+ '--no-clean-info-json', '--no-clean-infojson',
action='store_false', dest='clean_infojson',
help='Write all fields to the infojson')
filesystem.add_option(
'--audio-format', metavar='FORMAT', dest='audioformat', default='best',
help=(
'Specify audio format to convert the audio to when -x is used. Currently supported formats are: '
- 'best (default) or one of %s' % '|'.join(FFmpegExtractAudioPP.SUPPORTED_EXTS)))
+ 'best (default) or one of %s' % ', '.join(FFmpegExtractAudioPP.SUPPORTED_EXTS)))
postproc.add_option(
'--audio-quality', metavar='QUALITY',
dest='audioquality', default='5',
- help='Specify ffmpeg audio quality, insert a value between 0 (best) and 10 (worst) for VBR or a specific bitrate like 128K (default %default)')
+ help='Specify ffmpeg audio quality to use when converting the audio with -x. 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,
'Remux the video into another container if necessary (currently supported: %s). '
'If target container does not support the video/audio codec, remuxing will fail. '
'You can specify multiple rules; Eg. "aac>m4a/mov>mp4/mkv" will remux aac to m4a, mov to mp4 '
- 'and anything else to mkv.' % '|'.join(FFmpegVideoRemuxerPP.SUPPORTED_EXTS)))
+ 'and anything else to mkv.' % ', '.join(FFmpegVideoRemuxerPP.SUPPORTED_EXTS)))
postproc.add_option(
'--recode-video',
metavar='FORMAT', dest='recodevideo', default=None,
callback_kwargs={
'allowed_keys': r'\w+(?:\+\w+)?',
'default_key': 'default-compat',
- 'process': compat_shlex_split,
+ 'process': shlex.split,
'multiple_keys': False
}, help=(
'Give these arguments to the postprocessors. '
'"multi_video" (default; only when the videos form a single show). '
'All the video files must have same codecs and number of streams to be concatable. '
'The "pl_video:" prefix can be used with "--paths" and "--output" to '
- 'set the output filename for the split files. See "OUTPUT TEMPLATE" for details'))
+ 'set the output filename for the concatenated files. See "OUTPUT TEMPLATE" for details'))
postproc.add_option(
'--fixup',
metavar='POLICY', dest='fixup', default=None,
help=optparse.SUPPRESS_HELP)
postproc.add_option(
'--no-exec-before-download',
- action='store_const', dest='exec_before_dl_cmd', const=[],
+ action='store_const', dest='exec_before_dl_cmd', const=None,
help=optparse.SUPPRESS_HELP)
postproc.add_option(
'--convert-subs', '--convert-sub', '--convert-subtitles',
metavar='FORMAT', dest='convertsubtitles', default=None,
help=(
'Convert the subtitles to another format (currently supported: %s) '
- '(Alias: --convert-subtitles)' % '|'.join(FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)))
+ '(Alias: --convert-subtitles)' % ', '.join(FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)))
postproc.add_option(
'--convert-thumbnails',
metavar='FORMAT', dest='convertthumbnails', default=None,
help=(
'Convert the thumbnails to another format '
- '(currently supported: %s) ' % '|'.join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)))
+ '(currently supported: %s) ' % ', '.join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)))
postproc.add_option(
'--split-chapters', '--split-tracks',
dest='split_chapters', action='store_true', default=False,
'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), '
- '"after_move" (after moving file to their final locations), '
+ 'It can be one of "pre_process" (after video extraction), "after_filter" (after video passes filter), '
+ '"before_dl" (before each video download), "post_process" (after each video download; default), '
+ '"after_move" (after moving video file to it\'s final locations), '
'"after_video" (after downloading and processing all formats of a video), '
- 'or "playlist" (end of playlist). '
+ 'or "playlist" (at end of playlist). '
'This option can be used multiple times to add different postprocessors'))
sponsorblock = optparse.OptionGroup(parser, 'SponsorBlock Options', description=(