From 43820c0370acaf8306880f235364535c1c92c157 Mon Sep 17 00:00:00 2001
From: pukkandan <pukkandan@gmail.com>
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 @@ ## Post-Processing Options:
                                      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 @@
 import codecs
 import io
 import os
-import re
 import random
+import re
 import sys
 
 
@@ -340,18 +340,18 @@ def parse_retries(retries):
     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
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 _comma_separated_values_options_callback(option, opt_str, value, parser):
         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 @@
 
 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 @@ def try_utime(self, path, atime, mtime, errnote='Cannot update utime of file'):
         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 @@ def is_webp(path):
                    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 @@
 
 
 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 @@ def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
         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 @@
     encodeArgument,
     encodeFilename,
     shell_quote,
+    str_or_none,
     PostProcessingError,
     prepend_extension,
 )
@@ -16,15 +17,13 @@
 
 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 @@ def run(self, information):
         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]