]> jfr.im git - yt-dlp.git/blobdiff - youtube_dlc/__init__.py
Fix `--windows-filenames` removing `/` from UNIX paths
[yt-dlp.git] / youtube_dlc / __init__.py
index c58fb7563f1af2bebdbc3a1d2cc58b181987aa3d..60400632d585ec8696d6267dc59f518109640d6b 100644 (file)
 )
 from .compat import (
     compat_getpass,
-    compat_shlex_split,
     workaround_optparse_bug9161,
 )
 from .utils import (
     DateRange,
     decodeOption,
-    DEFAULT_OUTTMPL,
     DownloadError,
     ExistingVideoReached,
     expand_path,
     preferredencoding,
     read_batch_urls,
     RejectedVideoReached,
+    REMUX_EXTENSIONS,
+    render_table,
     SameFileError,
     setproctitle,
     std_headers,
     write_string,
-    render_table,
 )
 from .update import update_self
 from .downloader import (
@@ -46,6 +45,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
 
 
@@ -209,12 +209,14 @@ 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_EXTENSIONS:
             parser.error('invalid video recode format specified')
+    if opts.remuxvideo is not None:
+        opts.remuxvideo = opts.remuxvideo.replace(' ', '')
+        remux_regex = r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(REMUX_EXTENSIONS))
+        if not re.match(remux_regex, opts.remuxvideo):
+            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')
@@ -228,37 +230,79 @@ def parse_retries(retries):
     if opts.extractaudio and not opts.keepvideo and opts.format is None:
         opts.format = 'bestaudio/best'
 
-    # --all-sub automatically sets --write-sub if --write-auto-sub is not given
-    # this was the old behaviour if only --all-sub was given.
-    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
 
+    def report_conflict(arg1, arg2):
+        write_string('WARNING: %s is ignored since %s was given\n' % (arg2, arg1), out=sys.stderr)
+    if opts.remuxvideo and opts.recodevideo:
+        report_conflict('--recode-video', '--remux-video')
+        opts.remuxvideo = False
+    if opts.allow_unplayable_formats:
+        if opts.extractaudio:
+            report_conflict('--allow-unplayable-formats', '--extract-audio')
+            opts.extractaudio = False
+        if opts.remuxvideo:
+            report_conflict('--allow-unplayable-formats', '--remux-video')
+            opts.remuxvideo = False
+        if opts.recodevideo:
+            report_conflict('--allow-unplayable-formats', '--recode-video')
+            opts.recodevideo = False
+        if opts.addmetadata:
+            report_conflict('--allow-unplayable-formats', '--add-metadata')
+            opts.addmetadata = False
+        if opts.embedsubtitles:
+            report_conflict('--allow-unplayable-formats', '--embed-subs')
+            opts.embedsubtitles = False
+        if opts.embedthumbnail:
+            report_conflict('--allow-unplayable-formats', '--embed-thumbnail')
+            opts.embedthumbnail = False
+        if opts.xattrs:
+            report_conflict('--allow-unplayable-formats', '--xattrs')
+            opts.xattrs = False
+        if opts.fixup and opts.fixup.lower() not in ('never', 'ignore'):
+            report_conflict('--allow-unplayable-formats', '--fixup')
+        opts.fixup = 'never'
+        if opts.sponskrub:
+            report_conflict('--allow-unplayable-formats', '--sponskrub')
+        opts.sponskrub = False
+
     # 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({
@@ -293,9 +337,17 @@ def parse_retries(retries):
             'format': opts.convertsubtitles,
         })
     if opts.embedsubtitles:
+        already_have_subtitle = opts.writesubtitles
         postprocessors.append({
             'key': 'FFmpegEmbedSubtitle',
+            'already_have_subtitle': already_have_subtitle
         })
+        if not already_have_subtitle:
+            opts.writesubtitles = True
+    # --all-sub automatically sets --write-sub if --write-auto-sub is not given
+    # this was the old behaviour if only --all-sub was given.
+    if opts.allsubtitles and not opts.writeautomaticsub:
+        opts.writesubtitles = True
     if opts.embedthumbnail:
         already_have_thumbnail = opts.writethumbnail or opts.write_all_thumbnails
         postprocessors.append({
@@ -319,18 +371,32 @@ 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'
         })
 
+    def report_args_compat(arg, name):
+        write_string(
+            'WARNING: %s given without specifying name. The arguments will be given to all %s\n' % (arg, name),
+            out=sys.stderr)
+    if 'default' in opts.external_downloader_args:
+        report_args_compat('--external-downloader-args', 'external downloaders')
+
     if 'default-compat' in opts.postprocessor_args and 'default' not in opts.postprocessor_args:
+        report_args_compat('--post-processor-args', 'post-processors')
         opts.postprocessor_args.setdefault('sponskrub', [])
         opts.postprocessor_args['default'] = opts.postprocessor_args['default-compat']
 
+    final_ext = (
+        opts.recodevideo
+        or (opts.remuxvideo in REMUX_EXTENSIONS) and opts.remuxvideo
+        or (opts.extractaudio and opts.audioformat != 'best') and opts.audioformat
+        or None)
+
     match_filter = (
         None if opts.match_filter is None
         else match_filter_func(opts.match_filter))
@@ -361,6 +427,7 @@ def parse_retries(retries):
         'simulate': opts.simulate or any_getting,
         'skip_download': opts.skip_download,
         'format': opts.format,
+        'allow_unplayable_formats': opts.allow_unplayable_formats,
         'format_sort': opts.format_sort,
         'format_sort_force': opts.format_sort_force,
         'allow_multiple_video_streams': opts.allow_multiple_video_streams,
@@ -368,9 +435,12 @@ 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,
+        'windowsfilenames': opts.windowsfilenames,
         'ignoreerrors': opts.ignoreerrors,
         'force_generic_extractor': opts.force_generic_extractor,
         'ratelimit': opts.ratelimit,
@@ -390,13 +460,15 @@ 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,
+        'allow_playlist_files': opts.allow_playlist_files,
+        'getcomments': opts.getcomments,
         'writethumbnail': opts.writethumbnail,
         'write_all_thumbnails': opts.write_all_thumbnails,
         'writelink': opts.writelink,
@@ -447,6 +519,7 @@ def parse_retries(retries):
         'extract_flat': opts.extract_flat,
         'mark_watched': opts.mark_watched,
         'merge_output_format': opts.merge_output_format,
+        'final_ext': final_ext,
         'postprocessors': postprocessors,
         'fixup': opts.fixup,
         'source_address': opts.source_address,
@@ -463,6 +536,7 @@ def parse_retries(retries):
         'ffmpeg_location': opts.ffmpeg_location,
         'hls_prefer_native': opts.hls_prefer_native,
         'hls_use_mpegts': opts.hls_use_mpegts,
+        'hls_split_discontinuity': opts.hls_split_discontinuity,
         'external_downloader_args': opts.external_downloader_args,
         'postprocessor_args': opts.postprocessor_args,
         'cn_verification_proxy': opts.cn_verification_proxy,
@@ -477,16 +551,22 @@ def parse_retries(retries):
     }
 
     with YoutubeDL(ydl_opts) as ydl:
-        # Update version
-        if opts.update_self:
-            update_self(ydl.to_screen, opts.verbose, ydl._opener)
+        actual_use = len(all_urls) or opts.load_info_filename
 
         # Remove cache dir
         if opts.rm_cachedir:
             ydl.cache.remove()
 
+        # Update version
+        if opts.update_self:
+            # If updater returns True, exit. Required for windows
+            if update_self(ydl.to_screen, opts.verbose, ydl._opener):
+                if actual_use:
+                    sys.exit('ERROR: The program must exit for the update to complete')
+                sys.exit()
+
         # Maybe do nothing
-        if (len(all_urls) < 1) and (opts.load_info_filename is None):
+        if not actual_use:
             if opts.update_self or opts.rm_cachedir:
                 sys.exit()