From a927acb1ecf7cef80c24eca604b73d9b5e45b732 Mon Sep 17 00:00:00 2001 From: louie-github <30176969+louie-github@users.noreply.github.com> Date: Sat, 22 May 2021 02:09:48 +0800 Subject: [PATCH] [ThumbnailsConvertor] Support conversion to `png` and make it the default (#333) PNG, being a lossless format, should be a better default here compared to JPG since we won't be compressing to a lossy format and losing some of the original image data PNG is also supported for embedding in all the formats similar to JPEG Authored by: louie-github --- README.md | 2 +- yt_dlp/__init__.py | 2 +- yt_dlp/options.py | 2 +- yt_dlp/postprocessor/embedthumbnail.py | 9 ++++++--- yt_dlp/postprocessor/ffmpeg.py | 26 ++++++++++++++++---------- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b1ad22416..9d77f9735 100644 --- a/README.md +++ b/README.md @@ -752,7 +752,7 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t (currently supported: srt|ass|vtt|lrc) (Alias: --convert-subtitles) --convert-thumbnails FORMAT Convert the thumbnails to another format - (currently supported: jpg) + (currently supported: jpg, png) --split-chapters Split video into multiple files based on internal chapters. The "chapter:" prefix can be used with "--paths" and "--output" diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 00a28128d..d014d1e01 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -227,7 +227,7 @@ def _real_main(argv=None): if opts.convertsubtitles not in ('srt', 'vtt', 'ass', 'lrc'): parser.error('invalid subtitle format specified') if opts.convertthumbnails is not None: - if opts.convertthumbnails not in ('jpg', ): + if opts.convertthumbnails not in ('jpg', 'png'): parser.error('invalid thumbnail format specified') if opts.date is not None: diff --git a/yt_dlp/options.py b/yt_dlp/options.py index 695e08594..c982dbb84 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -1253,7 +1253,7 @@ def parseOpts(overrideArguments=None): postproc.add_option( '--convert-thumbnails', metavar='FORMAT', dest='convertthumbnails', default=None, - help='Convert the thumbnails to another format (currently supported: jpg)') + help='Convert the thumbnails to another format (currently supported: jpg, png)') postproc.add_option( '--split-chapters', '--split-tracks', dest='split_chapters', action='store_true', default=False, diff --git a/yt_dlp/postprocessor/embedthumbnail.py b/yt_dlp/postprocessor/embedthumbnail.py index 2d4f42a20..b0372225a 100644 --- a/yt_dlp/postprocessor/embedthumbnail.py +++ b/yt_dlp/postprocessor/embedthumbnail.py @@ -77,11 +77,14 @@ class EmbedThumbnailPP(FFmpegPostProcessor): original_thumbnail = thumbnail_filename = info['thumbnails'][-1]['filepath'] - # Convert unsupported thumbnail formats to JPEG (see #25687, #25717) + # Convert unsupported thumbnail formats to PNG (see #25687, #25717) + # Original behavior was to convert to JPG, but since JPG is a lossy + # format, there will be some additional data loss. + # PNG, on the other hand, is lossless. thumbnail_ext = os.path.splitext(thumbnail_filename)[1][1:] if thumbnail_ext not in ('jpg', 'png'): - thumbnail_filename = convertor.convert_thumbnail(thumbnail_filename, 'jpg') - thumbnail_ext = 'jpg' + thumbnail_filename = convertor.convert_thumbnail(thumbnail_filename, 'png') + thumbnail_ext = 'png' mtime = os.stat(encodeFilename(filename)).st_mtime diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py index b15610829..f2e3559a5 100644 --- a/yt_dlp/postprocessor/ffmpeg.py +++ b/yt_dlp/postprocessor/ffmpeg.py @@ -849,24 +849,30 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor): info['__files_to_move'].pop(thumbnail_filename), 'webp') def convert_thumbnail(self, thumbnail_filename, ext): - if ext != 'jpg': - raise FFmpegPostProcessorError('Only conversion to jpg is currently supported') + if ext == 'jpg': + format_name = 'JPEG' + opts = ['-bsf:v', 'mjpeg2jpeg'] + elif ext == 'png': + format_name = 'PNG' + opts = [] + else: + raise FFmpegPostProcessorError('Only conversion to either jpg or png is currently supported') # NB: % is supposed to be escaped with %% but this does not work # for input files so working around with standard substitution escaped_thumbnail_filename = thumbnail_filename.replace('%', '#') os.rename(encodeFilename(thumbnail_filename), encodeFilename(escaped_thumbnail_filename)) - escaped_thumbnail_jpg_filename = replace_extension(escaped_thumbnail_filename, 'jpg') - self.to_screen('Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename) - self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_jpg_filename, ['-bsf:v', 'mjpeg2jpeg']) - thumbnail_jpg_filename = replace_extension(thumbnail_filename, 'jpg') + escaped_thumbnail_conv_filename = replace_extension(escaped_thumbnail_filename, ext) + self.to_screen('Converting thumbnail "%s" to %s' % (escaped_thumbnail_filename, format_name)) + self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_conv_filename, opts) + thumbnail_conv_filename = replace_extension(thumbnail_filename, ext) # Rename back to unescaped os.rename(encodeFilename(escaped_thumbnail_filename), encodeFilename(thumbnail_filename)) - os.rename(encodeFilename(escaped_thumbnail_jpg_filename), encodeFilename(thumbnail_jpg_filename)) - return thumbnail_jpg_filename + os.rename(encodeFilename(escaped_thumbnail_conv_filename), encodeFilename(thumbnail_conv_filename)) + return thumbnail_conv_filename def run(self, info): - if self.format != 'jpg': - raise FFmpegPostProcessorError('Only conversion to jpg is currently supported') + if self.format not in ('jpg', 'png'): + raise FFmpegPostProcessorError('Only conversion to either jpg or png is currently supported') files_to_delete = [] has_thumbnail = False