From a906d47bd292183066fef93cfabb275772154495 Mon Sep 17 00:00:00 2001 From: John Hawkinson Date: Tue, 14 Nov 2017 01:57:04 -0500 Subject: [PATCH 1/2] Support --output-format (and -O) Allow specifying an output format when using an external downloader, either through --output-format or -O, in preference to the extractor's default. This is particularly helpful as -O mkv, to allow Matroska output from ffmpeg in cases where .mp4 isn't a great answer, such as while trying to play during the download. --- youtube_dl/YoutubeDL.py | 5 +++++ youtube_dl/__init__.py | 1 + youtube_dl/downloader/common.py | 1 + youtube_dl/downloader/external.py | 14 +++++++++++--- youtube_dl/options.py | 4 ++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 68721e9ab..1d92585ea 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -253,6 +253,7 @@ class YoutubeDL(object): Progress hooks are guaranteed to be called at least once (with status "finished") if the download is successful. merge_output_format: Extension to use when merging formats. + outputformat: Extension and format to use when using an external downloader fixup: Automatically correct known faults of the file. One of: - "never": do nothing @@ -635,6 +636,8 @@ class YoutubeDL(object): template_dict['resolution'] = '%sp' % template_dict['height'] elif template_dict.get('width'): template_dict['resolution'] = '%dx?' % template_dict['width'] + if self.params.get('outputformat'): + template_dict['ext'] = self.params.get('outputformat') sanitize = lambda k, v: sanitize_filename( compat_str(v), @@ -1864,6 +1867,8 @@ class YoutubeDL(object): if filename_real_ext == info_dict['ext'] else filename) requested_formats = info_dict['requested_formats'] + if self.params.get('outputformat'): + info_dict['ext'] = self.params.get('outputformat') if self.params.get('merge_output_format') is None and not compatible_formats(requested_formats): info_dict['ext'] = 'mkv' self.report_warning( diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index ba684a075..467824104 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -417,6 +417,7 @@ def _real_main(argv=None): 'ffmpeg_location': opts.ffmpeg_location, 'hls_prefer_native': opts.hls_prefer_native, 'hls_use_mpegts': opts.hls_use_mpegts, + 'outputformat': opts.outputformat, 'external_downloader_args': external_downloader_args, 'postprocessor_args': postprocessor_args, 'cn_verification_proxy': opts.cn_verification_proxy, diff --git a/youtube_dl/downloader/common.py b/youtube_dl/downloader/common.py index 75b8166c5..978509001 100644 --- a/youtube_dl/downloader/common.py +++ b/youtube_dl/downloader/common.py @@ -49,6 +49,7 @@ class FileDownloader(object): external_downloader_args: A list of additional command-line arguments for the external downloader. hls_use_mpegts: Use the mpegts container for HLS videos. + outputformat: Output format for downloader to use Subclasses of this one must re-define the real_download method. """ diff --git a/youtube_dl/downloader/external.py b/youtube_dl/downloader/external.py index db018fa89..115bfa6d0 100644 --- a/youtube_dl/downloader/external.py +++ b/youtube_dl/downloader/external.py @@ -292,18 +292,26 @@ class FFmpegFD(ExternalFD): if self.params.get('test', False): args += ['-fs', compat_str(self._TEST_FILE_SIZE)] - if protocol in ('m3u8', 'm3u8_native'): + if self.params.get('outputformat'): + args += ['-f', EXT_TO_OUT_FORMATS.get(self.params.get('outputformat'), + self.params.get('outputformat'))] + elif protocol in ('m3u8', 'm3u8_native'): if self.params.get('hls_use_mpegts', False) or tmpfilename == '-': args += ['-f', 'mpegts'] else: args += ['-f', 'mp4'] - if (ffpp.basename == 'ffmpeg' and is_outdated_version(ffpp._versions['ffmpeg'], '3.2', False)) and (not info_dict.get('acodec') or info_dict['acodec'].split('.')[0] in ('aac', 'mp4a')): - args += ['-bsf:a', 'aac_adtstoasc'] elif protocol == 'rtmp': args += ['-f', 'flv'] else: args += ['-f', EXT_TO_OUT_FORMATS.get(info_dict['ext'], info_dict['ext'])] + if (protocol in ('m3u8', 'm3u8_native') and + (ffpp.basename == 'ffmpeg' and + is_outdated_version(ffpp._versions['ffmpeg'], '3.2', False)) and + (not info_dict.get('acodec') or + info_dict['acodec'].split('.')[0] in ('aac', 'mp4a'))): + args += ['-bsf:a', 'aac_adtstoasc'] + args = [encodeArgument(opt) for opt in args] args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True)) diff --git a/youtube_dl/options.py b/youtube_dl/options.py index 4c0455044..9f1df851e 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -516,6 +516,10 @@ def parseOpts(overrideArguments=None): '--external-downloader-args', dest='external_downloader_args', metavar='ARGS', help='Give these arguments to the external downloader') + downloader.add_option( + '-O', '--output-format', + metavar='FORMAT', dest='outputformat', default='', + help='Ask the downloader to encode the specified video format') workarounds = optparse.OptionGroup(parser, 'Workarounds') workarounds.add_option( From a4e49f14ef780fb13bcfc5e9002d342803306891 Mon Sep 17 00:00:00 2001 From: John Hawkinson Date: Wed, 15 Nov 2017 00:17:47 -0500 Subject: [PATCH 2/2] Revise per @dstftw: 1. Option name should explicitly reflect a dependency on ffmpeg in its name. 2. Description should explicitly emphasize it will only work with ffmpeg downloader. 3. -O should be removed. 4. It should be ignored completely when ffmpeg is not used as external downloader. So therefore Various: . outputformat -> ffmpeg_format (and --ffmpeg-format cmdline) YoutubeDL.py: . xxx: prepare_filename() is run before we know the downloader. How do we address this? downloader/external.py: . Use temp. var force_mpegts to avoid code duplication . Don't add -bsf:a if force_mpegts is True (oops! error from last rev) . re-indent complex if --- youtube_dl/YoutubeDL.py | 13 +++++++------ youtube_dl/__init__.py | 2 +- youtube_dl/downloader/common.py | 2 +- youtube_dl/downloader/external.py | 20 +++++++++++--------- youtube_dl/options.py | 6 +++--- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 1d92585ea..304e6aa3b 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -253,7 +253,6 @@ class YoutubeDL(object): Progress hooks are guaranteed to be called at least once (with status "finished") if the download is successful. merge_output_format: Extension to use when merging formats. - outputformat: Extension and format to use when using an external downloader fixup: Automatically correct known faults of the file. One of: - "never": do nothing @@ -299,7 +298,7 @@ class YoutubeDL(object): the downloader (see youtube_dl/downloader/common.py): nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test, noresizebuffer, retries, continuedl, noprogress, consoletitle, - xattr_set_filesize, external_downloader_args, hls_use_mpegts. + xattr_set_filesize, external_downloader_args, hls_use_mpegts, ffmpeg_format. The following options are used by the post processors: prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available, @@ -636,8 +635,9 @@ class YoutubeDL(object): template_dict['resolution'] = '%sp' % template_dict['height'] elif template_dict.get('width'): template_dict['resolution'] = '%dx?' % template_dict['width'] - if self.params.get('outputformat'): - template_dict['ext'] = self.params.get('outputformat') + # xxx: this can effect non-ffmpeg downloaders + if self.params.get('ffmpeg_format'): + template_dict['ext'] = self.params.get('ffmpeg_format') sanitize = lambda k, v: sanitize_filename( compat_str(v), @@ -1867,8 +1867,9 @@ class YoutubeDL(object): if filename_real_ext == info_dict['ext'] else filename) requested_formats = info_dict['requested_formats'] - if self.params.get('outputformat'): - info_dict['ext'] = self.params.get('outputformat') + # xxx: this can effect non-ffmpeg downloaders + if self.params.get('ffmpeg_format'): + info_dict['ext'] = self.params.get('ffmpeg_format') if self.params.get('merge_output_format') is None and not compatible_formats(requested_formats): info_dict['ext'] = 'mkv' self.report_warning( diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 467824104..d26e0ab4f 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -417,7 +417,7 @@ def _real_main(argv=None): 'ffmpeg_location': opts.ffmpeg_location, 'hls_prefer_native': opts.hls_prefer_native, 'hls_use_mpegts': opts.hls_use_mpegts, - 'outputformat': opts.outputformat, + 'ffmpeg_format': opts.ffmpeg_format, 'external_downloader_args': external_downloader_args, 'postprocessor_args': postprocessor_args, 'cn_verification_proxy': opts.cn_verification_proxy, diff --git a/youtube_dl/downloader/common.py b/youtube_dl/downloader/common.py index 978509001..08cc92565 100644 --- a/youtube_dl/downloader/common.py +++ b/youtube_dl/downloader/common.py @@ -49,7 +49,7 @@ class FileDownloader(object): external_downloader_args: A list of additional command-line arguments for the external downloader. hls_use_mpegts: Use the mpegts container for HLS videos. - outputformat: Output format for downloader to use + ffmpeg_format: Output format for the ffmpeg downloader to use Subclasses of this one must re-define the real_download method. """ diff --git a/youtube_dl/downloader/external.py b/youtube_dl/downloader/external.py index 115bfa6d0..19bc2fa45 100644 --- a/youtube_dl/downloader/external.py +++ b/youtube_dl/downloader/external.py @@ -292,11 +292,12 @@ class FFmpegFD(ExternalFD): if self.params.get('test', False): args += ['-fs', compat_str(self._TEST_FILE_SIZE)] - if self.params.get('outputformat'): - args += ['-f', EXT_TO_OUT_FORMATS.get(self.params.get('outputformat'), - self.params.get('outputformat'))] + force_mpegts = self.params.get('hls_use_mpegts', False) or tmpfilename == '-' + if self.params.get('ffmpeg_format'): + args += ['-f', EXT_TO_OUT_FORMATS.get(self.params.get('ffmpeg_format'), + self.params.get('ffmpeg_format'))] elif protocol in ('m3u8', 'm3u8_native'): - if self.params.get('hls_use_mpegts', False) or tmpfilename == '-': + if force_mpegts: args += ['-f', 'mpegts'] else: args += ['-f', 'mp4'] @@ -306,11 +307,12 @@ class FFmpegFD(ExternalFD): args += ['-f', EXT_TO_OUT_FORMATS.get(info_dict['ext'], info_dict['ext'])] if (protocol in ('m3u8', 'm3u8_native') and - (ffpp.basename == 'ffmpeg' and - is_outdated_version(ffpp._versions['ffmpeg'], '3.2', False)) and - (not info_dict.get('acodec') or - info_dict['acodec'].split('.')[0] in ('aac', 'mp4a'))): - args += ['-bsf:a', 'aac_adtstoasc'] + not force_mpegts and + (ffpp.basename == 'ffmpeg' and + is_outdated_version(ffpp._versions['ffmpeg'], '3.2', False)) and + (not info_dict.get('acodec') or + info_dict['acodec'].split('.')[0] in ('aac', 'mp4a'))): + args += ['-bsf:a', 'aac_adtstoasc'] args = [encodeArgument(opt) for opt in args] args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True)) diff --git a/youtube_dl/options.py b/youtube_dl/options.py index 9f1df851e..805e9ba0f 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -517,9 +517,9 @@ def parseOpts(overrideArguments=None): dest='external_downloader_args', metavar='ARGS', help='Give these arguments to the external downloader') downloader.add_option( - '-O', '--output-format', - metavar='FORMAT', dest='outputformat', default='', - help='Ask the downloader to encode the specified video format') + '--ffmpeg-format', + metavar='FORMAT', dest='ffmpeg_format', default='', + help='Ask the ffmpeg downloader to encode the specified video format') workarounds = optparse.OptionGroup(parser, 'Workarounds') workarounds.add_option(