Improved passing of multiple postprocessor-args

* Added `PP+exe:args` syntax
    If `PP+exe:args` is specifically given, only it used.
    Otherwise, `PP:args` and `exe:args` are combined.
    If none of the `PP`, `exe` or `PP+exe` args are given, `default` is used
    `Default` is purposely left undocumented since it exists only for backward compatibility

* Also added proper handling of args in `EmbedThumbnail`

Related: https://github.com/ytdl-org/youtube-dl/pull/27723
This commit is contained in:
pukkandan 2021-01-20 21:37:40 +05:30
parent 5c610515c9
commit 43820c0370
9 changed files with 89 additions and 44 deletions

View file

@ -551,18 +551,24 @@ Then simply type this
re-encoding is necessary (currently
supported: mp4|flv|ogg|webm|mkv|avi)
--postprocessor-args NAME:ARGS Give these arguments to the postprocessors.
Specify the postprocessor name and the
arguments separated by a colon ':' to give
the argument to only the specified
postprocessor. Supported names are
Specify the postprocessor/executable name
and the arguments separated by a colon ':'
to give the argument to only the specified
postprocessor/executable. Supported
postprocessors are: SponSkrub,
ExtractAudio, VideoRemuxer, VideoConvertor,
EmbedSubtitle, Metadata, Merger,
FixupStretched, FixupM4a, FixupM3u8,
SubtitlesConvertor, EmbedThumbnail,
XAttrMetadata, SponSkrub and Default. You
can use this option multiple times to give
different arguments to different
postprocessors
SubtitlesConvertor and EmbedThumbnail. The
supported executables are: SponSkrub,
FFmpeg, FFprobe, avconf, avprobe and
AtomicParsley. You can use this option
multiple times to give different arguments
to different postprocessors. You can also
specify "PP+EXE:ARGS" to give the arguments
to the specified executable only when being
used by the specified postprocessor (Alias:
--ppa)
-k, --keep-video Keep the intermediate video file on disk
after post-processing
--no-keep-video Delete the intermediate video file after

View file

@ -343,10 +343,11 @@ class YoutubeDL(object):
otherwise prefer ffmpeg.
ffmpeg_location: Location of the ffmpeg/avconv binary; either the path
to the binary or its containing directory.
postprocessor_args: A dictionary of postprocessor names (in lower case) and a list
of additional command-line arguments for the postprocessor.
Use 'default' as the name for arguments to passed to all PP.
postprocessor_args: A dictionary of postprocessor/executable keys (in lower case)
and a list of additional command-line arguments for the
postprocessor/executable. The dict can also have "PP+EXE" keys
which are used when the given exe is used by the given PP.
Use 'default' as the name for arguments to passed to all PP
The following options are used by the Youtube extractor:
youtube_include_dash_manifest: If True (default), DASH manifests and related
data will be downloaded and processed by extractor.

View file

@ -8,8 +8,8 @@ __license__ = 'Public Domain'
import codecs
import io
import os
import re
import random
import re
import sys
@ -340,18 +340,18 @@ def _real_main(argv=None):
postprocessor_args = {}
if opts.postprocessor_args is not None:
for string in opts.postprocessor_args:
mobj = re.match(r'(?P<pp>\w+):(?P<args>.*)$', string)
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_name, pp_args = 'default', string
write_string('[debug] Adding postprocessor args from command line option sponskrub: \n')
pp_key, pp_args = 'default', string
else:
pp_name, pp_args = mobj.group('pp').lower(), mobj.group('args')
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_name, pp_args))
postprocessor_args[pp_name] = compat_shlex_split(pp_args)
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)
match_filter = (
None if opts.match_filter is None

View file

@ -975,15 +975,18 @@ def parseOpts(overrideArguments=None):
metavar='FORMAT', dest='recodevideo', default=None,
help='Re-encode the video into another format if re-encoding is necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)')
postproc.add_option(
'--postprocessor-args', metavar='NAME:ARGS',
'--postprocessor-args', '--ppa', metavar='NAME:ARGS',
dest='postprocessor_args', action='append',
help=(
'Give these arguments to the postprocessors. '
"Specify the postprocessor name and the arguments separated by a colon ':' "
'to give the argument to only the specified postprocessor. Supported names are '
'ExtractAudio, VideoRemuxer, VideoConvertor, EmbedSubtitle, Metadata, Merger, FixupStretched, '
'FixupM4a, FixupM3u8, SubtitlesConvertor, EmbedThumbnail, XAttrMetadata, SponSkrub and Default. '
'You can use this option multiple times to give different arguments to different postprocessors'))
'Specify the postprocessor/executable name and the arguments separated by a colon ":" '
'to give the argument to only the specified postprocessor/executable. Supported postprocessors are: '
'SponSkrub, ExtractAudio, VideoRemuxer, VideoConvertor, EmbedSubtitle, Metadata, Merger, '
'FixupStretched, FixupM4a, FixupM3u8, SubtitlesConvertor and EmbedThumbnail. '
'The supported executables are: SponSkrub, FFmpeg, FFprobe, avconf, avprobe and AtomicParsley. '
'You can use this option multiple times to give different arguments to different postprocessors. '
'You can also specify "PP+EXE:ARGS" to give the arguments to the specified executable '
'only when being used by the specified postprocessor (Alias: --ppa)'))
postproc.add_option(
'-k', '--keep-video',
action='store_true', dest='keepvideo', default=False,

View file

@ -2,9 +2,9 @@ from __future__ import unicode_literals
import os
from ..compat import compat_str
from ..utils import (
PostProcessingError,
cli_configuration_args,
encodeFilename,
)
@ -33,8 +33,12 @@ class PostProcessor(object):
def __init__(self, downloader=None):
self._downloader = downloader
if not hasattr(self, 'PP_NAME'):
self.PP_NAME = self.__class__.__name__[:-2]
self.PP_NAME = self.pp_key()
@classmethod
def pp_key(cls):
name = cls.__name__[:-2]
return compat_str(name[6:]) if name[:6].lower() == 'ffmpeg' else name
def to_screen(self, text, *args, **kwargs):
if self._downloader:
@ -84,11 +88,40 @@ class PostProcessor(object):
except Exception:
self.report_warning(errnote)
def _configuration_args(self, default=[]):
def _configuration_args(self, default=[], exe=None):
args = self.get_param('postprocessor_args', {})
if isinstance(args, list): # for backward compatibility
args = {'default': args, 'sponskrub': []}
return cli_configuration_args(args, self.PP_NAME.lower(), args.get('default', []))
pp_key = self.pp_key().lower()
if isinstance(args, (list, tuple)): # for backward compatibility
return default if pp_key == 'sponskrub' else args
if args is None:
return default
assert isinstance(args, dict)
exe_args = None
if exe is not None:
assert isinstance(exe, compat_str)
exe = exe.lower()
specific_args = args.get('%s+%s' % (pp_key, exe))
if specific_args is not None:
assert isinstance(specific_args, (list, tuple))
return specific_args
exe_args = args.get(exe)
pp_args = args.get(pp_key) if pp_key != exe else None
if pp_args is None and exe_args is None:
default = args.get('default', default)
assert isinstance(default, (list, tuple))
return default
if pp_args is None:
pp_args = []
elif exe_args is None:
exe_args = []
assert isinstance(pp_args, (list, tuple))
assert isinstance(exe_args, (list, tuple))
return pp_args + exe_args
class AudioConversionError(PostProcessingError):

View file

@ -24,7 +24,6 @@ class EmbedThumbnailPPError(PostProcessingError):
class EmbedThumbnailPP(FFmpegPostProcessor):
PP_NAME = 'EmbedThumbnail'
def __init__(self, downloader=None, already_have_thumbnail=False):
super(EmbedThumbnailPP, self).__init__(downloader)
@ -102,6 +101,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
encodeFilename(thumbnail_filename, True),
encodeArgument('-o'),
encodeFilename(temp_filename, True)]
cmd += [encodeArgument(o) for o in self._configuration_args(exe='AtomicParsley')]
self.to_screen('Adding thumbnail to "%s"' % filename)
self.write_debug('AtomicParsley command line: %s' % shell_quote(cmd))

View file

@ -11,12 +11,15 @@ from ..utils import (
class ExecAfterDownloadPP(PostProcessor):
PP_NAME = 'Exec'
def __init__(self, downloader, exec_cmd):
super(ExecAfterDownloadPP, self).__init__(downloader)
self.exec_cmd = exec_cmd
@classmethod
def pp_key(cls):
return 'Exec'
def run(self, information):
cmd = self.exec_cmd
if '{}' not in cmd:

View file

@ -54,8 +54,6 @@ class FFmpegPostProcessorError(PostProcessingError):
class FFmpegPostProcessor(PostProcessor):
def __init__(self, downloader=None):
if not hasattr(self, 'PP_NAME'):
self.PP_NAME = self.__class__.__name__[6:-2] # Remove ffmpeg from the front
PostProcessor.__init__(self, downloader)
self._determine_executables()
@ -209,7 +207,7 @@ class FFmpegPostProcessor(PostProcessor):
oldest_mtime = min(
os.stat(encodeFilename(path)).st_mtime for path in input_paths)
opts += self._configuration_args()
opts += self._configuration_args(exe=self.basename)
files_cmd = []
for path in input_paths:

View file

@ -9,6 +9,7 @@ from ..utils import (
encodeArgument,
encodeFilename,
shell_quote,
str_or_none,
PostProcessingError,
prepend_extension,
)
@ -16,15 +17,13 @@ from ..utils import (
class SponSkrubPP(PostProcessor):
_temp_ext = 'spons'
_def_args = []
_exe_name = 'sponskrub'
def __init__(self, downloader, path='', args=None, ignoreerror=False, cut=False, force=False):
PostProcessor.__init__(self, downloader)
self.force = force
self.cutout = cut
self.args = ['-chapter'] if not cut else []
self.args += self._configuration_args(self._def_args) if args is None else compat_shlex_split(args)
self.args = str_or_none(args) or '' # For backward compatibility
self.path = self.get_exe(path)
if not ignoreerror and self.path is None:
@ -64,9 +63,11 @@ class SponSkrubPP(PostProcessor):
if os.path.exists(encodeFilename(temp_filename)):
os.remove(encodeFilename(temp_filename))
cmd = [self.path]
if self.args:
cmd += self.args
cmd = [self.path]
if not self.cutout:
cmd += ['-chapter']
cmd += compat_shlex_split(self.args) # For backward compatibility
cmd += self._configuration_args(exe=self._exe_name)
cmd += ['--', information['id'], filename, temp_filename]
cmd = [encodeArgument(i) for i in cmd]