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')
47 if not info
.get('thumbnails'):
48 self
.to_screen('There aren\'t any thumbnails to embed')
51 thumbnail_filename
= info
['thumbnails'][-1]['filename']
53 if not os
.path
.exists(encodeFilename(thumbnail_filename
)):
54 self
.report_warning('Skipping embedding the thumbnail because the file is missing.')
58 with open(encodeFilename(path
), 'rb') as f
:
60 return b
[0:4] == b
'RIFF' and b
[8:] == b
'WEBP'
62 # Correct extension for WebP file with wrong extension (see #25687, #25717)
63 _
, thumbnail_ext
= os
.path
.splitext(thumbnail_filename
)
65 thumbnail_ext
= thumbnail_ext
[1:].lower()
66 if thumbnail_ext
!= 'webp' and is_webp(thumbnail_filename
):
67 self
.to_screen('Correcting extension to webp and escaping path for thumbnail "%s"' % thumbnail_filename
)
68 thumbnail_webp_filename
= replace_extension(thumbnail_filename
, 'webp')
69 os
.rename(encodeFilename(thumbnail_filename
), encodeFilename(thumbnail_webp_filename
))
70 thumbnail_filename
= thumbnail_webp_filename
71 thumbnail_ext
= 'webp'
73 # Convert unsupported thumbnail formats to JPEG (see #25687, #25717)
74 if thumbnail_ext
not in ['jpg', 'png']:
75 # NB: % is supposed to be escaped with %% but this does not work
76 # for input files so working around with standard substitution
77 escaped_thumbnail_filename
= thumbnail_filename
.replace('%', '#')
78 os
.rename(encodeFilename(thumbnail_filename
), encodeFilename(escaped_thumbnail_filename
))
79 escaped_thumbnail_jpg_filename
= replace_extension(escaped_thumbnail_filename
, 'jpg')
80 self
.to_screen('Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename
)
81 self
.run_ffmpeg(escaped_thumbnail_filename
, escaped_thumbnail_jpg_filename
, ['-bsf:v', 'mjpeg2jpeg'])
82 files_to_delete
.append(escaped_thumbnail_filename
)
83 thumbnail_jpg_filename
= replace_extension(thumbnail_filename
, 'jpg')
84 # Rename back to unescaped for further processing
85 os
.rename(encodeFilename(escaped_thumbnail_jpg_filename
), encodeFilename(thumbnail_jpg_filename
))
86 thumbnail_filename
= thumbnail_jpg_filename
90 if info
['ext'] == 'mp3':
92 '-c', 'copy', '-map', '0:0', '-map', '1:0', '-id3v2_version', '3',
93 '-metadata:s:v', 'title="Album cover"', '-metadata:s:v', 'comment="Cover (front)"']
95 self
.to_screen('Adding thumbnail to "%s"' % filename
)
96 self
.run_ffmpeg_multiple_files([filename
, thumbnail_filename
], temp_filename
, options
)
98 elif info
['ext'] in ['mkv', 'mka']:
99 options
= ['-c', 'copy', '-map', '0', '-dn']
101 mimetype
= 'image/%s' % ('png' if thumbnail_ext
== 'png' else 'jpeg')
102 old_stream
, new_stream
= self
.get_stream_number(
103 filename
, ('tags', 'mimetype'), mimetype
)
104 if old_stream
is not None:
105 options
.extend(['-map', '-0:%d' % old_stream
])
108 '-attach', thumbnail_filename
,
109 '-metadata:s:%d' % new_stream
, 'mimetype=%s' % mimetype
,
110 '-metadata:s:%d' % new_stream
, 'filename=cover.%s' % thumbnail_ext
])
112 self
.to_screen('Adding thumbnail to "%s"' % filename
)
113 self
.run_ffmpeg(filename
, temp_filename
, options
)
115 elif info
['ext'] in ['m4a', 'mp4', 'mov']:
117 options
= ['-c', 'copy', '-map', '0', '-dn', '-map', '1']
119 old_stream
, new_stream
= self
.get_stream_number(
120 filename
, ('disposition', 'attached_pic'), 1)
121 if old_stream
is not None:
122 options
.extend(['-map', '-0:%d' % old_stream
])
124 options
.extend(['-disposition:%s' % new_stream
, 'attached_pic'])
126 self
.to_screen('Adding thumbnail to "%s"' % filename
)
127 self
.run_ffmpeg_multiple_files([filename
, thumbnail_filename
], temp_filename
, options
)
129 except PostProcessingError
as err
:
130 self
.report_warning('unable to embed using ffprobe & ffmpeg; %s' % error_to_compat_str(err
))
131 if not check_executable('AtomicParsley', ['-v']):
132 raise EmbedThumbnailPPError('AtomicParsley was not found. Please install.')
134 cmd
= [encodeFilename('AtomicParsley', True),
135 encodeFilename(filename
, True),
136 encodeArgument('--artwork'),
137 encodeFilename(thumbnail_filename
, True),
138 encodeArgument('-o'),
139 encodeFilename(temp_filename
, True)]
140 cmd
+= [encodeArgument(o
) for o
in self
._configuration
_args
(exe
='AtomicParsley')]
142 self
.to_screen('Adding thumbnail to "%s"' % filename
)
143 self
.write_debug('AtomicParsley command line: %s' % shell_quote(cmd
))
144 p
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
145 stdout
, stderr
= process_communicate_or_kill(p
)
146 if p
.returncode
!= 0:
147 msg
= stderr
.decode('utf-8', 'replace').strip()
148 raise EmbedThumbnailPPError(msg
)
149 # for formats that don't support thumbnails (like 3gp) AtomicParsley
150 # won't create to the temporary file
151 if b
'No changes' in stdout
:
152 self
.report_warning('The file format doesn\'t support embedding a thumbnail')
155 elif info
['ext'] in ['ogg', 'opus']:
157 raise EmbedThumbnailPPError('module mutagen was not found. Please install using `python -m pip install mutagen`')
158 self
.to_screen('Adding thumbnail to "%s"' % filename
)
160 size_regex
= r
',\s*(?P<w>\d+)x(?P<h>\d+)\s*[,\[]'
161 size_result
= self
.run_ffmpeg(thumbnail_filename
, thumbnail_filename
, ['-hide_banner'])
162 mobj
= re
.search(size_regex
, size_result
)
163 width
, height
= int(mobj
.group('w')), int(mobj
.group('h'))
164 mimetype
= ('image/%s' % ('png' if thumbnail_ext
== 'png' else 'jpeg')).encode('ascii')
166 # https://xiph.org/flac/format.html#metadata_block_picture
168 data
+= struct
.pack('>II', 3, len(mimetype
))
170 data
+= struct
.pack('>IIIIII', 0, width
, height
, 8, 0, os
.stat(thumbnail_filename
).st_size
) # 32 if png else 24
172 fin
= open(thumbnail_filename
, "rb")
176 temp_filename
= filename
177 f
= mutagen
.File(temp_filename
)
178 f
.tags
['METADATA_BLOCK_PICTURE'] = base64
.b64encode(data
).decode('ascii')
182 raise EmbedThumbnailPPError('Supported filetypes for thumbnail embedding are: mp3, mkv/mka, ogg/opus, m4a/mp4/mov')
184 if success
and temp_filename
!= filename
:
185 os
.remove(encodeFilename(filename
))
186 os
.rename(encodeFilename(temp_filename
), encodeFilename(filename
))
187 if self
._already
_have
_thumbnail
:
188 info
['__files_to_move'][thumbnail_filename
] = replace_extension(
189 info
['__thumbnail_filename'], os
.path
.splitext(thumbnail_filename
)[1][1:])
191 files_to_delete
.append(thumbnail_filename
)
192 return files_to_delete
, info