diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index b1bc05a80..2d37530bc 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1822,14 +1822,16 @@ class YoutubeDL(object): format_modified = mobj.group('mod') is not None format_fallback = not format_type and not format_modified # for b, w - filter_f = ( + _filter_f = ( (lambda f: f.get('%scodec' % format_type) != 'none') if format_type and format_modified # bv*, ba*, wv*, wa* else (lambda f: f.get('%scodec' % not_format_type) == 'none') if format_type # bv, ba, wv, wa else (lambda f: f.get('vcodec') != 'none' and f.get('acodec') != 'none') if not format_modified # b, w - else None) # b*, w* + else lambda f: True) # b*, w* + filter_f = lambda f: _filter_f(f) and ( + f.get('vcodec') != 'none' or f.get('acodec') != 'none') else: filter_f = ((lambda f: f.get('ext') == format_spec) if format_spec in ['mp4', 'flv', 'webm', '3gp', 'm4a', 'mp3', 'ogg', 'aac', 'wav'] # extension @@ -2928,6 +2930,8 @@ class YoutubeDL(object): @staticmethod def format_resolution(format, default='unknown'): if format.get('vcodec') == 'none': + if format.get('acodec') == 'none': + return 'images' return 'audio only' if format.get('resolution') is not None: return format['resolution'] diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 3a345b2cd..3603924e4 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -1473,7 +1473,7 @@ class InfoExtractor(object): class FormatSort: regex = r' *((?P\+)?(?P[a-zA-Z0-9_]+)((?P[~:])(?P.*?))?)? *$' - default = ('hidden', 'hasvid', 'ie_pref', 'lang', 'quality', + default = ('hidden', 'aud_or_vid', 'hasvid', 'ie_pref', 'lang', 'quality', 'res', 'fps', 'codec:vp9.2', 'size', 'br', 'asr', 'proto', 'ext', 'hasaud', 'source', 'format_id') # These must not be aliases ytdl_default = ('hasaud', 'quality', 'tbr', 'filesize', 'vbr', @@ -1494,6 +1494,9 @@ class InfoExtractor(object): 'order': ('m4a', 'aac', 'mp3', 'ogg', 'opus', 'webm', '', 'none'), 'order_free': ('opus', 'ogg', 'webm', 'm4a', 'mp3', 'aac', '', 'none')}, 'hidden': {'visible': False, 'forced': True, 'type': 'extractor', 'max': -1000}, + 'aud_or_vid': {'visible': False, 'forced': True, 'type': 'multiple', 'default': 1, + 'field': ('vcodec', 'acodec'), + 'function': lambda it: int(any(v != 'none' for v in it))}, 'ie_pref': {'priority': True, 'type': 'extractor'}, 'hasvid': {'priority': True, 'field': 'vcodec', 'type': 'boolean', 'not_in_list': ('none',)}, 'hasaud': {'field': 'acodec', 'type': 'boolean', 'not_in_list': ('none',)}, @@ -1701,9 +1704,7 @@ class InfoExtractor(object): def wrapped_function(values): values = tuple(filter(lambda x: x is not None, values)) - return (self._get_field_setting(field, 'function')(*values) if len(values) > 1 - else values[0] if values - else None) + return self._get_field_setting(field, 'function')(values) if values else None value = wrapped_function((get_value(f) for f in actual_fields)) else: @@ -1719,7 +1720,7 @@ class InfoExtractor(object): if not format.get('ext') and 'url' in format: format['ext'] = determine_ext(format['url']) if format.get('vcodec') == 'none': - format['audio_ext'] = format['ext'] + format['audio_ext'] = format['ext'] if format.get('acodec') != 'none' else 'none' format['video_ext'] = 'none' else: format['video_ext'] = format['ext'] diff --git a/yt_dlp/postprocessor/common.py b/yt_dlp/postprocessor/common.py index b6d06f33f..9bd025ff6 100644 --- a/yt_dlp/postprocessor/common.py +++ b/yt_dlp/postprocessor/common.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import functools import os from ..compat import compat_str @@ -67,6 +68,25 @@ class PostProcessor(object): """Sets the downloader for this PP.""" self._downloader = downloader + @staticmethod + def _restrict_to(*, video=True, audio=True, images=True): + allowed = {'video': video, 'audio': audio, 'images': images} + + def decorator(func): + @functools.wraps(func) + def wrapper(self, info): + format_type = ( + 'video' if info['vcodec'] != 'none' + else 'audio' if info['acodec'] != 'none' + else 'images') + if allowed[format_type]: + func(self, info) + else: + self.to_screen('Skipping %s' % format_type) + return [], info + return wrapper + return decorator + def run(self, information): """Run the PostProcessor. diff --git a/yt_dlp/postprocessor/embedthumbnail.py b/yt_dlp/postprocessor/embedthumbnail.py index 278a45eb6..3ac00b79a 100644 --- a/yt_dlp/postprocessor/embedthumbnail.py +++ b/yt_dlp/postprocessor/embedthumbnail.py @@ -16,6 +16,7 @@ try: except ImportError: has_mutagen = False +from .common import PostProcessor from .ffmpeg import ( FFmpegPostProcessor, FFmpegThumbnailsConvertorPP, @@ -62,6 +63,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor): def _report_run(self, exe, filename): self.to_screen('%s: Adding thumbnail to "%s"' % (exe, filename)) + @PostProcessor._restrict_to(images=False) def run(self, info): filename = info['filepath'] temp_filename = prepend_extension(filename, 'temp') diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py index 374da8c02..273f1b763 100644 --- a/yt_dlp/postprocessor/ffmpeg.py +++ b/yt_dlp/postprocessor/ffmpeg.py @@ -310,6 +310,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): except FFmpegPostProcessorError as err: raise AudioConversionError(err.msg) + @PostProcessor._restrict_to(images=False) def run(self, information): path = information['filepath'] orig_ext = information['ext'] @@ -419,6 +420,7 @@ class FFmpegVideoConvertorPP(FFmpegPostProcessor): return ['-c:v', 'libxvid', '-vtag', 'XVID'] return [] + @PostProcessor._restrict_to(images=False) def run(self, information): path, source_ext = information['filepath'], information['ext'].lower() target_ext = self._target_ext(source_ext) @@ -456,6 +458,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor): super(FFmpegEmbedSubtitlePP, self).__init__(downloader) self._already_have_subtitle = already_have_subtitle + @PostProcessor._restrict_to(images=False) def run(self, information): if information['ext'] not in ('mp4', 'webm', 'mkv'): self.to_screen('Subtitles can only be embedded in mp4, webm or mkv files') @@ -523,6 +526,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor): class FFmpegMetadataPP(FFmpegPostProcessor): + @PostProcessor._restrict_to(images=False) def run(self, info): metadata = {} @@ -625,6 +629,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor): class FFmpegMergerPP(FFmpegPostProcessor): + @PostProcessor._restrict_to(images=False) def run(self, info): filename = info['filepath'] temp_filename = prepend_extension(filename, 'temp') @@ -657,6 +662,7 @@ class FFmpegMergerPP(FFmpegPostProcessor): class FFmpegFixupStretchedPP(FFmpegPostProcessor): + @PostProcessor._restrict_to(images=False, audio=False) def run(self, info): stretched_ratio = info.get('stretched_ratio') if stretched_ratio is None or stretched_ratio == 1: @@ -676,6 +682,7 @@ class FFmpegFixupStretchedPP(FFmpegPostProcessor): class FFmpegFixupM4aPP(FFmpegPostProcessor): + @PostProcessor._restrict_to(images=False, video=False) def run(self, info): if info.get('container') != 'm4a_dash': return [], info @@ -694,6 +701,7 @@ class FFmpegFixupM4aPP(FFmpegPostProcessor): class FFmpegFixupM3u8PP(FFmpegPostProcessor): + @PostProcessor._restrict_to(images=False) def run(self, info): filename = info['filepath'] if self.get_audio_codec(filename) == 'aac': @@ -805,6 +813,7 @@ class FFmpegSplitChaptersPP(FFmpegPostProcessor): ['-ss', compat_str(chapter['start_time']), '-t', compat_str(chapter['end_time'] - chapter['start_time'])]) + @PostProcessor._restrict_to(images=False) def run(self, info): chapters = info.get('chapters') or [] if not chapters: diff --git a/yt_dlp/postprocessor/sponskrub.py b/yt_dlp/postprocessor/sponskrub.py index 51f841ac4..73b6b4a20 100644 --- a/yt_dlp/postprocessor/sponskrub.py +++ b/yt_dlp/postprocessor/sponskrub.py @@ -41,6 +41,7 @@ class SponSkrubPP(PostProcessor): return None return path + @PostProcessor._restrict_to(images=False) def run(self, information): if self.path is None: return [], information