]> jfr.im git - yt-dlp.git/commitdiff
Allow `--exec` to be run at any post-processing stage
authorpukkandan <redacted>
Mon, 3 Jan 2022 11:13:54 +0000 (16:43 +0530)
committerpukkandan <redacted>
Mon, 3 Jan 2022 14:10:02 +0000 (19:40 +0530)
Deprecates `--exec-before-download`

README.md
yt_dlp/YoutubeDL.py
yt_dlp/__init__.py
yt_dlp/options.py
yt_dlp/postprocessor/exec.py
yt_dlp/utils.py

index e032ea6e6f73f3fc83503de09e3703e84223db5d..1b8680e339bc2b80e95b3acbd3be73c0f3797be2 100644 (file)
--- a/README.md
+++ b/README.md
@@ -896,23 +896,20 @@ ## Post-Processing Options:
     --ffmpeg-location PATH           Location of the ffmpeg binary; either the
                                      path to the binary or its containing
                                      directory
-    --exec CMD                       Execute a command on the file after
-                                     downloading and post-processing. Same
-                                     syntax as the output template can be used
-                                     to pass any field as arguments to the
-                                     command. An additional field "filepath"
+    --exec [WHEN:]CMD                Execute a command, optionally prefixed with
+                                     when to execute it (after_move if
+                                     unspecified), separated by a ":". Supported
+                                     values of "WHEN" are the same as that of
+                                     --use-postprocessor. Same syntax as the
+                                     output template can be used to pass any
+                                     field as arguments to the command. After
+                                     download, an additional field "filepath"
                                      that contains the final path of the
-                                     downloaded file is also available. If no
-                                     fields are passed, %(filepath)q is appended
-                                     to the end of the command. This option can
-                                     be used multiple times
-    --no-exec                        Remove any previously defined --exec
-    --exec-before-download CMD       Execute a command before the actual
-                                     download. The syntax is the same as --exec
-                                     but "filepath" is not available. This
+                                     downloaded file is also available, and if
+                                     no fields are passed, %(filepath)q is
+                                     appended to the end of the command. This
                                      option can be used multiple times
-    --no-exec-before-download        Remove any previously defined
-                                     --exec-before-download
+    --no-exec                        Remove any previously defined --exec
     --convert-subs FORMAT            Convert the subtitles to another format
                                      (currently supported: srt|vtt|ass|lrc)
                                      (Alias: --convert-subtitles)
@@ -1800,6 +1797,8 @@ #### Redundant options
 #### Not recommended
 While these options still work, their use is not recommended since there are other alternatives to achieve the same
 
+    --exec-before-download CMD       --exec "before_dl:CMD"
+    --no-exec-before-download        --no-exec
     --all-formats                    -f all
     --all-subs                       --sub-langs all --write-subs
     --print-json                     -j --no-simulate
index faea854855da8e07aca72305d9a94353cadf5eeb..5b285e1a161be0efc5194622de7395676e3001db 100644 (file)
@@ -91,6 +91,7 @@
     PerRequestProxyHandler,
     platform_name,
     Popen,
+    POSTPROCESS_WHEN,
     PostProcessingError,
     preferredencoding,
     prepend_extension,
@@ -507,7 +508,7 @@ class YoutubeDL(object):
 
     params = None
     _ies = {}
-    _pps = {'pre_process': [], 'before_dl': [], 'after_move': [], 'post_process': []}
+    _pps = {k: [] for k in POSTPROCESS_WHEN}
     _printed_messages = set()
     _first_webpage_request = True
     _download_retcode = None
@@ -525,7 +526,7 @@ def __init__(self, params=None, auto_init=True):
             params = {}
         self._ies = {}
         self._ies_instances = {}
-        self._pps = {'pre_process': [], 'before_dl': [], 'after_move': [], 'post_process': []}
+        self._pps = {k: [] for k in POSTPROCESS_WHEN}
         self._printed_messages = set()
         self._first_webpage_request = True
         self._post_hooks = []
index af7a4e195ca82e9923c5b0910b45700b27d7f563..85f000df4f5f0816005481a55a1b5923e4503310 100644 (file)
@@ -143,6 +143,8 @@ def _real_main(argv=None):
             '"-f best" selects the best pre-merged format which is often not the best option',
             'To let yt-dlp download and merge the best available formats, simply do not pass any format selection',
             'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning')))
+    if opts.exec_cmd.get('before_dl') and opts.exec_before_dl_cmd:
+        parser.error('using "--exec-before-download" conflicts with "--exec before_dl:"')
     if opts.usenetrc and (opts.username is not None or opts.password is not None):
         parser.error('using .netrc conflicts with giving username/password')
     if opts.password is not None and opts.username is None:
@@ -489,13 +491,6 @@ def report_unplayable_conflict(opt_name, arg, default=False, allowed=None):
             # Run this before the actual video download
             'when': 'before_dl'
         })
-    # Must be after all other before_dl
-    if opts.exec_before_dl_cmd:
-        postprocessors.append({
-            'key': 'Exec',
-            'exec_cmd': opts.exec_before_dl_cmd,
-            'when': 'before_dl'
-        })
     if opts.extractaudio:
         postprocessors.append({
             'key': 'FFmpegExtractAudio',
@@ -596,13 +591,15 @@ def report_unplayable_conflict(opt_name, arg, default=False, allowed=None):
     # XAttrMetadataPP should be run after post-processors that may change file contents
     if opts.xattrs:
         postprocessors.append({'key': 'XAttrMetadata'})
-    # Exec must be the last PP
-    if opts.exec_cmd:
+    # Exec must be the last PP of each category
+    if opts.exec_before_dl_cmd:
+        opts.exec_cmd.setdefault('before_dl', opts.exec_before_dl_cmd)
+    for when, exec_cmd in opts.exec_cmd.items():
         postprocessors.append({
             'key': 'Exec',
-            'exec_cmd': opts.exec_cmd,
+            'exec_cmd': exec_cmd,
             # Run this only after the files have been moved to their final locations
-            'when': 'after_move'
+            'when': when,
         })
 
     def report_args_compat(arg, name):
index d48cd1457a7b3b8cc45e677a610601cc9ac73842..f4e5d14df5c565477c01c59e4b6b1f3c04b21d6a 100644 (file)
@@ -16,6 +16,7 @@
     expand_path,
     get_executable_path,
     OUTTMPL_TYPES,
+    POSTPROCESS_WHEN,
     preferredencoding,
     remove_end,
     write_string,
@@ -1393,29 +1394,33 @@ def _dict_from_options_callback(
         dest='ffmpeg_location',
         help='Location of the ffmpeg binary; either the path to the binary or its containing directory')
     postproc.add_option(
-        '--exec', metavar='CMD',
-        action='append', dest='exec_cmd',
-        help=(
-            'Execute a command on the file after downloading and post-processing. '
+        '--exec',
+        metavar='[WHEN:]CMD', dest='exec_cmd', default={}, type='str',
+        action='callback', callback=_dict_from_options_callback,
+        callback_kwargs={
+            'allowed_keys': '|'.join(map(re.escape, POSTPROCESS_WHEN)),
+            'default_key': 'after_move',
+            'multiple_keys': False,
+            'append': True,
+        }, help=(
+            'Execute a command, optionally prefixed with when to execute it (after_move if unspecified), separated by a ":". '
+            'Supported values of "WHEN" are the same as that of --use-postprocessor. '
             'Same syntax as the output template can be used to pass any field as arguments to the command. '
-            'An additional field "filepath" that contains the final path of the downloaded file is also available. '
-            'If no fields are passed, %(filepath)q is appended to the end of the command. '
+            'After download, an additional field "filepath" that contains the final path of the downloaded file '
+            'is also available, and if no fields are passed, %(filepath)q is appended to the end of the command. '
             'This option can be used multiple times'))
     postproc.add_option(
         '--no-exec',
-        action='store_const', dest='exec_cmd', const=[],
+        action='store_const', dest='exec_cmd', const={},
         help='Remove any previously defined --exec')
     postproc.add_option(
         '--exec-before-download', metavar='CMD',
         action='append', dest='exec_before_dl_cmd',
-        help=(
-            'Execute a command before the actual download. '
-            'The syntax is the same as --exec but "filepath" is not available. '
-            'This option can be used multiple times'))
+        help=optparse.SUPPRESS_HELP)
     postproc.add_option(
         '--no-exec-before-download',
         action='store_const', dest='exec_before_dl_cmd', const=[],
-        help='Remove any previously defined --exec-before-download')
+        help=optparse.SUPPRESS_HELP)
     postproc.add_option(
         '--convert-subs', '--convert-sub', '--convert-subtitles',
         metavar='FORMAT', dest='convertsubtitles', default=None,
index 28a7c3d704e399abba7583ef04c094e0e5536d73..63f4d23f26d90f7cad4803dbccfeffb0d71d50c7 100644 (file)
@@ -22,11 +22,13 @@ def parse_cmd(self, cmd, info):
         if tmpl_dict:  # if there are no replacements, tmpl_dict = {}
             return self._downloader.escape_outtmpl(tmpl) % tmpl_dict
 
-        # If no replacements are found, replace {} for backard compatibility
-        if '{}' not in cmd:
-            cmd += ' {}'
-        return cmd.replace('{}', compat_shlex_quote(
-            info.get('filepath') or info['_filename']))
+        filepath = info.get('filepath', info.get('_filename'))
+        # If video, and no replacements are found, replace {} for backard compatibility
+        if filepath:
+            if '{}' not in cmd:
+                cmd += ' {}'
+            cmd = cmd.replace('{}', compat_shlex_quote(filepath))
+        return cmd
 
     def run(self, info):
         for tmpl in self.exec_cmd:
index ae23ec2a364ae7b1ee9fad4d7d0561ebf5d4d75e..f56129aa5fd92c611e5d63325ad945b823a957c0 100644 (file)
@@ -3036,6 +3036,9 @@ def q(qid):
     return q
 
 
+POSTPROCESS_WHEN = {'pre_process', 'before_dl', 'after_move', 'post_process'}
+
+
 DEFAULT_OUTTMPL = {
     'default': '%(title)s [%(id)s].%(ext)s',
     'chapter': '%(title)s - %(section_number)03d %(section_title)s [%(id)s].%(ext)s',