diff --git a/.gitignore b/.gitignore
index a2484b7526..b6431b7666 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,46 @@
+# Config
+*.conf
+*.spec
+cookies
+cookies.txt
+
+# Downloaded
+*.srt
+*.ttml
+*.sbv
+*.vtt
+*.flv
+*.mp4
+*.m4a
+*.m4v
+*.mp3
+*.3gp
+*.webm
+*.wav
+*.ape
+*.mkv
+*.swf
+*.part
+*.part-*
+*.ytdl
+*.dump
+*.frag
+*.frag.urls
+*.aria2
+*.swp
+*.ogg
+*.opus
+*.info.json
+*.live_chat.json
+*.jpg
+*.png
+*.webp
+*.annotations.xml
+*.description
+
+# Allow config/media files in testdata
+!test/testdata/**
+
# Python
*.pyc
*.pyo
@@ -43,48 +86,6 @@ README.txt
yt-dlp.zip
*.exe
-# Downloaded
-*.srt
-*.ttml
-*.sbv
-*.vtt
-*.flv
-*.mp4
-*.m4a
-*.m4v
-*.mp3
-*.3gp
-*.webm
-*.wav
-*.ape
-*.mkv
-*.swf
-*.part
-*.part-*
-*.ytdl
-*.dump
-*.frag
-*.frag.urls
-*.aria2
-*.swp
-*.ogg
-*.opus
-*.info.json
-*.live_chat.json
-*.jpg
-*.png
-*.webp
-*.annotations.xml
-*.description
-
-# Config
-*.conf
-*.spec
-cookies
-cookies.txt
-
-
-
# Text Editor / IDE
.idea
*.iml
diff --git a/test/test_postprocessors.py b/test/test_postprocessors.py
index 7574a0b950..868bb25f9b 100644
--- a/test/test_postprocessors.py
+++ b/test/test_postprocessors.py
@@ -8,7 +8,11 @@ import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-from yt_dlp.postprocessor import MetadataFromFieldPP, MetadataFromTitlePP
+from yt_dlp.postprocessor import (
+ FFmpegThumbnailsConvertorPP,
+ MetadataFromFieldPP,
+ MetadataFromTitlePP,
+)
class TestMetadataFromField(unittest.TestCase):
@@ -30,3 +34,24 @@ class TestMetadataFromTitle(unittest.TestCase):
def test_format_to_regex(self):
pp = MetadataFromTitlePP(None, '%(title)s - %(artist)s')
self.assertEqual(pp._titleregex, r'(?P
.+)\ \-\ (?P.+)')
+
+
+class TestConvertThumbnail(unittest.TestCase):
+ def test_escaping(self):
+ pp = FFmpegThumbnailsConvertorPP()
+ if not pp.available:
+ print('Skipping: ffmpeg not found')
+ return
+
+ file = 'test/testdata/thumbnails/foo %d bar/foo_%d.{}'
+ tests = (('webp', 'png'), ('png', 'jpg'))
+
+ for inp, out in tests:
+ out_file = file.format(out)
+ if os.path.exists(out_file):
+ os.remove(out_file)
+ pp.convert_thumbnail(file.format(inp), out)
+ assert os.path.exists(out_file)
+
+ for _, out in tests:
+ os.remove(file.format(out))
diff --git a/test/testdata/thumbnails/foo %d bar/foo_%d.webp b/test/testdata/thumbnails/foo %d bar/foo_%d.webp
new file mode 100644
index 0000000000..d64d0839f0
Binary files /dev/null and b/test/testdata/thumbnails/foo %d bar/foo_%d.webp differ
diff --git a/yt_dlp/postprocessor/embedthumbnail.py b/yt_dlp/postprocessor/embedthumbnail.py
index f3eb7d96d5..278a45eb64 100644
--- a/yt_dlp/postprocessor/embedthumbnail.py
+++ b/yt_dlp/postprocessor/embedthumbnail.py
@@ -70,7 +70,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
self.to_screen('There aren\'t any thumbnails to embed')
return [], info
- idx = next((-(i+1) for i, t in enumerate(info['thumbnails'][::-1]) if t.get('filepath')), None)
+ idx = next((-i for i, t in enumerate(info['thumbnails'][::-1], 1) if t.get('filepath')), None)
if idx is None:
self.to_screen('There are no thumbnails on disk')
return [], info
diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py
index ea728be37b..d9f816b043 100644
--- a/yt_dlp/postprocessor/ffmpeg.py
+++ b/yt_dlp/postprocessor/ffmpeg.py
@@ -853,19 +853,12 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
return []
def convert_thumbnail(self, thumbnail_filename, target_ext):
- # 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_conv_filename = replace_extension(escaped_thumbnail_filename, target_ext)
-
- self.to_screen('Converting thumbnail "%s" to %s' % (escaped_thumbnail_filename, target_ext))
- self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_conv_filename, self._options(target_ext))
-
- # Rename back to unescaped
thumbnail_conv_filename = replace_extension(thumbnail_filename, target_ext)
- os.rename(encodeFilename(escaped_thumbnail_filename), encodeFilename(thumbnail_filename))
- os.rename(encodeFilename(escaped_thumbnail_conv_filename), encodeFilename(thumbnail_conv_filename))
+
+ self.to_screen('Converting thumbnail "%s" to %s' % (thumbnail_filename, target_ext))
+ self.real_run_ffmpeg(
+ [(thumbnail_filename, ['-f', 'image2', '-pattern_type', 'none'])],
+ [(thumbnail_conv_filename.replace('%', '%%'), self._options(target_ext))])
return thumbnail_conv_filename
def run(self, info):