]> jfr.im git - yt-dlp.git/blame - youtube_dl/postprocessor/ffmpeg.py
Ignore TTML subtitles
[yt-dlp.git] / youtube_dl / postprocessor / ffmpeg.py
CommitLineData
3aa578ca
PH
1from __future__ import unicode_literals
2
e9fade72 3import io
496c1923
PH
4import os
5import subprocess
496c1923 6import time
fa2a36d9 7import re
496c1923
PH
8
9
10from .common import AudioConversionError, PostProcessor
11
8c25f81b 12from ..compat import (
496c1923 13 compat_subprocess_get_DEVNULL,
8c25f81b
PH
14)
15from ..utils import (
f07b74fc 16 encodeArgument,
496c1923 17 encodeFilename,
95807118 18 get_exe_version,
48844745 19 is_outdated_version,
496c1923
PH
20 PostProcessingError,
21 prepend_extension,
22 shell_quote,
23 subtitles_filename,
bf6427d2 24 dfxp2srt,
39672624 25 ISO639Utils,
fa2a36d9 26 replace_extension,
496c1923
PH
27)
28
29
a755f825 30EXT_TO_OUT_FORMATS = {
21bfcd3d
PH
31 'aac': 'adts',
32 'flac': 'flac',
33 'm4a': 'ipod',
34 'mka': 'matroska',
35 'mkv': 'matroska',
36 'mpg': 'mpeg',
37 'ogv': 'ogg',
38 'ts': 'mpegts',
39 'wma': 'asf',
40 'wmv': 'asf',
41}
42ACODECS = {
43 'mp3': 'libmp3lame',
44 'aac': 'aac',
45 'flac': 'flac',
46 'm4a': 'aac',
47 'opus': 'opus',
48 'vorbis': 'libvorbis',
49 'wav': None,
a755f825 50}
51
52
496c1923
PH
53class FFmpegPostProcessorError(PostProcessingError):
54 pass
55
d799b47b 56
496c1923 57class FFmpegPostProcessor(PostProcessor):
d47aeb22 58 def __init__(self, downloader=None):
496c1923 59 PostProcessor.__init__(self, downloader)
73fac4e9 60 self._determine_executables()
496c1923 61
48844745 62 def check_version(self):
f740fae2 63 if not self.available:
3aa578ca 64 raise FFmpegPostProcessorError('ffmpeg or avconv not found. Please install one.')
48844745 65
65bf37ef 66 required_version = '10-0' if self.basename == 'avconv' else '1.0'
48844745 67 if is_outdated_version(
73fac4e9 68 self._versions[self.basename], required_version):
3aa578ca 69 warning = 'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % (
73fac4e9 70 self.basename, self.basename, required_version)
6194bb14
PH
71 if self._downloader:
72 self._downloader.report_warning(warning)
48844745 73
496c1923 74 @staticmethod
73fac4e9
PH
75 def get_versions(downloader=None):
76 return FFmpegPostProcessor(downloader)._versions
6271f1ca 77
73fac4e9
PH
78 def _determine_executables(self):
79 programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
374c761e 80 prefer_ffmpeg = False
73fac4e9
PH
81
82 self.basename = None
83 self.probe_basename = None
84
85 self._paths = None
86 self._versions = None
87 if self._downloader:
374c761e 88 prefer_ffmpeg = self._downloader.params.get('prefer_ffmpeg', False)
73fac4e9
PH
89 location = self._downloader.params.get('ffmpeg_location')
90 if location is not None:
91 if not os.path.exists(location):
92 self._downloader.report_warning(
93 'ffmpeg-location %s does not exist! '
94 'Continuing without avconv/ffmpeg.' % (location))
95 self._versions = {}
96 return
97 elif not os.path.isdir(location):
98 basename = os.path.splitext(os.path.basename(location))[0]
99 if basename not in programs:
100 self._downloader.report_warning(
101 'Cannot identify executable %s, its basename should be one of %s. '
102 'Continuing without avconv/ffmpeg.' %
103 (location, ', '.join(programs)))
104 self._versions = {}
105 return None
106 location = os.path.dirname(os.path.abspath(location))
107 if basename in ('ffmpeg', 'ffprobe'):
108 prefer_ffmpeg = True
109
110 self._paths = dict(
111 (p, os.path.join(location, p)) for p in programs)
112 self._versions = dict(
113 (p, get_exe_version(self._paths[p], args=['-version']))
114 for p in programs)
115 if self._versions is None:
116 self._versions = dict(
117 (p, get_exe_version(p, args=['-version'])) for p in programs)
118 self._paths = dict((p, p) for p in programs)
119
120 if prefer_ffmpeg:
d28b5171 121 prefs = ('ffmpeg', 'avconv')
76b1bd67 122 else:
d28b5171
PH
123 prefs = ('avconv', 'ffmpeg')
124 for p in prefs:
125 if self._versions[p]:
73fac4e9
PH
126 self.basename = p
127 break
76b1bd67 128
73fac4e9 129 if prefer_ffmpeg:
50b51830 130 prefs = ('ffprobe', 'avprobe')
1a253e13
PH
131 else:
132 prefs = ('avprobe', 'ffprobe')
133 for p in prefs:
134 if self._versions[p]:
73fac4e9
PH
135 self.probe_basename = p
136 break
137
f740fae2 138 @property
73fac4e9
PH
139 def available(self):
140 return self.basename is not None
1a253e13 141
73fac4e9
PH
142 @property
143 def executable(self):
144 return self._paths[self.basename]
145
3da4b313
JMF
146 @property
147 def probe_available(self):
148 return self.probe_basename is not None
149
73fac4e9
PH
150 @property
151 def probe_executable(self):
152 return self._paths[self.probe_basename]
76b1bd67 153
30d9e209
RA
154 def get_audio_codec(self, path):
155 if not self.probe_available:
156 raise PostProcessingError('ffprobe or avprobe not found. Please install one.')
157 try:
158 cmd = [
159 encodeFilename(self.probe_executable, True),
160 encodeArgument('-show_streams'),
161 encodeFilename(self._ffmpeg_filename_argument(path), True)]
162 if self._downloader.params.get('verbose', False):
163 self._downloader.to_screen('[debug] %s command line: %s' % (self.basename, shell_quote(cmd)))
164 handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
165 output = handle.communicate()[0]
166 if handle.wait() != 0:
167 return None
168 except (IOError, OSError):
169 return None
170 audio_codec = None
171 for line in output.decode('ascii', 'ignore').split('\n'):
172 if line.startswith('codec_name='):
173 audio_codec = line.split('=')[1].strip()
174 elif line.strip() == 'codec_type=audio' and audio_codec is not None:
175 return audio_codec
176 return None
177
496c1923 178 def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
48844745 179 self.check_version()
496c1923 180
52afb2ac
PH
181 oldest_mtime = min(
182 os.stat(encodeFilename(path)).st_mtime for path in input_paths)
43bc8890 183
15006fed
S
184 opts += self._configuration_args()
185
496c1923
PH
186 files_cmd = []
187 for path in input_paths:
8a7bbd16
JMF
188 files_cmd.extend([
189 encodeArgument('-i'),
190 encodeFilename(self._ffmpeg_filename_argument(path), True)
191 ])
73fac4e9 192 cmd = ([encodeFilename(self.executable, True), encodeArgument('-y')] +
43bc8890
PH
193 files_cmd +
194 [encodeArgument(o) for o in opts] +
496c1923
PH
195 [encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
196
197 if self._downloader.params.get('verbose', False):
3aa578ca 198 self._downloader.to_screen('[debug] ffmpeg command line: %s' % shell_quote(cmd))
cffcbc02 199 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
62fec3b2 200 stdout, stderr = p.communicate()
496c1923
PH
201 if p.returncode != 0:
202 stderr = stderr.decode('utf-8', 'replace')
203 msg = stderr.strip().split('\n')[-1]
204 raise FFmpegPostProcessorError(msg)
dd29eb7f 205 self.try_utime(out_path, oldest_mtime, oldest_mtime)
cc55d088 206
496c1923
PH
207 def run_ffmpeg(self, path, out_path, opts):
208 self.run_ffmpeg_multiple_files([path], out_path, opts)
209
210 def _ffmpeg_filename_argument(self, fn):
8a7bbd16
JMF
211 # Always use 'file:' because the filename may contain ':' (ffmpeg
212 # interprets that as a protocol) or can start with '-' (-- is broken in
213 # ffmpeg, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details)
b9f2fdd3 214 # Also leave '-' intact in order not to break streaming to stdout.
d868f43c 215 return 'file:' + fn if fn != '-' else fn
496c1923
PH
216
217
218class FFmpegExtractAudioPP(FFmpegPostProcessor):
219 def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False):
220 FFmpegPostProcessor.__init__(self, downloader)
221 if preferredcodec is None:
222 preferredcodec = 'best'
223 self._preferredcodec = preferredcodec
224 self._preferredquality = preferredquality
225 self._nopostoverwrites = nopostoverwrites
226
496c1923 227 def run_ffmpeg(self, path, out_path, codec, more_opts):
496c1923
PH
228 if codec is None:
229 acodec_opts = []
230 else:
231 acodec_opts = ['-acodec', codec]
232 opts = ['-vn'] + acodec_opts + more_opts
233 try:
234 FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts)
235 except FFmpegPostProcessorError as err:
236 raise AudioConversionError(err.msg)
237
238 def run(self, information):
239 path = information['filepath']
240
241 filecodec = self.get_audio_codec(path)
242 if filecodec is None:
3aa578ca 243 raise PostProcessingError('WARNING: unable to obtain file audio codec with ffprobe')
496c1923
PH
244
245 more_opts = []
246 if self._preferredcodec == 'best' or self._preferredcodec == filecodec or (self._preferredcodec == 'm4a' and filecodec == 'aac'):
247 if filecodec == 'aac' and self._preferredcodec in ['m4a', 'best']:
248 # Lossless, but in another container
249 acodec = 'copy'
250 extension = 'm4a'
467d3c9a 251 more_opts = ['-bsf:a', 'aac_adtstoasc']
21bfcd3d 252 elif filecodec in ['aac', 'flac', 'mp3', 'vorbis', 'opus']:
496c1923
PH
253 # Lossless if possible
254 acodec = 'copy'
255 extension = filecodec
256 if filecodec == 'aac':
257 more_opts = ['-f', 'adts']
258 if filecodec == 'vorbis':
259 extension = 'ogg'
260 else:
261 # MP3 otherwise.
262 acodec = 'libmp3lame'
263 extension = 'mp3'
264 more_opts = []
265 if self._preferredquality is not None:
266 if int(self._preferredquality) < 10:
467d3c9a 267 more_opts += ['-q:a', self._preferredquality]
496c1923 268 else:
467d3c9a 269 more_opts += ['-b:a', self._preferredquality + 'k']
496c1923 270 else:
21bfcd3d
PH
271 # We convert the audio (lossy if codec is lossy)
272 acodec = ACODECS[self._preferredcodec]
496c1923
PH
273 extension = self._preferredcodec
274 more_opts = []
275 if self._preferredquality is not None:
276 # The opus codec doesn't support the -aq option
277 if int(self._preferredquality) < 10 and extension != 'opus':
467d3c9a 278 more_opts += ['-q:a', self._preferredquality]
496c1923 279 else:
467d3c9a 280 more_opts += ['-b:a', self._preferredquality + 'k']
496c1923
PH
281 if self._preferredcodec == 'aac':
282 more_opts += ['-f', 'adts']
283 if self._preferredcodec == 'm4a':
467d3c9a 284 more_opts += ['-bsf:a', 'aac_adtstoasc']
496c1923
PH
285 if self._preferredcodec == 'vorbis':
286 extension = 'ogg'
287 if self._preferredcodec == 'wav':
288 extension = 'wav'
289 more_opts += ['-f', 'wav']
290
3aa578ca 291 prefix, sep, ext = path.rpartition('.') # not os.path.splitext, since the latter does not work on unicode in all setups
496c1923 292 new_path = prefix + sep + extension
0b94dbb1 293
2273e2c5
PM
294 information['filepath'] = new_path
295 information['ext'] = extension
496c1923
PH
296
297 # If we download foo.mp3 and convert it to... foo.mp3, then don't delete foo.mp3, silly.
ce81b141
JMF
298 if (new_path == path or
299 (self._nopostoverwrites and os.path.exists(encodeFilename(new_path)))):
9750e7d7 300 self._downloader.to_screen('[ffmpeg] Post-process file %s exists, skipping' % new_path)
592e97e8 301 return [], information
496c1923
PH
302
303 try:
deb85c32 304 self._downloader.to_screen('[ffmpeg] Destination: ' + new_path)
ce81b141 305 self.run_ffmpeg(path, new_path, acodec, more_opts)
70a1165b
JMF
306 except AudioConversionError as e:
307 raise PostProcessingError(
308 'audio conversion failed: ' + e.msg)
309 except Exception:
310 raise PostProcessingError('error running ' + self.basename)
496c1923
PH
311
312 # Try to update the date time for extracted audio file.
313 if information.get('filetime') is not None:
dd29eb7f
S
314 self.try_utime(
315 new_path, time.time(), information['filetime'],
316 errnote='Cannot update utime of audio file')
496c1923 317
592e97e8 318 return [path], information
496c1923
PH
319
320
4f026faf 321class FFmpegVideoConvertorPP(FFmpegPostProcessor):
5f6a1245 322 def __init__(self, downloader=None, preferedformat=None):
4f026faf 323 super(FFmpegVideoConvertorPP, self).__init__(downloader)
5f6a1245 324 self._preferedformat = preferedformat
496c1923
PH
325
326 def run(self, information):
327 path = information['filepath']
496c1923 328 if information['ext'] == self._preferedformat:
3aa578ca 329 self._downloader.to_screen('[ffmpeg] Not converting video file %s - already is in target format %s' % (path, self._preferedformat))
592e97e8 330 return [], information
15006fed
S
331 options = []
332 if self._preferedformat == 'avi':
333 options.extend(['-c:v', 'libxvid', '-vtag', 'XVID'])
334 prefix, sep, ext = path.rpartition('.')
335 outpath = prefix + sep + self._preferedformat
3aa578ca 336 self._downloader.to_screen('[' + 'ffmpeg' + '] Converting video from %s to %s, Destination: ' % (information['ext'], self._preferedformat) + outpath)
d84f1d14 337 self.run_ffmpeg(path, outpath, options)
496c1923
PH
338 information['filepath'] = outpath
339 information['format'] = self._preferedformat
f72b0a60 340 information['ext'] = self._preferedformat
592e97e8 341 return [path], information
496c1923
PH
342
343
344class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
496c1923 345 def run(self, information):
40025ee2
S
346 if information['ext'] not in ('mp4', 'webm', 'mkv'):
347 self._downloader.to_screen('[ffmpeg] Subtitles can only be embedded in mp4, webm or mkv files')
592e97e8 348 return [], information
c84dd8a9
JMF
349 subtitles = information.get('requested_subtitles')
350 if not subtitles:
3aa578ca 351 self._downloader.to_screen('[ffmpeg] There aren\'t any subtitles to embed')
592e97e8 352 return [], information
496c1923 353
496c1923 354 filename = information['filepath']
40025ee2
S
355
356 ext = information['ext']
357 sub_langs = []
358 sub_filenames = []
359 webm_vtt_warn = False
360
361 for lang, sub_info in subtitles.items():
362 sub_ext = sub_info['ext']
363 if ext != 'webm' or ext == 'webm' and sub_ext == 'vtt':
364 sub_langs.append(lang)
365 sub_filenames.append(subtitles_filename(filename, lang, sub_ext))
366 else:
367 if not webm_vtt_warn and ext == 'webm' and sub_ext != 'vtt':
368 webm_vtt_warn = True
369 self._downloader.to_screen('[ffmpeg] Only WebVTT subtitles can be embedded in webm files')
370
371 if not sub_langs:
372 return [], information
373
14523ed9 374 input_files = [filename] + sub_filenames
496c1923 375
e205db3b 376 opts = [
23495d6a
YCH
377 '-map', '0',
378 '-c', 'copy',
e205db3b
JMF
379 # Don't copy the existing subtitles, we may be running the
380 # postprocessor a second time
381 '-map', '-0:s',
382 ]
083c1bb9
N
383 if information['ext'] == 'mp4':
384 opts += ['-c:s', 'mov_text']
496c1923 385 for (i, lang) in enumerate(sub_langs):
2875cf01 386 opts.extend(['-map', '%d:0' % (i + 1)])
39672624 387 lang_code = ISO639Utils.short2long(lang)
496c1923
PH
388 if lang_code is not None:
389 opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code])
496c1923 390
2875cf01 391 temp_filename = prepend_extension(filename, 'temp')
3aa578ca 392 self._downloader.to_screen('[ffmpeg] Embedding subtitles in \'%s\'' % filename)
496c1923
PH
393 self.run_ffmpeg_multiple_files(input_files, temp_filename, opts)
394 os.remove(encodeFilename(filename))
395 os.rename(encodeFilename(temp_filename), encodeFilename(filename))
396
14523ed9 397 return sub_filenames, information
496c1923
PH
398
399
400class FFmpegMetadataPP(FFmpegPostProcessor):
401 def run(self, info):
402 metadata = {}
4bd143a3
S
403
404 def add(meta_list, info_list=None):
405 if not info_list:
406 info_list = meta_list
407 if not isinstance(meta_list, (list, tuple)):
408 meta_list = (meta_list,)
409 if not isinstance(info_list, (list, tuple)):
410 info_list = (info_list,)
411 for info_f in info_list:
412 if info.get(info_f) is not None:
413 for meta_f in meta_list:
414 metadata[meta_f] = info[info_f]
415 break
416
417 add('title', ('track', 'title'))
418 add('date', 'upload_date')
419 add(('description', 'comment'), 'description')
420 add('purl', 'webpage_url')
421 add('track', 'track_number')
422 add('artist', ('artist', 'creator', 'uploader', 'uploader_id'))
423 add('genre')
424 add('album')
425 add('album_artist')
426 add('disc', 'disc_number')
496c1923
PH
427
428 if not metadata:
3aa578ca 429 self._downloader.to_screen('[ffmpeg] There isn\'t any metadata to add')
592e97e8 430 return [], info
496c1923
PH
431
432 filename = info['filepath']
433 temp_filename = prepend_extension(filename, 'temp')
fa2a36d9 434 in_filenames = [filename]
435 options = []
496c1923 436
3aa578ca 437 if info['ext'] == 'm4a':
fa2a36d9 438 options.extend(['-vn', '-acodec', 'copy'])
39c68260 439 else:
fa2a36d9 440 options.extend(['-c', 'copy'])
39c68260 441
496c1923
PH
442 for (name, value) in metadata.items():
443 options.extend(['-metadata', '%s=%s' % (name, value)])
444
fa2a36d9 445 chapters = info.get('chapters', [])
446 if chapters:
5192ee17 447 metadata_filename = replace_extension(filename, 'meta')
fa2a36d9 448 with io.open(metadata_filename, 'wt', encoding='utf-8') as f:
449 def ffmpeg_escape(text):
450 return re.sub(r'(=|;|#|\\|\n)', r'\\\1', text)
451
452 metadata_file_content = ';FFMETADATA1\n'
453 for chapter in chapters:
454 metadata_file_content += '[CHAPTER]\nTIMEBASE=1/1000\n'
455 metadata_file_content += 'START=%d\n' % (chapter['start_time'] * 1000)
456 metadata_file_content += 'END=%d\n' % (chapter['end_time'] * 1000)
457 chapter_title = chapter.get('title')
458 if chapter_title:
459 metadata_file_content += 'title=%s\n' % ffmpeg_escape(chapter_title)
460 f.write(metadata_file_content)
461 in_filenames.append(metadata_filename)
462 options.extend(['-map_metadata', '1'])
463
3aa578ca 464 self._downloader.to_screen('[ffmpeg] Adding metadata to \'%s\'' % filename)
fa2a36d9 465 self.run_ffmpeg_multiple_files(in_filenames, temp_filename, options)
466 if chapters:
467 os.remove(metadata_filename)
496c1923
PH
468 os.remove(encodeFilename(filename))
469 os.rename(encodeFilename(temp_filename), encodeFilename(filename))
592e97e8 470 return [], info
496c1923
PH
471
472
473class FFmpegMergerPP(FFmpegPostProcessor):
474 def run(self, info):
475 filename = info['filepath']
5b5fbc08 476 temp_filename = prepend_extension(filename, 'temp')
bc3e582f 477 args = ['-c', 'copy', '-map', '0:v:0', '-map', '1:a:0']
3aa578ca 478 self._downloader.to_screen('[ffmpeg] Merging formats into "%s"' % filename)
5b5fbc08
JMF
479 self.run_ffmpeg_multiple_files(info['__files_to_merge'], temp_filename, args)
480 os.rename(encodeFilename(temp_filename), encodeFilename(filename))
d47aeb22 481 return info['__files_to_merge'], info
496c1923 482
13763ce5
S
483 def can_merge(self):
484 # TODO: figure out merge-capable ffmpeg version
485 if self.basename != 'avconv':
486 return True
487
488 required_version = '10-0'
489 if is_outdated_version(
490 self._versions[self.basename], required_version):
491 warning = ('Your copy of %s is outdated and unable to properly mux separate video and audio files, '
492 'youtube-dl will download single file media. '
493 'Update %s to version %s or newer to fix this.') % (
494 self.basename, self.basename, required_version)
495 if self._downloader:
496 self._downloader.report_warning(warning)
497 return False
498 return True
499
0c14e2fb 500
6271f1ca
PH
501class FFmpegFixupStretchedPP(FFmpegPostProcessor):
502 def run(self, info):
503 stretched_ratio = info.get('stretched_ratio')
504 if stretched_ratio is None or stretched_ratio == 1:
592e97e8 505 return [], info
6271f1ca
PH
506
507 filename = info['filepath']
508 temp_filename = prepend_extension(filename, 'temp')
509
510 options = ['-c', 'copy', '-aspect', '%f' % stretched_ratio]
511 self._downloader.to_screen('[ffmpeg] Fixing aspect ratio in "%s"' % filename)
512 self.run_ffmpeg(filename, temp_filename, options)
513
514 os.remove(encodeFilename(filename))
515 os.rename(encodeFilename(temp_filename), encodeFilename(filename))
516
592e97e8 517 return [], info
62cd676c
PH
518
519
520class FFmpegFixupM4aPP(FFmpegPostProcessor):
521 def run(self, info):
522 if info.get('container') != 'm4a_dash':
592e97e8 523 return [], info
62cd676c
PH
524
525 filename = info['filepath']
526 temp_filename = prepend_extension(filename, 'temp')
527
528 options = ['-c', 'copy', '-f', 'mp4']
529 self._downloader.to_screen('[ffmpeg] Correcting container in "%s"' % filename)
530 self.run_ffmpeg(filename, temp_filename, options)
531
532 os.remove(encodeFilename(filename))
533 os.rename(encodeFilename(temp_filename), encodeFilename(filename))
534
592e97e8 535 return [], info
e9fade72
JMF
536
537
f17f8651 538class FFmpegFixupM3u8PP(FFmpegPostProcessor):
539 def run(self, info):
540 filename = info['filepath']
30d9e209
RA
541 if self.get_audio_codec(filename) == 'aac':
542 temp_filename = prepend_extension(filename, 'temp')
f17f8651 543
30d9e209 544 options = ['-c', 'copy', '-f', 'mp4', '-bsf:a', 'aac_adtstoasc']
a02682fd 545 self._downloader.to_screen('[ffmpeg] Fixing malformed AAC bitstream in "%s"' % filename)
30d9e209 546 self.run_ffmpeg(filename, temp_filename, options)
f17f8651 547
30d9e209
RA
548 os.remove(encodeFilename(filename))
549 os.rename(encodeFilename(temp_filename), encodeFilename(filename))
f17f8651 550 return [], info
551
552
e9fade72
JMF
553class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
554 def __init__(self, downloader=None, format=None):
555 super(FFmpegSubtitlesConvertorPP, self).__init__(downloader)
556 self.format = format
557
558 def run(self, info):
559 subs = info.get('requested_subtitles')
560 filename = info['filepath']
561 new_ext = self.format
562 new_format = new_ext
563 if new_format == 'vtt':
564 new_format = 'webvtt'
565 if subs is None:
566 self._downloader.to_screen('[ffmpeg] There aren\'t any subtitles to convert')
592e97e8 567 return [], info
e9fade72 568 self._downloader.to_screen('[ffmpeg] Converting subtitles')
e04398e3 569 sub_filenames = []
e9fade72
JMF
570 for lang, sub in subs.items():
571 ext = sub['ext']
572 if ext == new_ext:
573 self._downloader.to_screen(
0f57447d 574 '[ffmpeg] Subtitle file for %s is already in the requested format' % new_ext)
e9fade72 575 continue
e04398e3
JMF
576 old_file = subtitles_filename(filename, lang, ext)
577 sub_filenames.append(old_file)
e9fade72 578 new_file = subtitles_filename(filename, lang, new_ext)
bf6427d2 579
40fcba5e 580 if ext in ('dfxp', 'ttml', 'tt'):
bf6427d2
YCH
581 self._downloader.report_warning(
582 'You have requested to convert dfxp (TTML) subtitles into another format, '
583 'which results in style information loss')
584
e04398e3 585 dfxp_file = old_file
bf6427d2
YCH
586 srt_file = subtitles_filename(filename, lang, 'srt')
587
588 with io.open(dfxp_file, 'rt', encoding='utf-8') as f:
589 srt_data = dfxp2srt(f.read())
590
591 with io.open(srt_file, 'wt', encoding='utf-8') as f:
592 f.write(srt_data)
7e62c2eb 593 old_file = srt_file
bf6427d2 594
bf6427d2
YCH
595 subs[lang] = {
596 'ext': 'srt',
597 'data': srt_data
598 }
599
600 if new_ext == 'srt':
601 continue
7b8b007c
JMF
602 else:
603 sub_filenames.append(srt_file)
bf6427d2 604
e04398e3 605 self.run_ffmpeg(old_file, new_file, ['-f', new_format])
e9fade72
JMF
606
607 with io.open(new_file, 'rt', encoding='utf-8') as f:
608 subs[lang] = {
3547d265 609 'ext': new_ext,
e9fade72
JMF
610 'data': f.read(),
611 }
612
e04398e3 613 return sub_filenames, info