]> jfr.im git - yt-dlp.git/blobdiff - youtube_dlc/__init__.py
[pyinst.py] Move back to root dir (Closes #63)
[yt-dlp.git] / youtube_dlc / __init__.py
index a663417dab292caf7c6e4a72599a6419391a108c..d28510467a452d2f588e7af6c92b8f608dfcd5aa 100644 (file)
@@ -9,6 +9,7 @@
 import io
 import os
 import random
+import re
 import sys
 
 
 )
 from .compat import (
     compat_getpass,
-    compat_shlex_split,
     workaround_optparse_bug9161,
 )
 from .utils import (
     DateRange,
     decodeOption,
-    DEFAULT_OUTTMPL,
     DownloadError,
+    ExistingVideoReached,
     expand_path,
     match_filter_func,
     MaxDownloadsReached,
     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 (
     FileDownloader,
 )
 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
 
 
@@ -66,14 +70,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:
@@ -174,6 +171,9 @@ def _real_main(argv=None):
         opts.max_sleep_interval = opts.sleep_interval
     if opts.ap_mso and opts.ap_mso not in MSO_INFO:
         parser.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
+    if opts.overwrites:
+        # --yes-overwrites implies --no-continue
+        opts.continue_dl = False
 
     def parse_retries(retries):
         if retries in ('inf', 'infinite'):
@@ -210,8 +210,16 @@ def parse_retries(retries):
         if not opts.audioquality.isdigit():
             parser.error('invalid audio quality 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 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:
+        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')
@@ -230,18 +238,33 @@ 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
@@ -249,10 +272,11 @@ def parse_retries(retries):
 
     # 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({
@@ -261,6 +285,11 @@ def parse_retries(retries):
             'preferredquality': opts.audioquality,
             'nopostoverwrites': opts.nopostoverwrites,
         })
+    if opts.remuxvideo:
+        postprocessors.append({
+            'key': 'FFmpegVideoRemuxer',
+            'preferedformat': opts.remuxvideo,
+        })
     if opts.recodevideo:
         postprocessors.append({
             'key': 'FFmpegVideoConvertor',
@@ -297,24 +326,46 @@ def parse_retries(retries):
     # contents
     if opts.xattrs:
         postprocessors.append({'key': 'XAttrMetadata'})
-    # 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.
+    # This should be below all ffmpeg PP because it may cut parts out from the video
+    # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
+    if opts.sponskrub is not False:
+        postprocessors.append({
+            'key': 'SponSkrub',
+            'path': opts.sponskrub_path,
+            'args': opts.sponskrub_args,
+            'cut': opts.sponskrub_cut,
+            'force': opts.sponskrub_force,
+            'ignoreerror': opts.sponskrub is None,
+        })
+    # 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 = None
-    if opts.postprocessor_args:
-        postprocessor_args = compat_shlex_split(opts.postprocessor_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']
+
+    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))
 
     ydl_opts = {
+        'convertsubtitles': opts.convertsubtitles,
         'usenetrc': opts.usenetrc,
         'username': opts.username,
         'password': opts.password,
@@ -335,18 +386,26 @@ def parse_retries(retries):
         'forceformat': opts.getformat,
         'forcejson': opts.dumpjson or opts.print_json,
         'dump_single_json': opts.dump_single_json,
+        'force_write_download_archive': opts.force_write_download_archive,
         'simulate': opts.simulate or any_getting,
         'skip_download': opts.skip_download,
         'format': opts.format,
+        'format_sort': opts.format_sort,
+        'format_sort_force': opts.format_sort_force,
+        'allow_multiple_video_streams': opts.allow_multiple_video_streams,
+        'allow_multiple_audio_streams': opts.allow_multiple_audio_streams,
         '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,
         'ignoreerrors': opts.ignoreerrors,
         'force_generic_extractor': opts.force_generic_extractor,
         'ratelimit': opts.ratelimit,
-        'nooverwrites': opts.nooverwrites,
+        'overwrites': opts.overwrites,
         'retries': opts.retries,
         'fragment_retries': opts.fragment_retries,
         'skip_unavailable_fragments': opts.skip_unavailable_fragments,
@@ -362,15 +421,20 @@ 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,
+        'writeurllink': opts.writeurllink,
+        'writewebloclink': opts.writewebloclink,
+        'writedesktoplink': opts.writedesktoplink,
         'writesubtitles': opts.writesubtitles,
         'writeautomaticsub': opts.writeautomaticsub,
         'allsubtitles': opts.allsubtitles,
@@ -381,6 +445,7 @@ def parse_retries(retries):
         'rejecttitle': decodeOption(opts.rejecttitle),
         'max_downloads': opts.max_downloads,
         'prefer_free_formats': opts.prefer_free_formats,
+        'trim_file_name': opts.trim_file_name,
         'verbose': opts.verbose,
         'dump_intermediate_pages': opts.dump_intermediate_pages,
         'write_pages': opts.write_pages,
@@ -395,6 +460,8 @@ def parse_retries(retries):
         'youtube_print_sig_code': opts.youtube_print_sig_code,
         'age_limit': opts.age_limit,
         'download_archive': download_archive_fn,
+        'break_on_existing': opts.break_on_existing,
+        'break_on_reject': opts.break_on_reject,
         'cookiefile': opts.cookiefile,
         'nocheckcertificate': opts.no_check_certificate,
         'prefer_insecure': opts.prefer_insecure,
@@ -405,17 +472,21 @@ def parse_retries(retries):
         'prefer_ffmpeg': opts.prefer_ffmpeg,
         'include_ads': opts.include_ads,
         'default_search': opts.default_search,
+        'dynamic_mpd': opts.dynamic_mpd,
         'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
+        'youtube_include_hls_manifest': opts.youtube_include_hls_manifest,
         'encoding': opts.encoding,
         '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,
         'call_home': opts.call_home,
         'sleep_interval': opts.sleep_interval,
         'max_sleep_interval': opts.max_sleep_interval,
+        'sleep_interval_subtitles': opts.sleep_interval_subtitles,
         'external_downloader': opts.external_downloader,
         'list_thumbnails': opts.list_thumbnails,
         'playlist_items': opts.playlist_items,
@@ -425,8 +496,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,
@@ -462,8 +533,8 @@ def parse_retries(retries):
                 retcode = ydl.download_with_info_file(expand_path(opts.load_info_filename))
             else:
                 retcode = ydl.download(all_urls)
-        except MaxDownloadsReached:
-            ydl.to_screen('--max-download limit reached, aborting.')
+        except (MaxDownloadsReached, ExistingVideoReached, RejectedVideoReached):
+            ydl.to_screen('Aborting remaining downloads')
             retcode = 101
 
     sys.exit(retcode)