]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/PostProcessor.py
   9 class PostProcessor(object): 
  10     """Post Processor class. 
  12     PostProcessor objects can be added to downloaders with their 
  13     add_post_processor() method. When the downloader has finished a 
  14     successful download, it will take its internal chain of PostProcessors 
  15     and start calling the run() method on each one of them, first with 
  16     an initial argument and then with the returned value of the previous 
  19     The chain will be stopped if one of them ever returns None or the end 
  20     of the chain is reached. 
  22     PostProcessor objects follow a "mutual registration" process similar 
  23     to InfoExtractor objects. 
  28     def __init__(self
, downloader
=None): 
  29         self
._downloader 
= downloader
 
  31     def set_downloader(self
, downloader
): 
  32         """Sets the downloader for this PP.""" 
  33         self
._downloader 
= downloader
 
  35     def run(self
, information
): 
  36         """Run the PostProcessor. 
  38         The "information" argument is a dictionary like the ones 
  39         composed by InfoExtractors. The only difference is that this 
  40         one has an extra field called "filepath" that points to the 
  43         This method returns a tuple, the first element of which describes 
  44         whether the original file should be kept (i.e. not deleted - None for 
  45         no preference), and the second of which is the updated information. 
  47         In addition, this method may raise a PostProcessingError 
  48         exception if post processing fails. 
  50         return None, information 
# by default, keep file and do nothing 
  52 class FFmpegPostProcessorError(PostProcessingError
): 
  55 class AudioConversionError(PostProcessingError
): 
  58 class FFmpegPostProcessor(PostProcessor
): 
  59     def __init__(self
,downloader
=None): 
  60         PostProcessor
.__init
__(self
, downloader
) 
  61         self
._exes 
= self
.detect_executables() 
  64     def detect_executables(): 
  67                 subprocess
.Popen([exe
, '-version'], stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
).communicate() 
  71         programs 
= ['avprobe', 'avconv', 'ffmpeg', 'ffprobe'] 
  72         return dict((program
, executable(program
)) for program 
in programs
) 
  74     def run_ffmpeg_multiple_files(self
, input_paths
, out_path
, opts
): 
  75         if not self
._exes
['ffmpeg'] and not self
._exes
['avconv']: 
  76             raise FFmpegPostProcessorError(u
'ffmpeg or avconv not found. Please install one.') 
  79         for path 
in input_paths
: 
  80             files_cmd
.extend(['-i', encodeFilename(path
)]) 
  81         cmd 
= ([self
._exes
['avconv'] or self
._exes
['ffmpeg'], '-y'] + files_cmd
 
  83                [encodeFilename(self
._ffmpeg
_filename
_argument
(out_path
))]) 
  85         p 
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
) 
  86         stdout
,stderr 
= p
.communicate() 
  88             stderr 
= stderr
.decode('utf-8', 'replace') 
  89             msg 
= stderr
.strip().split('\n')[-1] 
  90             raise FFmpegPostProcessorError(msg
) 
  92     def run_ffmpeg(self
, path
, out_path
, opts
): 
  93         self
.run_ffmpeg_multiple_files([path
], out_path
, opts
) 
  95     def _ffmpeg_filename_argument(self
, fn
): 
  96         # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details 
  97         if fn
.startswith(u
'-'): 
 101 class FFmpegExtractAudioPP(FFmpegPostProcessor
): 
 102     def __init__(self
, downloader
=None, preferredcodec
=None, preferredquality
=None, nopostoverwrites
=False): 
 103         FFmpegPostProcessor
.__init
__(self
, downloader
) 
 104         if preferredcodec 
is None: 
 105             preferredcodec 
= 'best' 
 106         self
._preferredcodec 
= preferredcodec
 
 107         self
._preferredquality 
= preferredquality
 
 108         self
._nopostoverwrites 
= nopostoverwrites
 
 110     def get_audio_codec(self
, path
): 
 111         if not self
._exes
['ffprobe'] and not self
._exes
['avprobe']: 
 112             raise PostProcessingError(u
'ffprobe or avprobe not found. Please install one.') 
 114             cmd 
= [self
._exes
['avprobe'] or self
._exes
['ffprobe'], '-show_streams', encodeFilename(self
._ffmpeg
_filename
_argument
(path
))] 
 115             handle 
= subprocess
.Popen(cmd
, stderr
=compat_subprocess_get_DEVNULL(), stdout
=subprocess
.PIPE
) 
 116             output 
= handle
.communicate()[0] 
 117             if handle
.wait() != 0: 
 119         except (IOError, OSError): 
 122         for line 
in output
.decode('ascii', 'ignore').split('\n'): 
 123             if line
.startswith('codec_name='): 
 124                 audio_codec 
= line
.split('=')[1].strip() 
 125             elif line
.strip() == 'codec_type=audio' and audio_codec 
is not None: 
 129     def run_ffmpeg(self
, path
, out_path
, codec
, more_opts
): 
 130         if not self
._exes
['ffmpeg'] and not self
._exes
['avconv']: 
 131             raise AudioConversionError('ffmpeg or avconv not found. Please install one.') 
 135             acodec_opts 
= ['-acodec', codec
] 
 136         opts 
= ['-vn'] + acodec_opts 
+ more_opts
 
 138             FFmpegPostProcessor
.run_ffmpeg(self
, path
, out_path
, opts
) 
 139         except FFmpegPostProcessorError 
as err
: 
 140             raise AudioConversionError(err
.msg
) 
 142     def run(self
, information
): 
 143         path 
= information
['filepath'] 
 145         filecodec 
= self
.get_audio_codec(path
) 
 146         if filecodec 
is None: 
 147             raise PostProcessingError(u
'WARNING: unable to obtain file audio codec with ffprobe') 
 150         if self
._preferredcodec 
== 'best' or self
._preferredcodec 
== filecodec 
or (self
._preferredcodec 
== 'm4a' and filecodec 
== 'aac'): 
 151             if filecodec 
== 'aac' and self
._preferredcodec 
in ['m4a', 'best']: 
 152                 # Lossless, but in another container 
 155                 more_opts 
= [self
._exes
['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc'] 
 156             elif filecodec 
in ['aac', 'mp3', 'vorbis', 'opus']: 
 157                 # Lossless if possible 
 159                 extension 
= filecodec
 
 160                 if filecodec 
== 'aac': 
 161                     more_opts 
= ['-f', 'adts'] 
 162                 if filecodec 
== 'vorbis': 
 166                 acodec 
= 'libmp3lame' 
 169                 if self
._preferredquality 
is not None: 
 170                     if int(self
._preferredquality
) < 10: 
 171                         more_opts 
+= [self
._exes
['avconv'] and '-q:a' or '-aq', self
._preferredquality
] 
 173                         more_opts 
+= [self
._exes
['avconv'] and '-b:a' or '-ab', self
._preferredquality 
+ 'k'] 
 175             # We convert the audio (lossy) 
 176             acodec 
= {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'opus': 'opus', 'vorbis': 'libvorbis', 'wav': None}[self
._preferredcodec
] 
 177             extension 
= self
._preferredcodec
 
 179             if self
._preferredquality 
is not None: 
 180                 if int(self
._preferredquality
) < 10: 
 181                     more_opts 
+= [self
._exes
['avconv'] and '-q:a' or '-aq', self
._preferredquality
] 
 183                     more_opts 
+= [self
._exes
['avconv'] and '-b:a' or '-ab', self
._preferredquality 
+ 'k'] 
 184             if self
._preferredcodec 
== 'aac': 
 185                 more_opts 
+= ['-f', 'adts'] 
 186             if self
._preferredcodec 
== 'm4a': 
 187                 more_opts 
+= [self
._exes
['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc'] 
 188             if self
._preferredcodec 
== 'vorbis': 
 190             if self
._preferredcodec 
== 'wav': 
 192                 more_opts 
+= ['-f', 'wav'] 
 194         prefix
, sep
, ext 
= path
.rpartition(u
'.') # not os.path.splitext, since the latter does not work on unicode in all setups 
 195         new_path 
= prefix 
+ sep 
+ extension
 
 197         # If we download foo.mp3 and convert it to... foo.mp3, then don't delete foo.mp3, silly. 
 199             self
._nopostoverwrites 
= True 
 202             if self
._nopostoverwrites 
and os
.path
.exists(encodeFilename(new_path
)): 
 203                 self
._downloader
.to_screen(u
'[youtube] Post-process file %s exists, skipping' % new_path
) 
 205                 self
._downloader
.to_screen(u
'[' + (self
._exes
['avconv'] and 'avconv' or 'ffmpeg') + '] Destination: ' + new_path
) 
 206                 self
.run_ffmpeg(path
, new_path
, acodec
, more_opts
) 
 208             etype
,e
,tb 
= sys
.exc_info() 
 209             if isinstance(e
, AudioConversionError
): 
 210                 msg 
= u
'audio conversion failed: ' + e
.msg
 
 212                 msg 
= u
'error running ' + (self
._exes
['avconv'] and 'avconv' or 'ffmpeg') 
 213             raise PostProcessingError(msg
) 
 215         # Try to update the date time for extracted audio file. 
 216         if information
.get('filetime') is not None: 
 218                 os
.utime(encodeFilename(new_path
), (time
.time(), information
['filetime'])) 
 220                 self
._downloader
.report_warning(u
'Cannot update utime of audio file') 
 222         information
['filepath'] = new_path
 
 223         return self
._nopostoverwrites
,information
 
 225 class FFmpegVideoConvertor(FFmpegPostProcessor
): 
 226     def __init__(self
, downloader
=None,preferedformat
=None): 
 227         super(FFmpegVideoConvertor
, self
).__init
__(downloader
) 
 228         self
._preferedformat
=preferedformat
 
 230     def run(self
, information
): 
 231         path 
= information
['filepath'] 
 232         prefix
, sep
, ext 
= path
.rpartition(u
'.') 
 233         outpath 
= prefix 
+ sep 
+ self
._preferedformat
 
 234         if information
['ext'] == self
._preferedformat
: 
 235             self
._downloader
.to_screen(u
'[ffmpeg] Not converting video file %s - already is in target format %s' % (path
, self
._preferedformat
)) 
 236             return True,information
 
 237         self
._downloader
.to_screen(u
'['+'ffmpeg'+'] Converting video from %s to %s, Destination: ' % (information
['ext'], self
._preferedformat
) +outpath
) 
 238         self
.run_ffmpeg(path
, outpath
, []) 
 239         information
['filepath'] = outpath
 
 240         information
['format'] = self
._preferedformat
 
 241         information
['ext'] = self
._preferedformat
 
 242         return False,information
 
 245 class FFmpegEmbedSubtitlePP(FFmpegPostProcessor
): 
 246     # See http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt 
 434     def __init__(self
, downloader
=None, subtitlesformat
='srt'): 
 435         super(FFmpegEmbedSubtitlePP
, self
).__init
__(downloader
) 
 436         self
._subformat 
= subtitlesformat
 
 439     def _conver_lang_code(cls
, code
): 
 440         """Convert language code from ISO 639-1 to ISO 639-2/T""" 
 441         return cls
._lang
_map
.get(code
[:2]) 
 443     def run(self
, information
): 
 444         if information
['ext'] != u
'mp4': 
 445             self
._downloader
.to_screen(u
'[ffmpeg] Subtitles can only be embedded in mp4 files') 
 446             return True, information
 
 447         sub_langs 
= [key 
for key 
in information
['subtitles']] 
 449         filename 
= information
['filepath'] 
 450         input_files 
= [filename
] + [subtitles_filename(filename
, lang
, self
._subformat
) for lang 
in sub_langs
] 
 452         opts 
= ['-map', '0:0', '-map', '0:1', '-c:v', 'copy', '-c:a', 'copy'] 
 453         for (i
, lang
) in enumerate(sub_langs
): 
 454             opts
.extend(['-map', '%d:0' % (i
+1), '-c:s:%d' % i
, 'mov_text']) 
 455             lang_code 
= self
._conver
_lang
_code
(lang
) 
 456             if lang_code 
is not None: 
 457                 opts
.extend(['-metadata:s:s:%d' % i
, 'language=%s' % lang_code
]) 
 458         opts
.extend(['-f', 'mp4']) 
 460         temp_filename 
= filename 
+ u
'.temp' 
 461         self
._downloader
.to_screen(u
'[ffmpeg] Embedding subtitles in \'%s\'' % filename
) 
 462         self
.run_ffmpeg_multiple_files(input_files
, temp_filename
, opts
) 
 463         os
.remove(encodeFilename(filename
)) 
 464         os
.rename(encodeFilename(temp_filename
), encodeFilename(filename
)) 
 466         return True, information