]> jfr.im git - yt-dlp.git/blobdiff - youtube_dlc/options.py
[pyinst.py] Move back to root dir (Closes #63)
[yt-dlp.git] / youtube_dlc / options.py
index 7a30882f198c8dae41d3ab3cdd46210bf8c9c9c4..a7c8701719023f14f4b39f949755aa1d5dc792e5 100644 (file)
     compat_shlex_split,
 )
 from .utils import (
+    expand_path,
+    get_executable_path,
+    OUTTMPL_TYPES,
     preferredencoding,
+    REMUX_EXTENSIONS,
     write_string,
 )
 from .version import __version__
@@ -62,7 +66,7 @@ def _readUserConf(package_name, default=[]):
             userConfFile = os.path.join(xdg_config_home, '%s.conf' % package_name)
         userConf = _readOptions(userConfFile, default=None)
         if userConf is not None:
-            return userConf
+            return userConf, userConfFile
 
         # appdata
         appdata_dir = compat_getenv('appdata')
@@ -70,19 +74,21 @@ def _readUserConf(package_name, default=[]):
             userConfFile = os.path.join(appdata_dir, package_name, 'config')
             userConf = _readOptions(userConfFile, default=None)
             if userConf is None:
-                userConf = _readOptions('%s.txt' % userConfFile, default=None)
+                userConfFile += '.txt'
+                userConf = _readOptions(userConfFile, default=None)
         if userConf is not None:
-            return userConf
+            return userConf, userConfFile
 
         # home
         userConfFile = os.path.join(compat_expanduser('~'), '%s.conf' % package_name)
         userConf = _readOptions(userConfFile, default=None)
         if userConf is None:
-            userConf = _readOptions('%s.txt' % userConfFile, default=None)
+            userConfFile += '.txt'
+            userConf = _readOptions(userConfFile, default=None)
         if userConf is not None:
-            return userConf
+            return userConf, userConfFile
 
-        return default
+        return default, None
 
     def _format_option_string(option):
         ''' ('-o', '--option') -> -o, --format METAVAR'''
@@ -187,7 +193,7 @@ def _dict_from_multiple_values_options_callback(
     general.add_option(
         '--config-location',
         dest='config_location', metavar='PATH',
-        help='Location of the configuration file; either the path to the config or its containing directory')
+        help='Location of the main configuration file; either the path to the config or its containing directory')
     general.add_option(
         '--flat-playlist',
         action='store_const', dest='extract_flat', const='in_playlist', default=False,
@@ -641,7 +647,7 @@ def _dict_from_multiple_values_options_callback(
         metavar='NAME:ARGS', dest='external_downloader_args', default={}, type='str',
         action='callback', callback=_dict_from_multiple_values_options_callback,
         callback_kwargs={
-            'allowed_keys': '|'.join(list_external_downloaders()), 
+            'allowed_keys': '|'.join(list_external_downloaders()),
             'default_key': 'default', 'process': compat_shlex_split},
         help=(
             'Give these arguments to the external downloader. '
@@ -819,10 +825,33 @@ def _dict_from_multiple_values_options_callback(
     filesystem.add_option(
         '--id', default=False,
         action='store_true', dest='useid', help=optparse.SUPPRESS_HELP)
+    filesystem.add_option(
+        '-P', '--paths',
+        metavar='TYPE:PATH', dest='paths', default={}, type='str',
+        action='callback', callback=_dict_from_multiple_values_options_callback,
+        callback_kwargs={
+            'allowed_keys': 'home|temp|%s' % '|'.join(OUTTMPL_TYPES.keys()),
+            'process': lambda x: x.strip()},
+        help=(
+            'The paths where the files should be downloaded. '
+            'Specify the type of file and the path separated by a colon ":". '
+            'All the same types as --output are supported. '
+            'Additionally, you can also provide "home" and "temp" paths. '
+            'All intermediary files are first downloaded to the temp path and '
+            'then the final files are moved over to the home path after download is finished. '
+            'This option is ignored if --output is an absolute path'))
     filesystem.add_option(
         '-o', '--output',
-        dest='outtmpl', metavar='TEMPLATE',
+        metavar='[TYPE:]TEMPLATE', dest='outtmpl', default={}, type='str',
+        action='callback', callback=_dict_from_multiple_values_options_callback,
+        callback_kwargs={
+            'allowed_keys': '|'.join(OUTTMPL_TYPES.keys()),
+            'default_key': 'default', 'process': lambda x: x.strip()},
         help='Output filename template, see "OUTPUT TEMPLATE" for details')
+    filesystem.add_option(
+        '--output-na-placeholder',
+        dest='outtmpl_na_placeholder', metavar='TEXT', default='NA',
+        help=('Placeholder value for unavailable meta fields in output filename template (default: "%default")'))
     filesystem.add_option(
         '--autonumber-size',
         dest='autonumber_size', metavar='NUMBER', type=int,
@@ -866,11 +895,13 @@ def _dict_from_multiple_values_options_callback(
     filesystem.add_option(
         '-c', '--continue',
         action='store_true', dest='continue_dl', default=True,
-        help='Resume partially downloaded files (default)')
+        help='Resume partially downloaded files/fragments (default)')
     filesystem.add_option(
         '--no-continue',
         action='store_false', dest='continue_dl',
-        help='Restart download of partially downloaded files from beginning')
+        help=(
+            'Do not resume partially downloaded fragments. '
+            'If the file is unfragmented, restart download of the entire file'))
     filesystem.add_option(
         '--part',
         action='store_false', dest='nopart', default=False,
@@ -898,7 +929,7 @@ def _dict_from_multiple_values_options_callback(
     filesystem.add_option(
         '--write-info-json',
         action='store_true', dest='writeinfojson', default=False,
-        help='Write video metadata to a .info.json file')
+        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='writeinfojson',
@@ -911,6 +942,22 @@ def _dict_from_multiple_values_options_callback(
         '--no-write-annotations',
         action='store_false', dest='writeannotations',
         help='Do not write video annotations (default)')
+    filesystem.add_option(
+        '--write-playlist-metafiles',
+        action='store_true', dest='allow_playlist_files', default=True,
+        help=(
+            'Write playlist metadata in addition to the video metadata '
+            'when using --write-info-json, --write-description etc. (default)'))
+    filesystem.add_option(
+        '--no-write-playlist-metafiles',
+        action='store_false', dest='allow_playlist_files',
+        help=(
+            'Do not write playlist metadata when using '
+            '--write-info-json, --write-description etc.'))
+    filesystem.add_option(
+        '--get-comments',
+        action='store_true', dest='getcomments', default=False,
+        help='Retrieve video comments to be placed in the .info.json file')
     filesystem.add_option(
         '--load-info-json', '--load-info',
         dest='load_info_filename', metavar='FILE',
@@ -978,24 +1025,28 @@ def _dict_from_multiple_values_options_callback(
     postproc.add_option(
         '-x', '--extract-audio',
         action='store_true', dest='extractaudio', default=False,
-        help='Convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
+        help='Convert video files to audio-only files (requires ffmpeg and ffprobe)')
     postproc.add_option(
         '--audio-format', metavar='FORMAT', dest='audioformat', default='best',
         help='Specify audio format: "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "%default" by default; No effect without -x')
     postproc.add_option(
         '--audio-quality', metavar='QUALITY',
         dest='audioquality', default='5',
-        help='Specify ffmpeg/avconv 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 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default %default)')
     postproc.add_option(
         '--remux-video',
         metavar='FORMAT', dest='remuxvideo', default=None,
         help=(
-            'Remux the video into another container if necessary (currently supported: mp4|mkv). '
-            'If target container does not support the video/audio codec, remuxing will fail'))
+            '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(REMUX_EXTENSIONS)))
     postproc.add_option(
         '--recode-video',
         metavar='FORMAT', dest='recodevideo', default=None,
-        help='Re-encode the video into another format if re-encoding is necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)')
+        help=(
+            'Re-encode the video into another format if re-encoding is necessary. '
+            'The supported formats are the same as --remux-video'))
     postproc.add_option(
         '--postprocessor-args', '--ppa',
         metavar='NAME:ARGS', dest='postprocessor_args', default={}, type='str',
@@ -1007,7 +1058,7 @@ def _dict_from_multiple_values_options_callback(
             'to give the argument to the specified postprocessor/executable. Supported postprocessors are: '
             'SponSkrub, ExtractAudio, VideoRemuxer, VideoConvertor, EmbedSubtitle, Metadata, Merger, '
             'FixupStretched, FixupM4a, FixupM3u8, SubtitlesConvertor and EmbedThumbnail. '
-            'The supported executables are: SponSkrub, FFmpeg, FFprobe, avconf, avprobe and AtomicParsley. '
+            'The supported executables are: SponSkrub, FFmpeg, FFprobe, and AtomicParsley. '
             'You can use this option multiple times to give different arguments to different postprocessors. '
             'You can also specify "PP+EXE:ARGS" to give the arguments to the specified executable '
             'only when being used by the specified postprocessor. '
@@ -1055,14 +1106,20 @@ def _dict_from_multiple_values_options_callback(
     postproc.add_option(
         '--metadata-from-title',
         metavar='FORMAT', dest='metafromtitle',
+        help=optparse.SUPPRESS_HELP)
+    postproc.add_option(
+        '--parse-metadata',
+        metavar='FIELD:FORMAT', dest='metafromfield', action='append',
         help=(
-            'Parse additional metadata like song title / artist from the video title. '
-            'The format syntax is the same as --output. Regular expression with '
-            'named capture groups may also be used. '
-            'The parsed parameters replace existing values. '
-            'Example: --metadata-from-title "%(artist)s - %(title)s" matches a title like '
+            'Parse additional metadata like title/artist from other fields. '
+            'Give field name to extract data from, and format of the field seperated by a ":". '
+            'Either regular expression with named capture groups or a '
+            'similar syntax to the output template can also be used. '
+            'The parsed parameters replace any existing values and can be use in output template'
+            'This option can be used multiple times. '
+            'Example: --parse-metadata "title:%(artist)s - %(title)s" matches a title like '
             '"Coldplay - Paradise". '
-            'Example (regex): --metadata-from-title "(?P<artist>.+?) - (?P<title>.+)"'))
+            'Example (regex): --parse-metadata "description:Artist - (?P<artist>.+?)"'))
     postproc.add_option(
         '--xattrs',
         action='store_true', dest='xattrs', default=False,
@@ -1077,15 +1134,15 @@ def _dict_from_multiple_values_options_callback(
     postproc.add_option(
         '--prefer-avconv', '--no-prefer-ffmpeg',
         action='store_false', dest='prefer_ffmpeg',
-        help='Prefer avconv over ffmpeg for running the postprocessors (Alias: --no-prefer-ffmpeg)')
+        help=optparse.SUPPRESS_HELP)
     postproc.add_option(
         '--prefer-ffmpeg', '--no-prefer-avconv',
-        action='store_true', dest='prefer_ffmpeg',
-        help='Prefer ffmpeg over avconv for running the postprocessors (default) (Alias: --no-prefer-avconv)')
+        action='store_true', dest='prefer_ffmpeg', default=True,
+        help=optparse.SUPPRESS_HELP)
     postproc.add_option(
         '--ffmpeg-location', '--avconv-location', metavar='PATH',
         dest='ffmpeg_location',
-        help='Location of the ffmpeg/avconv binary; either the path to the binary or its containing directory (Alias: --avconv-location)')
+        help='Location of the ffmpeg binary; either the path to the binary or its containing directory')
     postproc.add_option(
         '--exec',
         metavar='CMD', dest='exec_cmd',
@@ -1171,59 +1228,69 @@ def compat_conf(conf):
             return conf
 
         configs = {
-            'command_line': compat_conf(sys.argv[1:]),
-            'custom': [], 'portable': [], 'user': [], 'system': []}
-        opts, args = parser.parse_args(configs['command_line'])
+            '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 get_configs():
-            if '--config-location' in configs['command_line']:
+            if '--config-location' in configs['command-line']:
                 location = compat_expanduser(opts.config_location)
                 if os.path.isdir(location):
                     location = os.path.join(location, 'youtube-dlc.conf')
                 if not os.path.exists(location):
                     parser.error('config-location %s does not exist.' % location)
-                configs['custom'] = _readOptions(location)
-
-            if '--ignore-config' in configs['command_line']:
+                configs['custom'] = _readOptions(location, default=None)
+                if configs['custom'] is None:
+                    configs['custom'] = []
+                else:
+                    paths['custom'] = location
+            if '--ignore-config' in configs['command-line']:
                 return
             if '--ignore-config' in configs['custom']:
                 return
 
-            def get_portable_path():
-                path = os.path.dirname(sys.argv[0])
-                if os.path.abspath(sys.argv[0]) != os.path.abspath(sys.executable):  # Not packaged
-                    path = os.path.join(path, '..')
-                return os.path.abspath(path)
-
-            run_path = get_portable_path()
-            configs['portable'] = _readOptions(os.path.join(run_path, 'yt-dlp.conf'), default=None)
-            if configs['portable'] is None:
-                configs['portable'] = _readOptions(os.path.join(run_path, 'youtube-dlc.conf'))
+            def read_options(path, user=False):
+                for package in ('yt-dlp', 'youtube-dlc'):
+                    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']:
                 return
-            configs['system'] = _readOptions('/etc/yt-dlp.conf', default=None)
-            if configs['system'] is None:
-                configs['system'] = _readOptions('/etc/youtube-dlc.conf')
 
+            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']:
+                return
+
+            configs['system'], paths['system'] = read_options('/etc')
             if '--ignore-config' in configs['system']:
                 return
-            configs['user'] = _readUserConf('yt-dlp', default=None)
-            if configs['user'] is None:
-                configs['user'] = _readUserConf('youtube-dlc')
+
+            configs['user'], paths['user'] = read_options('', True)
             if '--ignore-config' in configs['user']:
-                configs['system'] = []
+                configs['system'], paths['system'] = [], None
 
         get_configs()
-        argv = configs['system'] + configs['user'] + configs['portable'] + configs['custom'] + configs['command_line']
+        argv = configs['system'] + configs['user'] + configs['home'] + configs['portable'] + configs['custom'] + configs['command-line']
         opts, args = parser.parse_args(argv)
         if opts.verbose:
-            for conf_label, conf in (
-                    ('System config', configs['system']),
-                    ('User config', configs['user']),
-                    ('Portable config', configs['portable']),
-                    ('Custom config', configs['custom']),
-                    ('Command-line args', configs['command_line'])):
-                write_string('[debug] %s: %s\n' % (conf_label, repr(_hide_login_info(conf))))
+            for label in ('System', 'User', 'Portable', 'Home', 'Custom', 'Command-line'):
+                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]))))
 
     return parser, opts, args