From 0b976545c70f9788157d1b224df4008471c7817b Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 31 May 2012 21:03:29 +0200 Subject: [PATCH 1/2] check for avconv and ffmpeg, use as available; closes #344 --- youtube_dl/PostProcessor.py | 44 +++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/youtube_dl/PostProcessor.py b/youtube_dl/PostProcessor.py index 52e686a7f..5971f0897 100644 --- a/youtube_dl/PostProcessor.py +++ b/youtube_dl/PostProcessor.py @@ -60,7 +60,6 @@ class AudioConversionError(BaseException): self.message = message class FFmpegExtractAudioPP(PostProcessor): - def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, keepvideo=False): PostProcessor.__init__(self, downloader) if preferredcodec is None: @@ -68,11 +67,22 @@ class FFmpegExtractAudioPP(PostProcessor): self._preferredcodec = preferredcodec self._preferredquality = preferredquality self._keepvideo = keepvideo + self._exes = self.detect_executables() @staticmethod - def get_audio_codec(path): + def detect_executables(): + available = {'avprobe' : False, 'avconv' : False, 'ffmpeg' : False, 'ffprobe' : False} + for path in os.environ["PATH"].split(os.pathsep): + for program in available.keys(): + exe_file = os.path.join(path, program) + if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK): + available[program] = exe_file + return available + + def get_audio_codec(self, path): + if not self._exes['ffprobe'] and not self._exes['avprobe']: return None try: - cmd = ['ffprobe', '-show_streams', '--', encodeFilename(path)] + cmd = [self._exes['avprobe'] or self._exes['ffprobe'], '-show_streams', '--', encodeFilename(path)] handle = subprocess.Popen(cmd, stderr=file(os.path.devnull, 'w'), stdout=subprocess.PIPE) output = handle.communicate()[0] if handle.wait() != 0: @@ -87,22 +97,18 @@ class FFmpegExtractAudioPP(PostProcessor): return audio_codec return None - @staticmethod - def run_ffmpeg(path, out_path, codec, more_opts): + def run_ffmpeg(self, path, out_path, codec, more_opts): + if not self._exes['ffmpeg'] and not self._exes['avconv']: + raise AudioConversionError('ffmpeg or avconv not found. Please install avconv.') if codec is None: acodec_opts = [] else: acodec_opts = ['-acodec', codec] - cmd = ['ffmpeg', '-y', '-i', encodeFilename(path), '-vn'] + acodec_opts + more_opts + ['--', encodeFilename(out_path)] - try: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout,stderr = p.communicate() - except (IOError, OSError): - e = sys.exc_info()[1] - if isinstance(e, OSError) and e.errno == 2: - raise AudioConversionError('ffmpeg not found. Please install ffmpeg.') - else: - raise e + cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y', '-i', encodeFilename(path), '-vn'] + + acodec_opts + more_opts + + ['--', encodeFilename(out_path)]) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout,stderr = p.communicate() if p.returncode != 0: msg = stderr.strip().split('\n')[-1] raise AudioConversionError(msg) @@ -121,7 +127,7 @@ class FFmpegExtractAudioPP(PostProcessor): # Lossless, but in another container acodec = 'copy' extension = self._preferredcodec - more_opts = ['-absf', 'aac_adtstoasc'] + more_opts = [self._exes['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc'] elif filecodec in ['aac', 'mp3', 'vorbis']: # Lossless if possible acodec = 'copy' @@ -136,18 +142,18 @@ class FFmpegExtractAudioPP(PostProcessor): extension = 'mp3' more_opts = [] if self._preferredquality is not None: - more_opts += ['-ab', self._preferredquality] + more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality] else: # We convert the audio (lossy) acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'vorbis': 'libvorbis', 'wav': None}[self._preferredcodec] extension = self._preferredcodec more_opts = [] if self._preferredquality is not None: - more_opts += ['-ab', self._preferredquality] + more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality] if self._preferredcodec == 'aac': more_opts += ['-f', 'adts'] if self._preferredcodec == 'm4a': - more_opts += ['-absf', 'aac_adtstoasc'] + more_opts += [self._exes['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc'] if self._preferredcodec == 'vorbis': extension = 'ogg' if self._preferredcodec == 'wav': From 505ed3088fdb1a1276de3403ef0fa19890138f48 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 31 May 2012 22:42:25 +0200 Subject: [PATCH 2/2] normalize ffmpeg/avconv names printing --- README.md | 5 +++-- youtube_dl/PostProcessor.py | 6 +++--- youtube_dl/__init__.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 859ca663c..f04b96128 100644 --- a/README.md +++ b/README.md @@ -87,10 +87,11 @@ which means you can modify it, redistribute it or use it however you like. ### Post-processing Options: --extract-audio convert video files to audio-only files (requires - ffmpeg and ffprobe) + ffmpeg or avconv and ffprobe or avprobe) --audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default - --audio-quality QUALITY ffmpeg audio bitrate specification, 128k by default + --audio-quality QUALITY ffmpeg/avconv audio bitrate specification, 128k by + default -k, --keep-video keeps the video file on disk after the post- processing; the video is erased by default diff --git a/youtube_dl/PostProcessor.py b/youtube_dl/PostProcessor.py index 5971f0897..b4262f9e4 100644 --- a/youtube_dl/PostProcessor.py +++ b/youtube_dl/PostProcessor.py @@ -99,7 +99,7 @@ class FFmpegExtractAudioPP(PostProcessor): def run_ffmpeg(self, path, out_path, codec, more_opts): if not self._exes['ffmpeg'] and not self._exes['avconv']: - raise AudioConversionError('ffmpeg or avconv not found. Please install avconv.') + raise AudioConversionError('ffmpeg or avconv not found. Please install one.') if codec is None: acodec_opts = [] else: @@ -162,7 +162,7 @@ class FFmpegExtractAudioPP(PostProcessor): prefix, sep, ext = path.rpartition(u'.') # not os.path.splitext, since the latter does not work on unicode in all setups new_path = prefix + sep + extension - self._downloader.to_screen(u'[ffmpeg] Destination: ' + new_path) + self._downloader.to_screen(u'[' + self._exes['avconv'] and 'avconv' or 'ffmpeg' + '] Destination: ' + new_path) try: self.run_ffmpeg(path, new_path, acodec, more_opts) except: @@ -170,7 +170,7 @@ class FFmpegExtractAudioPP(PostProcessor): if isinstance(e, AudioConversionError): self._downloader.to_stderr(u'ERROR: audio conversion failed: ' + e.message) else: - self._downloader.to_stderr(u'ERROR: error running ffmpeg') + self._downloader.to_stderr(u'ERROR: error running ' + self._exes['avconv'] and 'avconv' or 'ffmpeg') return None # Try to update the date time for extracted audio file. diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 827b58264..f10822db1 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -293,11 +293,11 @@ def parseOpts(): postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False, - help='convert video files to audio-only files (requires ffmpeg and ffprobe)') + help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)') postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best', help='"best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default') postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K', - help='ffmpeg audio bitrate specification, 128k by default') + help='ffmpeg/avconv audio bitrate specification, 128k by default') postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False, help='keeps the video file on disk after the post-processing; the video is erased by default')