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:
parent
5c610515c9
commit
43820c0370
9 changed files with 89 additions and 44 deletions
24
README.md
24
README.md
|
@ -551,18 +551,24 @@ Then simply type this
|
||||||
re-encoding is necessary (currently
|
re-encoding is necessary (currently
|
||||||
supported: mp4|flv|ogg|webm|mkv|avi)
|
supported: mp4|flv|ogg|webm|mkv|avi)
|
||||||
--postprocessor-args NAME:ARGS Give these arguments to the postprocessors.
|
--postprocessor-args NAME:ARGS Give these arguments to the postprocessors.
|
||||||
Specify the postprocessor name and the
|
Specify the postprocessor/executable name
|
||||||
arguments separated by a colon ':' to give
|
and the arguments separated by a colon ':'
|
||||||
the argument to only the specified
|
to give the argument to only the specified
|
||||||
postprocessor. Supported names are
|
postprocessor/executable. Supported
|
||||||
|
postprocessors are: SponSkrub,
|
||||||
ExtractAudio, VideoRemuxer, VideoConvertor,
|
ExtractAudio, VideoRemuxer, VideoConvertor,
|
||||||
EmbedSubtitle, Metadata, Merger,
|
EmbedSubtitle, Metadata, Merger,
|
||||||
FixupStretched, FixupM4a, FixupM3u8,
|
FixupStretched, FixupM4a, FixupM3u8,
|
||||||
SubtitlesConvertor, EmbedThumbnail,
|
SubtitlesConvertor and EmbedThumbnail. The
|
||||||
XAttrMetadata, SponSkrub and Default. You
|
supported executables are: SponSkrub,
|
||||||
can use this option multiple times to give
|
FFmpeg, FFprobe, avconf, avprobe and
|
||||||
different arguments to different
|
AtomicParsley. You can use this option
|
||||||
postprocessors
|
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
|
-k, --keep-video Keep the intermediate video file on disk
|
||||||
after post-processing
|
after post-processing
|
||||||
--no-keep-video Delete the intermediate video file after
|
--no-keep-video Delete the intermediate video file after
|
||||||
|
|
|
@ -343,10 +343,11 @@ class YoutubeDL(object):
|
||||||
otherwise prefer ffmpeg.
|
otherwise prefer ffmpeg.
|
||||||
ffmpeg_location: Location of the ffmpeg/avconv binary; either the path
|
ffmpeg_location: Location of the ffmpeg/avconv binary; either the path
|
||||||
to the binary or its containing directory.
|
to the binary or its containing directory.
|
||||||
postprocessor_args: A dictionary of postprocessor names (in lower case) and a list
|
postprocessor_args: A dictionary of postprocessor/executable keys (in lower case)
|
||||||
of additional command-line arguments for the postprocessor.
|
and a list of additional command-line arguments for the
|
||||||
Use 'default' as the name for arguments to passed to all PP.
|
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:
|
The following options are used by the Youtube extractor:
|
||||||
youtube_include_dash_manifest: If True (default), DASH manifests and related
|
youtube_include_dash_manifest: If True (default), DASH manifests and related
|
||||||
data will be downloaded and processed by extractor.
|
data will be downloaded and processed by extractor.
|
||||||
|
|
|
@ -8,8 +8,8 @@ __license__ = 'Public Domain'
|
||||||
import codecs
|
import codecs
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@ -340,18 +340,18 @@ def _real_main(argv=None):
|
||||||
postprocessor_args = {}
|
postprocessor_args = {}
|
||||||
if opts.postprocessor_args is not None:
|
if opts.postprocessor_args is not None:
|
||||||
for string in opts.postprocessor_args:
|
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 mobj is None:
|
||||||
if 'sponskrub' not in postprocessor_args: # for backward compatibility
|
if 'sponskrub' not in postprocessor_args: # for backward compatibility
|
||||||
postprocessor_args['sponskrub'] = []
|
postprocessor_args['sponskrub'] = []
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
write_string('[debug] Adding postprocessor args from command line option sponskrub:\n')
|
write_string('[debug] Adding postprocessor args from command line option sponskrub: \n')
|
||||||
pp_name, pp_args = 'default', string
|
pp_key, pp_args = 'default', string
|
||||||
else:
|
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:
|
if opts.verbose:
|
||||||
write_string('[debug] Adding postprocessor args from command line option %s:%s\n' % (pp_name, pp_args))
|
write_string('[debug] Adding postprocessor args from command line option %s: %s\n' % (pp_key, pp_args))
|
||||||
postprocessor_args[pp_name] = compat_shlex_split(pp_args)
|
postprocessor_args[pp_key] = compat_shlex_split(pp_args)
|
||||||
|
|
||||||
match_filter = (
|
match_filter = (
|
||||||
None if opts.match_filter is None
|
None if opts.match_filter is None
|
||||||
|
|
|
@ -975,15 +975,18 @@ def parseOpts(overrideArguments=None):
|
||||||
metavar='FORMAT', dest='recodevideo', default=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)')
|
help='Re-encode the video into another format if re-encoding is necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)')
|
||||||
postproc.add_option(
|
postproc.add_option(
|
||||||
'--postprocessor-args', metavar='NAME:ARGS',
|
'--postprocessor-args', '--ppa', metavar='NAME:ARGS',
|
||||||
dest='postprocessor_args', action='append',
|
dest='postprocessor_args', action='append',
|
||||||
help=(
|
help=(
|
||||||
'Give these arguments to the postprocessors. '
|
'Give these arguments to the postprocessors. '
|
||||||
"Specify the postprocessor name and the arguments separated by a colon ':' "
|
'Specify the postprocessor/executable name and the arguments separated by a colon ":" '
|
||||||
'to give the argument to only the specified postprocessor. Supported names are '
|
'to give the argument to only the specified postprocessor/executable. Supported postprocessors are: '
|
||||||
'ExtractAudio, VideoRemuxer, VideoConvertor, EmbedSubtitle, Metadata, Merger, FixupStretched, '
|
'SponSkrub, ExtractAudio, VideoRemuxer, VideoConvertor, EmbedSubtitle, Metadata, Merger, '
|
||||||
'FixupM4a, FixupM3u8, SubtitlesConvertor, EmbedThumbnail, XAttrMetadata, SponSkrub and Default. '
|
'FixupStretched, FixupM4a, FixupM3u8, SubtitlesConvertor and EmbedThumbnail. '
|
||||||
'You can use this option multiple times to give different arguments to different postprocessors'))
|
'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(
|
postproc.add_option(
|
||||||
'-k', '--keep-video',
|
'-k', '--keep-video',
|
||||||
action='store_true', dest='keepvideo', default=False,
|
action='store_true', dest='keepvideo', default=False,
|
||||||
|
|
|
@ -2,9 +2,9 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
PostProcessingError,
|
PostProcessingError,
|
||||||
cli_configuration_args,
|
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,8 +33,12 @@ class PostProcessor(object):
|
||||||
|
|
||||||
def __init__(self, downloader=None):
|
def __init__(self, downloader=None):
|
||||||
self._downloader = downloader
|
self._downloader = downloader
|
||||||
if not hasattr(self, 'PP_NAME'):
|
self.PP_NAME = self.pp_key()
|
||||||
self.PP_NAME = self.__class__.__name__[:-2]
|
|
||||||
|
@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):
|
def to_screen(self, text, *args, **kwargs):
|
||||||
if self._downloader:
|
if self._downloader:
|
||||||
|
@ -84,11 +88,40 @@ class PostProcessor(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
self.report_warning(errnote)
|
self.report_warning(errnote)
|
||||||
|
|
||||||
def _configuration_args(self, default=[]):
|
def _configuration_args(self, default=[], exe=None):
|
||||||
args = self.get_param('postprocessor_args', {})
|
args = self.get_param('postprocessor_args', {})
|
||||||
if isinstance(args, list): # for backward compatibility
|
pp_key = self.pp_key().lower()
|
||||||
args = {'default': args, 'sponskrub': []}
|
|
||||||
return cli_configuration_args(args, self.PP_NAME.lower(), args.get('default', []))
|
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):
|
class AudioConversionError(PostProcessingError):
|
||||||
|
|
|
@ -24,7 +24,6 @@ class EmbedThumbnailPPError(PostProcessingError):
|
||||||
|
|
||||||
|
|
||||||
class EmbedThumbnailPP(FFmpegPostProcessor):
|
class EmbedThumbnailPP(FFmpegPostProcessor):
|
||||||
PP_NAME = 'EmbedThumbnail'
|
|
||||||
|
|
||||||
def __init__(self, downloader=None, already_have_thumbnail=False):
|
def __init__(self, downloader=None, already_have_thumbnail=False):
|
||||||
super(EmbedThumbnailPP, self).__init__(downloader)
|
super(EmbedThumbnailPP, self).__init__(downloader)
|
||||||
|
@ -102,6 +101,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
|
||||||
encodeFilename(thumbnail_filename, True),
|
encodeFilename(thumbnail_filename, True),
|
||||||
encodeArgument('-o'),
|
encodeArgument('-o'),
|
||||||
encodeFilename(temp_filename, True)]
|
encodeFilename(temp_filename, True)]
|
||||||
|
cmd += [encodeArgument(o) for o in self._configuration_args(exe='AtomicParsley')]
|
||||||
|
|
||||||
self.to_screen('Adding thumbnail to "%s"' % filename)
|
self.to_screen('Adding thumbnail to "%s"' % filename)
|
||||||
self.write_debug('AtomicParsley command line: %s' % shell_quote(cmd))
|
self.write_debug('AtomicParsley command line: %s' % shell_quote(cmd))
|
||||||
|
|
|
@ -11,12 +11,15 @@ from ..utils import (
|
||||||
|
|
||||||
|
|
||||||
class ExecAfterDownloadPP(PostProcessor):
|
class ExecAfterDownloadPP(PostProcessor):
|
||||||
PP_NAME = 'Exec'
|
|
||||||
|
|
||||||
def __init__(self, downloader, exec_cmd):
|
def __init__(self, downloader, exec_cmd):
|
||||||
super(ExecAfterDownloadPP, self).__init__(downloader)
|
super(ExecAfterDownloadPP, self).__init__(downloader)
|
||||||
self.exec_cmd = exec_cmd
|
self.exec_cmd = exec_cmd
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pp_key(cls):
|
||||||
|
return 'Exec'
|
||||||
|
|
||||||
def run(self, information):
|
def run(self, information):
|
||||||
cmd = self.exec_cmd
|
cmd = self.exec_cmd
|
||||||
if '{}' not in cmd:
|
if '{}' not in cmd:
|
||||||
|
|
|
@ -54,8 +54,6 @@ class FFmpegPostProcessorError(PostProcessingError):
|
||||||
|
|
||||||
class FFmpegPostProcessor(PostProcessor):
|
class FFmpegPostProcessor(PostProcessor):
|
||||||
def __init__(self, downloader=None):
|
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)
|
PostProcessor.__init__(self, downloader)
|
||||||
self._determine_executables()
|
self._determine_executables()
|
||||||
|
|
||||||
|
@ -209,7 +207,7 @@ class FFmpegPostProcessor(PostProcessor):
|
||||||
oldest_mtime = min(
|
oldest_mtime = min(
|
||||||
os.stat(encodeFilename(path)).st_mtime for path in input_paths)
|
os.stat(encodeFilename(path)).st_mtime for path in input_paths)
|
||||||
|
|
||||||
opts += self._configuration_args()
|
opts += self._configuration_args(exe=self.basename)
|
||||||
|
|
||||||
files_cmd = []
|
files_cmd = []
|
||||||
for path in input_paths:
|
for path in input_paths:
|
||||||
|
|
|
@ -9,6 +9,7 @@ from ..utils import (
|
||||||
encodeArgument,
|
encodeArgument,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
shell_quote,
|
shell_quote,
|
||||||
|
str_or_none,
|
||||||
PostProcessingError,
|
PostProcessingError,
|
||||||
prepend_extension,
|
prepend_extension,
|
||||||
)
|
)
|
||||||
|
@ -16,15 +17,13 @@ from ..utils import (
|
||||||
|
|
||||||
class SponSkrubPP(PostProcessor):
|
class SponSkrubPP(PostProcessor):
|
||||||
_temp_ext = 'spons'
|
_temp_ext = 'spons'
|
||||||
_def_args = []
|
|
||||||
_exe_name = 'sponskrub'
|
_exe_name = 'sponskrub'
|
||||||
|
|
||||||
def __init__(self, downloader, path='', args=None, ignoreerror=False, cut=False, force=False):
|
def __init__(self, downloader, path='', args=None, ignoreerror=False, cut=False, force=False):
|
||||||
PostProcessor.__init__(self, downloader)
|
PostProcessor.__init__(self, downloader)
|
||||||
self.force = force
|
self.force = force
|
||||||
self.cutout = cut
|
self.cutout = cut
|
||||||
self.args = ['-chapter'] if not cut else []
|
self.args = str_or_none(args) or '' # For backward compatibility
|
||||||
self.args += self._configuration_args(self._def_args) if args is None else compat_shlex_split(args)
|
|
||||||
self.path = self.get_exe(path)
|
self.path = self.get_exe(path)
|
||||||
|
|
||||||
if not ignoreerror and self.path is None:
|
if not ignoreerror and self.path is None:
|
||||||
|
@ -64,9 +63,11 @@ class SponSkrubPP(PostProcessor):
|
||||||
if os.path.exists(encodeFilename(temp_filename)):
|
if os.path.exists(encodeFilename(temp_filename)):
|
||||||
os.remove(encodeFilename(temp_filename))
|
os.remove(encodeFilename(temp_filename))
|
||||||
|
|
||||||
cmd = [self.path]
|
cmd = [self.path]
|
||||||
if self.args:
|
if not self.cutout:
|
||||||
cmd += self.args
|
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 += ['--', information['id'], filename, temp_filename]
|
||||||
cmd = [encodeArgument(i) for i in cmd]
|
cmd = [encodeArgument(i) for i in cmd]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue