diff --git a/README.md b/README.md index 20be114686..248b7e688c 100644 --- a/README.md +++ b/README.md @@ -439,9 +439,12 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t (Alias: --external-downloader) --downloader-args NAME:ARGS Give these arguments to the external downloader. Specify the downloader name and - the arguments separated by a colon ":". You - can use this option multiple times to give - different arguments to different downloaders + the arguments separated by a colon ":". For + ffmpeg, arguments can be passed to + different positions using the same syntax + as --postprocessor-args. You can use this + option multiple times to give different + arguments to different downloaders (Alias: --external-downloader-args) ## Filesystem Options: diff --git a/yt_dlp/downloader/external.py b/yt_dlp/downloader/external.py index f91e105990..be5bd6dd85 100644 --- a/yt_dlp/downloader/external.py +++ b/yt_dlp/downloader/external.py @@ -22,7 +22,7 @@ from ..utils import ( cli_option, cli_valueless_option, cli_bool_option, - cli_configuration_args, + _configuration_args, encodeFilename, encodeArgument, handle_youtubedl_headers, @@ -111,11 +111,10 @@ class ExternalFD(FileDownloader): def _valueless_option(self, command_option, param, expected_value=True): return cli_valueless_option(self.params, command_option, param, expected_value) - def _configuration_args(self, *args, **kwargs): - return cli_configuration_args( - self.params.get('external_downloader_args'), - [self.get_basename(), 'default'], - *args, **kwargs) + def _configuration_args(self, keys=None, *args, **kwargs): + return _configuration_args( + self.get_basename(), self.params.get('external_downloader_args'), self.get_basename(), + keys, *args, **kwargs) def _call_downloader(self, tmpfilename, info_dict): """ Either overwrite this or implement _make_cmd """ @@ -459,10 +458,10 @@ class FFmpegFD(ExternalFD): elif isinstance(conn, compat_str): args += ['-rtmp_conn', conn] - for url in urls: - args += ['-i', url] + for i, url in enumerate(urls): + args += self._configuration_args((f'_i{i + 1}', '_i')) + ['-i', url] - args += self._configuration_args() + ['-c', 'copy'] + args += ['-c', 'copy'] if info_dict.get('requested_formats'): for (i, fmt) in enumerate(info_dict['requested_formats']): if fmt.get('acodec') != 'none': @@ -491,9 +490,10 @@ class FFmpegFD(ExternalFD): else: args += ['-f', EXT_TO_OUT_FORMATS.get(ext, ext)] + args += self._configuration_args((f'_o1', '_o', '')) + args = [encodeArgument(opt) for opt in args] args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True)) - self._debug_cmd(args) proc = subprocess.Popen(args, stdin=subprocess.PIPE, env=env) diff --git a/yt_dlp/options.py b/yt_dlp/options.py index 56ab001bef..6bad37d198 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -742,12 +742,13 @@ def parseOpts(overrideArguments=None): metavar='NAME:ARGS', dest='external_downloader_args', default={}, type='str', action='callback', callback=_dict_from_options_callback, callback_kwargs={ - 'allowed_keys': '|'.join(list_external_downloaders()), + 'allowed_keys': r'ffmpeg_[io]\d*|%s' % '|'.join(list_external_downloaders()), 'default_key': 'default', 'process': compat_shlex_split }, help=( 'Give these arguments to the external downloader. ' 'Specify the downloader name and the arguments separated by a colon ":". ' + 'For ffmpeg, arguments can be passed to different positions using the same syntax as --postprocessor-args. ' 'You can use this option multiple times to give different arguments to different downloaders ' '(Alias: --external-downloader-args)')) diff --git a/yt_dlp/postprocessor/common.py b/yt_dlp/postprocessor/common.py index e8577c9ee4..aa4715b062 100644 --- a/yt_dlp/postprocessor/common.py +++ b/yt_dlp/postprocessor/common.py @@ -5,7 +5,7 @@ import os from ..compat import compat_str from ..utils import ( - cli_configuration_args, + _configuration_args, encodeFilename, PostProcessingError, ) @@ -110,18 +110,9 @@ class PostProcessor(object): except Exception: self.report_warning(errnote) - def _configuration_args(self, exe, keys=None, default=[], use_compat=True): - pp_key = self.pp_key().lower() - exe = exe.lower() - root_key = exe if pp_key == exe else '%s+%s' % (pp_key, exe) - keys = ['%s%s' % (root_key, k) for k in (keys or [''])] - if root_key in keys: - keys += [root_key] + ([] if pp_key == exe else [(self.pp_key(), exe)]) + ['default'] - else: - use_compat = False - return cli_configuration_args( - self.get_param('postprocessor_args'), - keys, default, use_compat) + def _configuration_args(self, exe, *args, **kwargs): + return _configuration_args( + self.pp_key(), self.get_param('postprocessor_args'), exe, *args, **kwargs) class AudioConversionError(PostProcessingError): diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index 61878b8d7c..04c280ba39 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -4987,6 +4987,18 @@ def cli_configuration_args(argdict, keys, default=[], use_compat=True): return [arg for args in arg_list for arg in args] return default +def _configuration_args(main_key, argdict, exe, keys=None, default=[], use_compat=True): + main_key, exe = main_key.lower(), exe.lower() + root_key = exe if main_key == exe else f'{main_key}+{exe}' + keys = [f'{root_key}{k}' for k in (keys or [''])] + if root_key in keys: + if main_key != exe: + keys.append((main_key, exe)) + keys.append('default') + else: + use_compat = False + return cli_configuration_args(argdict, keys, default, use_compat) + class ISO639Utils(object): # See http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt