]> jfr.im git - yt-dlp.git/commitdiff
[ThumbnailsConvertor] Allow conditional conversion
authorpukkandan <redacted>
Sun, 5 Jun 2022 14:28:56 +0000 (19:58 +0530)
committerpukkandan <redacted>
Sun, 5 Jun 2022 15:21:19 +0000 (20:51 +0530)
Closes #3970

README.md
yt_dlp/__init__.py
yt_dlp/options.py
yt_dlp/postprocessor/ffmpeg.py

index 87986e4c3aac3143ee8937e67628bdaa39791e5d..86f172a6403453df5991cbb52063334269df9c56 100644 (file)
--- a/README.md
+++ b/README.md
@@ -985,7 +985,9 @@ ## Post-Processing Options:
                                     (currently supported: srt, vtt, ass, lrc)
                                     (Alias: --convert-subtitles)
     --convert-thumbnails FORMAT     Convert the thumbnails to another format
-                                    (currently supported: jpg, png, webp)
+                                    (currently supported: jpg, png, webp). You
+                                    can specify multiple rules using similar
+                                    syntax as --remux-video
     --split-chapters                Split video into multiple files based on
                                     internal chapters. The "chapter:" prefix can
                                     be used with "--paths" and "--output" to set
index d0abc395ade309f503938d1788f9f181895352a5..d1b78303efe3a912ed925cb264f65ef7eaf09042 100644 (file)
@@ -215,13 +215,13 @@ def validate_minmax(min_val, max_val, min_name, max_name=None):
     # Postprocessor formats
     validate_in('audio format', opts.audioformat, ['best'] + list(FFmpegExtractAudioPP.SUPPORTED_EXTS))
     validate_in('subtitle format', opts.convertsubtitles, FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)
-    validate_in('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)
-    if opts.recodevideo is not None:
-        opts.recodevideo = opts.recodevideo.replace(' ', '')
-        validate_regex('video recode format', opts.recodevideo, FFmpegVideoConvertorPP.FORMAT_RE)
-    if opts.remuxvideo is not None:
-        opts.remuxvideo = opts.remuxvideo.replace(' ', '')
-        validate_regex('video remux format', opts.remuxvideo, FFmpegVideoRemuxerPP.FORMAT_RE)
+    for name, value, pp in (
+        ('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP),
+        ('recode video format', opts.recodevideo, FFmpegVideoConvertorPP),
+        ('remux video format', opts.remuxvideo, FFmpegVideoRemuxerPP),
+    ):
+        if value is not None:
+            validate_regex(name, value.replace(' ', ''), pp.FORMAT_RE)
     if opts.audioquality:
         opts.audioquality = opts.audioquality.strip('k').strip('K')
         # int_or_none prevents inf, nan
index 7cffcecfa3ea0bc4160722ecf0c628a5b42dc4f7..b326e885f4cf55401c9dc1ef189f43205c37e435 100644 (file)
@@ -1610,7 +1610,8 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
         metavar='FORMAT', dest='convertthumbnails', default=None,
         help=(
             'Convert the thumbnails to another format '
-            '(currently supported: %s) ' % ', '.join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)))
+            f'(currently supported: {", ".join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)}). '
+            'You can specify multiple rules using similar syntax as --remux-video'))
     postproc.add_option(
         '--split-chapters', '--split-tracks',
         dest='split_chapters', action='store_true', default=False,
index dad8b7f8f1db616ce58c98958b5daef9ac52df5d..3777703ebd16d5b31d4773d2c14a8ff42bc96eff 100644 (file)
 }
 
 
+def create_mapping_re(supported):
+    return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(supported)))
+
+
+def resolve_mapping(source, mapping):
+    """
+    Get corresponding item from a mapping string like 'A>B/C>D/E'
+    @returns    (target, error_message)
+    """
+    for pair in mapping.lower().split('/'):
+        kv = pair.split('>', 1)
+        if len(kv) == 1 or kv[0].strip() == source:
+            target = kv[-1].strip()
+            if target == source:
+                return target, f'already is in target format {source}'
+            return target, None
+    return None, f'could not find a mapping for {source}'
+
+
 class FFmpegPostProcessorError(PostProcessingError):
     pass
 
@@ -542,18 +561,12 @@ def run(self, information):
 
 class FFmpegVideoConvertorPP(FFmpegPostProcessor):
     SUPPORTED_EXTS = ('mp4', 'mkv', 'flv', 'webm', 'mov', 'avi', 'mka', 'ogg', *FFmpegExtractAudioPP.SUPPORTED_EXTS)
-    FORMAT_RE = re.compile(r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(SUPPORTED_EXTS)))
+    FORMAT_RE = create_mapping_re(SUPPORTED_EXTS)
     _ACTION = 'converting'
 
     def __init__(self, downloader=None, preferedformat=None):
         super().__init__(downloader)
-        self._preferedformats = preferedformat.lower().split('/')
-
-    def _target_ext(self, source_ext):
-        for pair in self._preferedformats:
-            kv = pair.split('>')
-            if len(kv) == 1 or kv[0].strip() == source_ext:
-                return kv[-1].strip()
+        self.mapping = preferedformat
 
     @staticmethod
     def _options(target_ext):
@@ -564,11 +577,7 @@ def _options(target_ext):
     @PostProcessor._restrict_to(images=False)
     def run(self, info):
         filename, source_ext = info['filepath'], info['ext'].lower()
-        target_ext = self._target_ext(source_ext)
-        _skip_msg = (
-            f'could not find a mapping for {source_ext}' if not target_ext
-            else f'already is in target format {source_ext}' if source_ext == target_ext
-            else None)
+        target_ext, _skip_msg = resolve_mapping(source_ext, self.mapping)
         if _skip_msg:
             self.to_screen(f'Not {self._ACTION} media file "{filename}"; {_skip_msg}')
             return [], info
@@ -1068,10 +1077,11 @@ def run(self, info):
 
 class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
     SUPPORTED_EXTS = ('jpg', 'png', 'webp')
+    FORMAT_RE = create_mapping_re(SUPPORTED_EXTS)
 
     def __init__(self, downloader=None, format=None):
         super().__init__(downloader)
-        self.format = format
+        self.mapping = format
 
     @classmethod
     def is_webp(cls, path):
@@ -1115,18 +1125,17 @@ def run(self, info):
                 continue
             has_thumbnail = True
             self.fixup_webp(info, idx)
-            _, thumbnail_ext = os.path.splitext(original_thumbnail)
-            if thumbnail_ext:
-                thumbnail_ext = thumbnail_ext[1:].lower()
+            thumbnail_ext = os.path.splitext(original_thumbnail)[1][1:].lower()
             if thumbnail_ext == 'jpeg':
                 thumbnail_ext = 'jpg'
-            if thumbnail_ext == self.format:
-                self.to_screen('Thumbnail "%s" is already in the requested format' % original_thumbnail)
+            target_ext, _skip_msg = resolve_mapping(thumbnail_ext, self.mapping)
+            if _skip_msg:
+                self.to_screen(f'Not converting thumbnail "{original_thumbnail}"; {_skip_msg}')
                 continue
-            thumbnail_dict['filepath'] = self.convert_thumbnail(original_thumbnail, self.format)
+            thumbnail_dict['filepath'] = self.convert_thumbnail(original_thumbnail, target_ext)
             files_to_delete.append(original_thumbnail)
             info['__files_to_move'][thumbnail_dict['filepath']] = replace_extension(
-                info['__files_to_move'][original_thumbnail], self.format)
+                info['__files_to_move'][original_thumbnail], target_ext)
 
         if not has_thumbnail:
             self.to_screen('There aren\'t any thumbnails to convert')