]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/PostProcessor.py
2 # -*- coding: utf-8 -*-
12 class PostProcessor(object):
13 """Post Processor class.
15 PostProcessor objects can be added to downloaders with their
16 add_post_processor() method. When the downloader has finished a
17 successful download, it will take its internal chain of PostProcessors
18 and start calling the run() method on each one of them, first with
19 an initial argument and then with the returned value of the previous
22 The chain will be stopped if one of them ever returns None or the end
23 of the chain is reached.
25 PostProcessor objects follow a "mutual registration" process similar
26 to InfoExtractor objects.
31 def __init__(self
, downloader
=None):
32 self
._downloader
= downloader
34 def set_downloader(self
, downloader
):
35 """Sets the downloader for this PP."""
36 self
._downloader
= downloader
38 def run(self
, information
):
39 """Run the PostProcessor.
41 The "information" argument is a dictionary like the ones
42 composed by InfoExtractors. The only difference is that this
43 one has an extra field called "filepath" that points to the
46 When this method returns None, the postprocessing chain is
47 stopped. However, this method may return an information
48 dictionary that will be passed to the next postprocessing
49 object in the chain. It can be the one it received after
52 In addition, this method may raise a PostProcessingError
53 exception that will be taken into account by the downloader
56 return information
# by default, do nothing
58 class AudioConversionError(BaseException
):
59 def __init__(self
, message
):
60 self
.message
= message
62 class FFmpegExtractAudioPP(PostProcessor
):
63 def __init__(self
, downloader
=None, preferredcodec
=None, preferredquality
=None, keepvideo
=False):
64 PostProcessor
.__init
__(self
, downloader
)
65 if preferredcodec
is None:
66 preferredcodec
= 'best'
67 self
._preferredcodec
= preferredcodec
68 self
._preferredquality
= preferredquality
69 self
._keepvideo
= keepvideo
70 self
._exes
= self
.detect_executables()
73 def detect_executables():
74 available
= {'avprobe' : False, 'avconv' : False, 'ffmpeg' : False, 'ffprobe' : False}
75 for path
in os
.environ
["PATH"].split(os
.pathsep
):
76 for program
in available
.keys():
77 exe_file
= os
.path
.join(path
, program
)
78 if os
.path
.isfile(exe_file
) and os
.access(exe_file
, os
.X_OK
):
79 available
[program
] = exe_file
82 def get_audio_codec(self
, path
):
83 if not self
._exes
['ffprobe'] and not self
._exes
['avprobe']: return None
85 cmd
= [self
._exes
['avprobe'] or self
._exes
['ffprobe'], '-show_streams', '--', encodeFilename(path
)]
86 handle
= subprocess
.Popen(cmd
, stderr
=file(os
.path
.devnull
, 'w'), stdout
=subprocess
.PIPE
)
87 output
= handle
.communicate()[0]
88 if handle
.wait() != 0:
90 except (IOError, OSError):
93 for line
in output
.split('\n'):
94 if line
.startswith('codec_name='):
95 audio_codec
= line
.split('=')[1].strip()
96 elif line
.strip() == 'codec_type=audio' and audio_codec
is not None:
100 def run_ffmpeg(self
, path
, out_path
, codec
, more_opts
):
101 if not self
._exes
['ffmpeg'] and not self
._exes
['avconv']:
102 raise AudioConversionError('ffmpeg or avconv not found. Please install one.')
106 acodec_opts
= ['-acodec', codec
]
107 cmd
= ([self
._exes
['avconv'] or self
._exes
['ffmpeg'], '-y', '-i', encodeFilename(path
), '-vn']
108 + acodec_opts
+ more_opts
+
109 ['--', encodeFilename(out_path
)])
110 p
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
111 stdout
,stderr
= p
.communicate()
112 if p
.returncode
!= 0:
113 msg
= stderr
.strip().split('\n')[-1]
114 raise AudioConversionError(msg
)
116 def run(self
, information
):
117 path
= information
['filepath']
119 filecodec
= self
.get_audio_codec(path
)
120 if filecodec
is None:
121 self
._downloader
.to_stderr(u
'WARNING: unable to obtain file audio codec with ffprobe')
125 if self
._preferredcodec
== 'best' or self
._preferredcodec
== filecodec
or (self
._preferredcodec
== 'm4a' and filecodec
== 'aac'):
126 if self
._preferredcodec
== 'm4a' and filecodec
== 'aac':
127 # Lossless, but in another container
129 extension
= self
._preferredcodec
130 more_opts
= [self
._exes
['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc']
131 elif filecodec
in ['aac', 'mp3', 'vorbis']:
132 # Lossless if possible
134 extension
= filecodec
135 if filecodec
== 'aac':
136 more_opts
= ['-f', 'adts']
137 if filecodec
== 'vorbis':
141 acodec
= 'libmp3lame'
144 if self
._preferredquality
is not None:
145 more_opts
+= [self
._exes
['avconv'] and '-b:a' or '-ab', self
._preferredquality
]
147 # We convert the audio (lossy)
148 acodec
= {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'vorbis': 'libvorbis', 'wav': None}[self
._preferredcodec
]
149 extension
= self
._preferredcodec
151 if self
._preferredquality
is not None:
152 more_opts
+= [self
._exes
['avconv'] and '-b:a' or '-ab', self
._preferredquality
]
153 if self
._preferredcodec
== 'aac':
154 more_opts
+= ['-f', 'adts']
155 if self
._preferredcodec
== 'm4a':
156 more_opts
+= [self
._exes
['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc']
157 if self
._preferredcodec
== 'vorbis':
159 if self
._preferredcodec
== 'wav':
161 more_opts
+= ['-f', 'wav']
163 prefix
, sep
, ext
= path
.rpartition(u
'.') # not os.path.splitext, since the latter does not work on unicode in all setups
164 new_path
= prefix
+ sep
+ extension
165 self
._downloader
.to_screen(u
'[' + (self
._exes
['avconv'] and 'avconv' or 'ffmpeg') + '] Destination: ' + new_path
)
167 self
.run_ffmpeg(path
, new_path
, acodec
, more_opts
)
169 etype
,e
,tb
= sys
.exc_info()
170 if isinstance(e
, AudioConversionError
):
171 self
._downloader
.to_stderr(u
'ERROR: audio conversion failed: ' + e
.message
)
173 self
._downloader
.to_stderr(u
'ERROR: error running ' + (self
._exes
['avconv'] and 'avconv' or 'ffmpeg'))
176 # Try to update the date time for extracted audio file.
177 if information
.get('filetime') is not None:
179 os
.utime(encodeFilename(new_path
), (time
.time(), information
['filetime']))
181 self
._downloader
.to_stderr(u
'WARNING: Cannot update utime of audio file')
183 if not self
._keepvideo
:
185 os
.remove(encodeFilename(path
))
186 except (IOError, OSError):
187 self
._downloader
.to_stderr(u
'WARNING: Unable to remove downloaded video file')
190 information
['filepath'] = new_path