]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/PostProcessor.py
fddf58606015b92cc21a9f89818c90852c365e83
   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(self
, path
, 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.') 
  77         cmd 
= ([self
._exes
['avconv'] or self
._exes
['ffmpeg'], '-y', '-i', encodeFilename(path
)] 
  79                [encodeFilename(self
._ffmpeg
_filename
_argument
(out_path
))]) 
  80         p 
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
) 
  81         stdout
,stderr 
= p
.communicate() 
  83             stderr 
= stderr
.decode('utf-8', 'replace') 
  84             msg 
= stderr
.strip().split('\n')[-1] 
  85             raise FFmpegPostProcessorError(msg
) 
  87     def _ffmpeg_filename_argument(self
, fn
): 
  88         # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details 
  89         if fn
.startswith(u
'-'): 
  93 class FFmpegExtractAudioPP(FFmpegPostProcessor
): 
  94     def __init__(self
, downloader
=None, preferredcodec
=None, preferredquality
=None, nopostoverwrites
=False): 
  95         FFmpegPostProcessor
.__init
__(self
, downloader
) 
  96         if preferredcodec 
is None: 
  97             preferredcodec 
= 'best' 
  98         self
._preferredcodec 
= preferredcodec
 
  99         self
._preferredquality 
= preferredquality
 
 100         self
._nopostoverwrites 
= nopostoverwrites
 
 102     def get_audio_codec(self
, path
): 
 103         if not self
._exes
['ffprobe'] and not self
._exes
['avprobe']: 
 104             raise PostProcessingError(u
'ffprobe or avprobe not found. Please install one.') 
 106             cmd 
= [self
._exes
['avprobe'] or self
._exes
['ffprobe'], '-show_streams', encodeFilename(self
._ffmpeg
_filename
_argument
(path
))] 
 107             handle 
= subprocess
.Popen(cmd
, stderr
=compat_subprocess_get_DEVNULL(), stdout
=subprocess
.PIPE
) 
 108             output 
= handle
.communicate()[0] 
 109             if handle
.wait() != 0: 
 111         except (IOError, OSError): 
 114         for line 
in output
.decode('ascii', 'ignore').split('\n'): 
 115             if line
.startswith('codec_name='): 
 116                 audio_codec 
= line
.split('=')[1].strip() 
 117             elif line
.strip() == 'codec_type=audio' and audio_codec 
is not None: 
 121     def run_ffmpeg(self
, path
, out_path
, codec
, more_opts
): 
 122         if not self
._exes
['ffmpeg'] and not self
._exes
['avconv']: 
 123             raise AudioConversionError('ffmpeg or avconv not found. Please install one.') 
 127             acodec_opts 
= ['-acodec', codec
] 
 128         opts 
= ['-vn'] + acodec_opts 
+ more_opts
 
 130             FFmpegPostProcessor
.run_ffmpeg(self
, path
, out_path
, opts
) 
 131         except FFmpegPostProcessorError 
as err
: 
 132             raise AudioConversionError(err
.message
) 
 134     def run(self
, information
): 
 135         path 
= information
['filepath'] 
 137         filecodec 
= self
.get_audio_codec(path
) 
 138         if filecodec 
is None: 
 139             raise PostProcessingError(u
'WARNING: unable to obtain file audio codec with ffprobe') 
 142         if self
._preferredcodec 
== 'best' or self
._preferredcodec 
== filecodec 
or (self
._preferredcodec 
== 'm4a' and filecodec 
== 'aac'): 
 143             if filecodec 
== 'aac' and self
._preferredcodec 
in ['m4a', 'best']: 
 144                 # Lossless, but in another container 
 147                 more_opts 
= [self
._exes
['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc'] 
 148             elif filecodec 
in ['aac', 'mp3', 'vorbis', 'opus']: 
 149                 # Lossless if possible 
 151                 extension 
= filecodec
 
 152                 if filecodec 
== 'aac': 
 153                     more_opts 
= ['-f', 'adts'] 
 154                 if filecodec 
== 'vorbis': 
 158                 acodec 
= 'libmp3lame' 
 161                 if self
._preferredquality 
is not None: 
 162                     if int(self
._preferredquality
) < 10: 
 163                         more_opts 
+= [self
._exes
['avconv'] and '-q:a' or '-aq', self
._preferredquality
] 
 165                         more_opts 
+= [self
._exes
['avconv'] and '-b:a' or '-ab', self
._preferredquality 
+ 'k'] 
 167             # We convert the audio (lossy) 
 168             acodec 
= {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'opus': 'opus', 'vorbis': 'libvorbis', 'wav': None}[self
._preferredcodec
] 
 169             extension 
= self
._preferredcodec
 
 171             if self
._preferredquality 
is not None: 
 172                 if int(self
._preferredquality
) < 10: 
 173                     more_opts 
+= [self
._exes
['avconv'] and '-q:a' or '-aq', self
._preferredquality
] 
 175                     more_opts 
+= [self
._exes
['avconv'] and '-b:a' or '-ab', self
._preferredquality 
+ 'k'] 
 176             if self
._preferredcodec 
== 'aac': 
 177                 more_opts 
+= ['-f', 'adts'] 
 178             if self
._preferredcodec 
== 'm4a': 
 179                 more_opts 
+= [self
._exes
['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc'] 
 180             if self
._preferredcodec 
== 'vorbis': 
 182             if self
._preferredcodec 
== 'wav': 
 184                 more_opts 
+= ['-f', 'wav'] 
 186         prefix
, sep
, ext 
= path
.rpartition(u
'.') # not os.path.splitext, since the latter does not work on unicode in all setups 
 187         new_path 
= prefix 
+ sep 
+ extension
 
 189         # If we download foo.mp3 and convert it to... foo.mp3, then don't delete foo.mp3, silly. 
 191             self
._nopostoverwrites 
= True 
 194             if self
._nopostoverwrites 
and os
.path
.exists(encodeFilename(new_path
)): 
 195                 self
._downloader
.to_screen(u
'[youtube] Post-process file %s exists, skipping' % new_path
) 
 197                 self
._downloader
.to_screen(u
'[' + (self
._exes
['avconv'] and 'avconv' or 'ffmpeg') + '] Destination: ' + new_path
) 
 198                 self
.run_ffmpeg(path
, new_path
, acodec
, more_opts
) 
 200             etype
,e
,tb 
= sys
.exc_info() 
 201             if isinstance(e
, AudioConversionError
): 
 202                 msg 
= u
'audio conversion failed: ' + e
.message
 
 204                 msg 
= u
'error running ' + (self
._exes
['avconv'] and 'avconv' or 'ffmpeg') 
 205             raise PostProcessingError(msg
) 
 207         # Try to update the date time for extracted audio file. 
 208         if information
.get('filetime') is not None: 
 210                 os
.utime(encodeFilename(new_path
), (time
.time(), information
['filetime'])) 
 212                 self
._downloader
.report_warning(u
'Cannot update utime of audio file') 
 214         information
['filepath'] = new_path
 
 215         return self
._nopostoverwrites
,information
 
 217 class FFmpegVideoConvertor(FFmpegPostProcessor
): 
 218     def __init__(self
, downloader
=None,preferedformat
=None): 
 219         super(FFmpegVideoConvertor
, self
).__init
__(downloader
) 
 220         self
._preferedformat
=preferedformat
 
 222     def run(self
, information
): 
 223         path 
= information
['filepath'] 
 224         prefix
, sep
, ext 
= path
.rpartition(u
'.') 
 225         outpath 
= prefix 
+ sep 
+ self
._preferedformat
 
 226         if information
['ext'] == self
._preferedformat
: 
 227             self
._downloader
.to_screen(u
'[ffmpeg] Not converting video file %s - already is in target format %s' % (path
, self
._preferedformat
)) 
 228             return True,information
 
 229         self
._downloader
.to_screen(u
'['+'ffmpeg'+'] Converting video from %s to %s, Destination: ' % (information
['ext'], self
._preferedformat
) +outpath
) 
 230         self
.run_ffmpeg(path
, outpath
, []) 
 231         information
['filepath'] = outpath
 
 232         information
['format'] = self
._preferedformat
 
 233         information
['ext'] = self
._preferedformat
 
 234         return False,information