From 43820c0370acaf8306880f235364535c1c92c157 Mon Sep 17 00:00:00 2001 From: pukkandan Date: Wed, 20 Jan 2021 21:37:40 +0530 Subject: [PATCH] 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 --- README.md | 24 ++++++---- youtube_dlc/YoutubeDL.py | 9 ++-- youtube_dlc/__init__.py | 14 +++--- youtube_dlc/options.py | 15 +++--- youtube_dlc/postprocessor/common.py | 47 ++++++++++++++++--- youtube_dlc/postprocessor/embedthumbnail.py | 2 +- .../postprocessor/execafterdownload.py | 5 +- youtube_dlc/postprocessor/ffmpeg.py | 4 +- youtube_dlc/postprocessor/sponskrub.py | 13 ++--- 9 files changed, 89 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index f0fe6e70e..7f8f09f14 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/youtube_dlc/YoutubeDL.py b/youtube_dlc/YoutubeDL.py index 4242a5ef9..fc39cbbc9 100644 --- a/youtube_dlc/YoutubeDL.py +++ b/youtube_dlc/YoutubeDL.py @@ -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. diff --git a/youtube_dlc/__init__.py b/youtube_dlc/__init__.py index 1ba240c0d..90479c6ff 100644 --- a/youtube_dlc/__init__.py +++ b/youtube_dlc/__init__.py @@ -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\w+):(?P.*)$', string) + mobj = re.match(r'(?P\w+(?:\+\w+)?):(?P.*)$', 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 diff --git a/youtube_dlc/options.py b/youtube_dlc/options.py index 96c6faae9..f1fc9adb2 100644 --- a/youtube_dlc/options.py +++ b/youtube_dlc/options.py @@ -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, diff --git a/youtube_dlc/postprocessor/common.py b/youtube_dlc/postprocessor/common.py index 1a893d05f..a4f8ca63e 100644 --- a/youtube_dlc/postprocessor/common.py +++ b/youtube_dlc/postprocessor/common.py @@ -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): diff --git a/youtube_dlc/postprocessor/embedthumbnail.py b/youtube_dlc/postprocessor/embedthumbnail.py index b43b0d94f..98a3531f1 100644 --- a/youtube_dlc/postprocessor/embedthumbnail.py +++ b/youtube_dlc/postprocessor/embedthumbnail.py @@ -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)) diff --git a/youtube_dlc/postprocessor/execafterdownload.py b/youtube_dlc/postprocessor/execafterdownload.py index 4083cea3e..24dc64ef0 100644 --- a/youtube_dlc/postprocessor/execafterdownload.py +++ b/youtube_dlc/postprocessor/execafterdownload.py @@ -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: diff --git a/youtube_dlc/postprocessor/ffmpeg.py b/youtube_dlc/postprocessor/ffmpeg.py index 9c6065018..3079d2e72 100644 --- a/youtube_dlc/postprocessor/ffmpeg.py +++ b/youtube_dlc/postprocessor/ffmpeg.py @@ -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: diff --git a/youtube_dlc/postprocessor/sponskrub.py b/youtube_dlc/postprocessor/sponskrub.py index f039861ac..4320b7c02 100644 --- a/youtube_dlc/postprocessor/sponskrub.py +++ b/youtube_dlc/postprocessor/sponskrub.py @@ -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]