2 from __future__
import unicode_literals
17 from .ffmpeg
import FFmpegPostProcessor
26 process_communicate_or_kill
,
32 class EmbedThumbnailPPError(PostProcessingError
):
36 class EmbedThumbnailPP(FFmpegPostProcessor
):
38 def __init__(self
, downloader
=None, already_have_thumbnail
=False):
39 super(EmbedThumbnailPP
, self
).__init
__(downloader
)
40 self
._already
_have
_thumbnail
= already_have_thumbnail
43 filename
= info
['filepath']
44 temp_filename
= prepend_extension(filename
, 'temp')
46 if not info
.get('thumbnails'):
47 self
.to_screen('There aren\'t any thumbnails to embed')
50 original_thumbnail
= thumbnail_filename
= info
['thumbnails'][-1]['filename']
52 if not os
.path
.exists(encodeFilename(thumbnail_filename
)):
53 self
.report_warning('Skipping embedding the thumbnail because the file is missing.')
57 with open(encodeFilename(path
), 'rb') as f
:
59 return b
[0:4] == b
'RIFF' and b
[8:] == b
'WEBP'
61 # Correct extension for WebP file with wrong extension (see #25687, #25717)
62 _
, thumbnail_ext
= os
.path
.splitext(thumbnail_filename
)
64 thumbnail_ext
= thumbnail_ext
[1:].lower()
65 if thumbnail_ext
!= 'webp' and is_webp(thumbnail_filename
):
66 self
.to_screen('Correcting extension to webp and escaping path for thumbnail "%s"' % thumbnail_filename
)
67 thumbnail_webp_filename
= replace_extension(thumbnail_filename
, 'webp')
68 os
.rename(encodeFilename(thumbnail_filename
), encodeFilename(thumbnail_webp_filename
))
69 original_thumbnail
= thumbnail_filename
= thumbnail_webp_filename
70 thumbnail_ext
= 'webp'
72 # Convert unsupported thumbnail formats to JPEG (see #25687, #25717)
73 if thumbnail_ext
not in ['jpg', 'png']:
74 # NB: % is supposed to be escaped with %% but this does not work
75 # for input files so working around with standard substitution
76 escaped_thumbnail_filename
= thumbnail_filename
.replace('%', '#')
77 os
.rename(encodeFilename(thumbnail_filename
), encodeFilename(escaped_thumbnail_filename
))
78 escaped_thumbnail_jpg_filename
= replace_extension(escaped_thumbnail_filename
, 'jpg')
79 self
.to_screen('Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename
)
80 self
.run_ffmpeg(escaped_thumbnail_filename
, escaped_thumbnail_jpg_filename
, ['-bsf:v', 'mjpeg2jpeg'])
81 thumbnail_jpg_filename
= replace_extension(thumbnail_filename
, 'jpg')
82 # Rename back to unescaped for further processing
83 os
.rename(encodeFilename(escaped_thumbnail_filename
), encodeFilename(thumbnail_filename
))
84 os
.rename(encodeFilename(escaped_thumbnail_jpg_filename
), encodeFilename(thumbnail_jpg_filename
))
85 thumbnail_filename
= thumbnail_jpg_filename
89 if info
['ext'] == 'mp3':
91 '-c', 'copy', '-map', '0:0', '-map', '1:0', '-id3v2_version', '3',
92 '-metadata:s:v', 'title="Album cover"', '-metadata:s:v', 'comment="Cover (front)"']
94 self
.to_screen('Adding thumbnail to "%s"' % filename
)
95 self
.run_ffmpeg_multiple_files([filename
, thumbnail_filename
], temp_filename
, options
)
97 elif info
['ext'] in ['mkv', 'mka']:
98 options
= ['-c', 'copy', '-map', '0', '-dn']
100 mimetype
= 'image/%s' % ('png' if thumbnail_ext
== 'png' else 'jpeg')
101 old_stream
, new_stream
= self
.get_stream_number(
102 filename
, ('tags', 'mimetype'), mimetype
)
103 if old_stream
is not None:
104 options
.extend(['-map', '-0:%d' % old_stream
])
107 '-attach', thumbnail_filename
,
108 '-metadata:s:%d' % new_stream
, 'mimetype=%s' % mimetype
,
109 '-metadata:s:%d' % new_stream
, 'filename=cover.%s' % thumbnail_ext
])
111 self
.to_screen('Adding thumbnail to "%s"' % filename
)
112 self
.run_ffmpeg(filename
, temp_filename
, options
)
114 elif info
['ext'] in ['m4a', 'mp4', 'mov']:
116 options
= ['-c', 'copy', '-map', '0', '-dn', '-map', '1']
118 old_stream
, new_stream
= self
.get_stream_number(
119 filename
, ('disposition', 'attached_pic'), 1)
120 if old_stream
is not None:
121 options
.extend(['-map', '-0:%d' % old_stream
])
123 options
.extend(['-disposition:%s' % new_stream
, 'attached_pic'])
125 self
.to_screen('Adding thumbnail to "%s"' % filename
)
126 self
.run_ffmpeg_multiple_files([filename
, thumbnail_filename
], temp_filename
, options
)
128 except PostProcessingError
as err
:
129 self
.report_warning('unable to embed using ffprobe & ffmpeg; %s' % error_to_compat_str(err
))
130 atomicparsley
= next((
131 x
for x
in ['AtomicParsley', 'atomicparsley']
132 if check_executable(x
, ['-v'])), None)
133 if atomicparsley
is None:
134 raise EmbedThumbnailPPError('AtomicParsley was not found. Please install.')
136 cmd
= [encodeFilename(atomicparsley
, True),
137 encodeFilename(filename
, True),
138 encodeArgument('--artwork'),
139 encodeFilename(thumbnail_filename
, True),
140 encodeArgument('-o'),
141 encodeFilename(temp_filename
, True)]
142 cmd
+= [encodeArgument(o
) for o
in self
._configuration
_args
(exe
='AtomicParsley')]
144 self
.to_screen('Adding thumbnail to "%s"' % filename
)
145 self
.write_debug('AtomicParsley command line: %s' % shell_quote(cmd
))
146 p
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
147 stdout
, stderr
= process_communicate_or_kill(p
)
148 if p
.returncode
!= 0:
149 msg
= stderr
.decode('utf-8', 'replace').strip()
150 raise EmbedThumbnailPPError(msg
)
151 # for formats that don't support thumbnails (like 3gp) AtomicParsley
152 # won't create to the temporary file
153 if b
'No changes' in stdout
:
154 self
.report_warning('The file format doesn\'t support embedding a thumbnail')
157 elif info
['ext'] in ['ogg', 'opus']:
159 raise EmbedThumbnailPPError('module mutagen was not found. Please install using `python -m pip install mutagen`')
160 self
.to_screen('Adding thumbnail to "%s"' % filename
)
162 size_regex
= r
',\s*(?P<w>\d+)x(?P<h>\d+)\s*[,\[]'
163 size_result
= self
.run_ffmpeg(thumbnail_filename
, thumbnail_filename
, ['-hide_banner'])
164 mobj
= re
.search(size_regex
, size_result
)
165 width
, height
= int(mobj
.group('w')), int(mobj
.group('h'))
166 mimetype
= ('image/%s' % ('png' if thumbnail_ext
== 'png' else 'jpeg')).encode('ascii')
168 # https://xiph.org/flac/format.html#metadata_block_picture
170 data
+= struct
.pack('>II', 3, len(mimetype
))
172 data
+= struct
.pack('>IIIIII', 0, width
, height
, 8, 0, os
.stat(thumbnail_filename
).st_size
) # 32 if png else 24
174 fin
= open(thumbnail_filename
, "rb")
178 temp_filename
= filename
179 f
= mutagen
.File(temp_filename
)
180 f
.tags
['METADATA_BLOCK_PICTURE'] = base64
.b64encode(data
).decode('ascii')
184 raise EmbedThumbnailPPError('Supported filetypes for thumbnail embedding are: mp3, mkv/mka, ogg/opus, m4a/mp4/mov')
186 if success
and temp_filename
!= filename
:
187 os
.remove(encodeFilename(filename
))
188 os
.rename(encodeFilename(temp_filename
), encodeFilename(filename
))
190 files_to_delete
= [thumbnail_filename
]
191 if self
._already
_have
_thumbnail
:
192 info
['__files_to_move'][original_thumbnail
] = replace_extension(
193 info
['__thumbnail_filename'], os
.path
.splitext(original_thumbnail
)[1][1:])
194 if original_thumbnail
== thumbnail_filename
:
196 return files_to_delete
, info