Allow --exec to be run at any post-processing stage

Deprecates `--exec-before-download`
This commit is contained in:
pukkandan 2022-01-03 16:43:54 +05:30
parent ca30f449a1
commit 1e43a6f733
No known key found for this signature in database
GPG key ID: 0F00D95A001F4698
6 changed files with 52 additions and 45 deletions

View file

@ -896,23 +896,20 @@ You can also fork the project on github and run your fork's [build workflow](.gi
--ffmpeg-location PATH Location of the ffmpeg binary; either the --ffmpeg-location PATH Location of the ffmpeg binary; either the
path to the binary or its containing path to the binary or its containing
directory directory
--exec CMD Execute a command on the file after --exec [WHEN:]CMD Execute a command, optionally prefixed with
downloading and post-processing. Same when to execute it (after_move if
syntax as the output template can be used unspecified), separated by a ":". Supported
to pass any field as arguments to the values of "WHEN" are the same as that of
command. An additional field "filepath" --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 that contains the final path of the
downloaded file is also available. If no downloaded file is also available, and if
fields are passed, %(filepath)q is appended no fields are passed, %(filepath)q is
to the end of the command. This option can appended to the end of the command. This
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
option can be used multiple times option can be used multiple times
--no-exec-before-download Remove any previously defined --no-exec Remove any previously defined --exec
--exec-before-download
--convert-subs FORMAT Convert the subtitles to another format --convert-subs FORMAT Convert the subtitles to another format
(currently supported: srt|vtt|ass|lrc) (currently supported: srt|vtt|ass|lrc)
(Alias: --convert-subtitles) (Alias: --convert-subtitles)
@ -1800,6 +1797,8 @@ While these options are redundant, they are still expected to be used due to the
#### Not recommended #### Not recommended
While these options still work, their use is not recommended since there are other alternatives to achieve the same 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-formats -f all
--all-subs --sub-langs all --write-subs --all-subs --sub-langs all --write-subs
--print-json -j --no-simulate --print-json -j --no-simulate

View file

@ -91,6 +91,7 @@ from .utils import (
PerRequestProxyHandler, PerRequestProxyHandler,
platform_name, platform_name,
Popen, Popen,
POSTPROCESS_WHEN,
PostProcessingError, PostProcessingError,
preferredencoding, preferredencoding,
prepend_extension, prepend_extension,
@ -507,7 +508,7 @@ class YoutubeDL(object):
params = None params = None
_ies = {} _ies = {}
_pps = {'pre_process': [], 'before_dl': [], 'after_move': [], 'post_process': []} _pps = {k: [] for k in POSTPROCESS_WHEN}
_printed_messages = set() _printed_messages = set()
_first_webpage_request = True _first_webpage_request = True
_download_retcode = None _download_retcode = None
@ -525,7 +526,7 @@ class YoutubeDL(object):
params = {} params = {}
self._ies = {} self._ies = {}
self._ies_instances = {} 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._printed_messages = set()
self._first_webpage_request = True self._first_webpage_request = True
self._post_hooks = [] self._post_hooks = []

View 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', '"-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', '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 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): if opts.usenetrc and (opts.username is not None or opts.password is not None):
parser.error('using .netrc conflicts with giving username/password') parser.error('using .netrc conflicts with giving username/password')
if opts.password is not None and opts.username is None: if opts.password is not None and opts.username is None:
@ -489,13 +491,6 @@ def _real_main(argv=None):
# Run this before the actual video download # Run this before the actual video download
'when': 'before_dl' '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: if opts.extractaudio:
postprocessors.append({ postprocessors.append({
'key': 'FFmpegExtractAudio', 'key': 'FFmpegExtractAudio',
@ -596,13 +591,15 @@ def _real_main(argv=None):
# XAttrMetadataPP should be run after post-processors that may change file contents # XAttrMetadataPP should be run after post-processors that may change file contents
if opts.xattrs: if opts.xattrs:
postprocessors.append({'key': 'XAttrMetadata'}) postprocessors.append({'key': 'XAttrMetadata'})
# Exec must be the last PP # Exec must be the last PP of each category
if opts.exec_cmd: 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({ postprocessors.append({
'key': 'Exec', 'key': 'Exec',
'exec_cmd': opts.exec_cmd, 'exec_cmd': exec_cmd,
# Run this only after the files have been moved to their final locations # Run this only after the files have been moved to their final locations
'when': 'after_move' 'when': when,
}) })
def report_args_compat(arg, name): def report_args_compat(arg, name):

View file

@ -16,6 +16,7 @@ from .utils import (
expand_path, expand_path,
get_executable_path, get_executable_path,
OUTTMPL_TYPES, OUTTMPL_TYPES,
POSTPROCESS_WHEN,
preferredencoding, preferredencoding,
remove_end, remove_end,
write_string, write_string,
@ -1393,29 +1394,33 @@ def parseOpts(overrideArguments=None):
dest='ffmpeg_location', dest='ffmpeg_location',
help='Location of the ffmpeg binary; either the path to the binary or its containing directory') help='Location of the ffmpeg binary; either the path to the binary or its containing directory')
postproc.add_option( postproc.add_option(
'--exec', metavar='CMD', '--exec',
action='append', dest='exec_cmd', metavar='[WHEN:]CMD', dest='exec_cmd', default={}, type='str',
help=( action='callback', callback=_dict_from_options_callback,
'Execute a command on the file after downloading and post-processing. ' 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. ' '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. ' 'After download, an additional field "filepath" that contains the final path of the downloaded file '
'If no fields are passed, %(filepath)q is appended to the end of the command. ' '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')) 'This option can be used multiple times'))
postproc.add_option( postproc.add_option(
'--no-exec', '--no-exec',
action='store_const', dest='exec_cmd', const=[], action='store_const', dest='exec_cmd', const={},
help='Remove any previously defined --exec') help='Remove any previously defined --exec')
postproc.add_option( postproc.add_option(
'--exec-before-download', metavar='CMD', '--exec-before-download', metavar='CMD',
action='append', dest='exec_before_dl_cmd', action='append', dest='exec_before_dl_cmd',
help=( help=optparse.SUPPRESS_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'))
postproc.add_option( postproc.add_option(
'--no-exec-before-download', '--no-exec-before-download',
action='store_const', dest='exec_before_dl_cmd', const=[], action='store_const', dest='exec_before_dl_cmd', const=[],
help='Remove any previously defined --exec-before-download') help=optparse.SUPPRESS_HELP)
postproc.add_option( postproc.add_option(
'--convert-subs', '--convert-sub', '--convert-subtitles', '--convert-subs', '--convert-sub', '--convert-subtitles',
metavar='FORMAT', dest='convertsubtitles', default=None, metavar='FORMAT', dest='convertsubtitles', default=None,

View file

@ -22,11 +22,13 @@ class ExecPP(PostProcessor):
if tmpl_dict: # if there are no replacements, tmpl_dict = {} if tmpl_dict: # if there are no replacements, tmpl_dict = {}
return self._downloader.escape_outtmpl(tmpl) % tmpl_dict return self._downloader.escape_outtmpl(tmpl) % tmpl_dict
# If no replacements are found, replace {} for backard compatibility filepath = info.get('filepath', info.get('_filename'))
if '{}' not in cmd: # If video, and no replacements are found, replace {} for backard compatibility
cmd += ' {}' if filepath:
return cmd.replace('{}', compat_shlex_quote( if '{}' not in cmd:
info.get('filepath') or info['_filename'])) cmd += ' {}'
cmd = cmd.replace('{}', compat_shlex_quote(filepath))
return cmd
def run(self, info): def run(self, info):
for tmpl in self.exec_cmd: for tmpl in self.exec_cmd:

View file

@ -3036,6 +3036,9 @@ def qualities(quality_ids):
return q return q
POSTPROCESS_WHEN = {'pre_process', 'before_dl', 'after_move', 'post_process'}
DEFAULT_OUTTMPL = { DEFAULT_OUTTMPL = {
'default': '%(title)s [%(id)s].%(ext)s', 'default': '%(title)s [%(id)s].%(ext)s',
'chapter': '%(title)s - %(section_number)03d %(section_title)s [%(id)s].%(ext)s', 'chapter': '%(title)s - %(section_number)03d %(section_title)s [%(id)s].%(ext)s',