]> jfr.im git - yt-dlp.git/blobdiff - youtube_dlc/__init__.py
Multiple output templates for different file types
[yt-dlp.git] / youtube_dlc / __init__.py
index 90479c6ff147ebd338943893789425cfba5c5196..646b135192be507164d85fd05c92fac8c7785485 100644 (file)
 
 from .options import (
     parseOpts,
+    _remux_formats,
 )
 from .compat import (
     compat_getpass,
-    compat_shlex_split,
     workaround_optparse_bug9161,
 )
 from .utils import (
@@ -46,6 +46,7 @@
 from .extractor import gen_extractors, list_extractors
 from .extractor.common import InfoExtractor
 from .extractor.adobepass import MSO_INFO
+from .postprocessor.metadatafromfield import MetadataFromFieldPP
 from .YoutubeDL import YoutubeDL
 
 
@@ -70,14 +71,7 @@ def _real_main(argv=None):
         std_headers['Referer'] = opts.referer
 
     # Custom HTTP headers
-    if opts.headers is not None:
-        for h in opts.headers:
-            if ':' not in h:
-                parser.error('wrong header formatting, it should be key:value, not "%s"' % h)
-            key, value = h.split(':', 1)
-            if opts.verbose:
-                write_string('[debug] Adding header from command line option %s:%s\n' % (key, value))
-            std_headers[key] = value
+    std_headers.update(opts.headers)
 
     # Dump user agent
     if opts.dump_user_agent:
@@ -216,12 +210,15 @@ def parse_retries(retries):
         opts.audioquality = opts.audioquality.strip('k').strip('K')
         if not opts.audioquality.isdigit():
             parser.error('invalid audio quality specified')
-    if opts.remuxvideo is not None:
-        if opts.remuxvideo not in ['mp4', 'mkv']:
-            parser.error('invalid video container format specified')
     if opts.recodevideo is not None:
-        if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv', 'avi']:
+        if opts.recodevideo not in _remux_formats:
             parser.error('invalid video recode format specified')
+    if opts.remuxvideo and opts.recodevideo:
+        opts.remuxvideo = None
+        write_string('WARNING: --remux-video is ignored since --recode-video was given\n', out=sys.stderr)
+    if opts.remuxvideo is not None:
+        if opts.remuxvideo not in _remux_formats:
+            parser.error('invalid video remux format specified')
     if opts.convertsubtitles is not None:
         if opts.convertsubtitles not in ['srt', 'vtt', 'ass', 'lrc']:
             parser.error('invalid subtitle format specified')
@@ -240,32 +237,45 @@ def parse_retries(retries):
     if opts.allsubtitles and not opts.writeautomaticsub:
         opts.writesubtitles = True
 
-    outtmpl = ((opts.outtmpl is not None and opts.outtmpl)
-               or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
-               or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
-               or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
-               or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
-               or (opts.useid and '%(id)s.%(ext)s')
-               or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
-               or DEFAULT_OUTTMPL)
-    if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
+    outtmpl = opts.outtmpl
+    if not outtmpl:
+        outtmpl = {'default': (
+            '%(title)s-%(id)s-%(format)s.%(ext)s' if opts.format == '-1' and opts.usetitle
+            else '%(id)s-%(format)s.%(ext)s' if opts.format == '-1'
+            else '%(autonumber)s-%(title)s-%(id)s.%(ext)s' if opts.usetitle and opts.autonumber
+            else '%(title)s-%(id)s.%(ext)s' if opts.usetitle
+            else '%(id)s.%(ext)s' if opts.useid
+            else '%(autonumber)s-%(id)s.%(ext)s' if opts.autonumber
+            else None)}
+    outtmpl_default = outtmpl.get('default')
+    if outtmpl_default is not None and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio:
         parser.error('Cannot download a video and extract audio into the same'
                      ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
-                     ' template'.format(outtmpl))
+                     ' template'.format(outtmpl_default))
+
     for f in opts.format_sort:
         if re.match(InfoExtractor.FormatSort.regex, f) is None:
             parser.error('invalid format sort string "%s" specified' % f)
 
+    if opts.metafromfield is None:
+        opts.metafromfield = []
+    if opts.metafromtitle is not None:
+        opts.metafromfield.append('title:%s' % opts.metafromtitle)
+    for f in opts.metafromfield:
+        if re.match(MetadataFromFieldPP.regex, f) is None:
+            parser.error('invalid format string "%s" specified for --parse-metadata' % f)
+
     any_getting = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
     any_printing = opts.print_json
     download_archive_fn = expand_path(opts.download_archive) if opts.download_archive is not None else opts.download_archive
 
     # PostProcessors
     postprocessors = []
-    if opts.metafromtitle:
+    if opts.metafromfield:
         postprocessors.append({
-            'key': 'MetadataFromTitle',
-            'titleformat': opts.metafromtitle
+            'key': 'MetadataFromField',
+            'formats': opts.metafromfield,
+            'when': 'beforedl'
         })
     if opts.extractaudio:
         postprocessors.append({
@@ -326,32 +336,24 @@ def parse_retries(retries):
             'force': opts.sponskrub_force,
             'ignoreerror': opts.sponskrub is None,
         })
-    # Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
-    # So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
+    # ExecAfterDownload must be the last PP
     if opts.exec_cmd:
         postprocessors.append({
             'key': 'ExecAfterDownload',
             'exec_cmd': opts.exec_cmd,
+            'when': 'aftermove'
         })
-    external_downloader_args = None
-    if opts.external_downloader_args:
-        external_downloader_args = compat_shlex_split(opts.external_downloader_args)
-
-    postprocessor_args = {}
-    if opts.postprocessor_args is not None:
-        for string in opts.postprocessor_args:
-            mobj = re.match(r'(?P<pp>\w+(?:\+\w+)?):(?P<args>.*)$', string)
-            if mobj is None:
-                if 'sponskrub' not in postprocessor_args:  # for backward compatibility
-                    postprocessor_args['sponskrub'] = []
-                    if opts.verbose:
-                        write_string('[debug] Adding postprocessor args from command line option sponskrub: \n')
-                pp_key, pp_args = 'default', string
-            else:
-                pp_key, pp_args = mobj.group('pp').lower(), mobj.group('args')
-            if opts.verbose:
-                write_string('[debug] Adding postprocessor args from command line option %s: %s\n' % (pp_key, pp_args))
-            postprocessor_args[pp_key] = compat_shlex_split(pp_args)
+
+    _args_compat_warning = 'WARNING: %s given without specifying name. The arguments will be given to all %s\n'
+    if 'default' in opts.external_downloader_args:
+        write_string(_args_compat_warning % ('--external-downloader-args', 'external downloaders'), out=sys.stderr),
+
+    if 'default-compat' in opts.postprocessor_args and 'default' not in opts.postprocessor_args:
+        write_string(_args_compat_warning % ('--post-processor-args', 'post-processors'), out=sys.stderr),
+        opts.postprocessor_args.setdefault('sponskrub', [])
+        opts.postprocessor_args['default'] = opts.postprocessor_args['default-compat']
+
+    audio_ext = opts.audioformat if (opts.extractaudio and opts.audioformat != 'best') else None
 
     match_filter = (
         None if opts.match_filter is None
@@ -390,6 +392,8 @@ def parse_retries(retries):
         'listformats': opts.listformats,
         'listformats_table': opts.listformats_table,
         'outtmpl': outtmpl,
+        'outtmpl_na_placeholder': opts.outtmpl_na_placeholder,
+        'paths': opts.paths,
         'autonumber_size': opts.autonumber_size,
         'autonumber_start': opts.autonumber_start,
         'restrictfilenames': opts.restrictfilenames,
@@ -412,13 +416,14 @@ def parse_retries(retries):
         'playlistreverse': opts.playlist_reverse,
         'playlistrandom': opts.playlist_random,
         'noplaylist': opts.noplaylist,
-        'logtostderr': opts.outtmpl == '-',
+        'logtostderr': outtmpl_default == '-',
         'consoletitle': opts.consoletitle,
         'nopart': opts.nopart,
         'updatetime': opts.updatetime,
         'writedescription': opts.writedescription,
         'writeannotations': opts.writeannotations,
-        'writeinfojson': opts.writeinfojson,
+        'writeinfojson': opts.writeinfojson or opts.getcomments,
+        'getcomments': opts.getcomments,
         'writethumbnail': opts.writethumbnail,
         'write_all_thumbnails': opts.write_all_thumbnails,
         'writelink': opts.writelink,
@@ -469,6 +474,7 @@ def parse_retries(retries):
         'extract_flat': opts.extract_flat,
         'mark_watched': opts.mark_watched,
         'merge_output_format': opts.merge_output_format,
+        'final_ext': opts.recodevideo or opts.remuxvideo or audio_ext,
         'postprocessors': postprocessors,
         'fixup': opts.fixup,
         'source_address': opts.source_address,
@@ -485,8 +491,8 @@ def parse_retries(retries):
         'ffmpeg_location': opts.ffmpeg_location,
         'hls_prefer_native': opts.hls_prefer_native,
         'hls_use_mpegts': opts.hls_use_mpegts,
-        'external_downloader_args': external_downloader_args,
-        'postprocessor_args': postprocessor_args,
+        'external_downloader_args': opts.external_downloader_args,
+        'postprocessor_args': opts.postprocessor_args,
         'cn_verification_proxy': opts.cn_verification_proxy,
         'geo_verification_proxy': opts.geo_verification_proxy,
         'config_location': opts.config_location,