From 488447455d3d90e1d83a7ebc2f9ce552e031e0d8 Mon Sep 17 00:00:00 2001 From: Philipp Hagemeister Date: Sun, 26 Oct 2014 16:46:34 +0100 Subject: [PATCH] [ffmpeg] Warn if ffmpeg/avconv version is too old (Fixes #4026) --- youtube_dl/YoutubeDL.py | 2 +- youtube_dl/postprocessor/ffmpeg.py | 28 ++++++++++++++++++++-------- youtube_dl/utils.py | 13 +++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index ecf426e37..28dcc0195 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -1026,7 +1026,7 @@ class YoutubeDL(object): downloaded = [] success = True merger = FFmpegMergerPP(self, not self.params.get('keepvideo')) - if not merger._get_executable(): + if not merger._executable: postprocessors = [] self.report_warning('You have requested multiple ' 'formats but ffmpeg or avconv are not installed.' diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py index 867e75d80..e13deab4b 100644 --- a/youtube_dl/postprocessor/ffmpeg.py +++ b/youtube_dl/postprocessor/ffmpeg.py @@ -12,6 +12,7 @@ from ..utils import ( compat_subprocess_get_DEVNULL, encodeArgument, encodeFilename, + is_outdated_version, PostProcessingError, prepend_extension, shell_quote, @@ -41,17 +42,29 @@ class FFmpegPostProcessorError(PostProcessingError): class FFmpegPostProcessor(PostProcessor): - def __init__(self, downloader=None, deletetempfiles=False): + def __init__(self, downloader, deletetempfiles=False): PostProcessor.__init__(self, downloader) self._versions = self.get_versions() self._deletetempfiles = deletetempfiles + def check_version(self): + if not self._executable: + raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.') + + REQUIRED_VERSION = '1.0' + if is_outdated_version( + self._versions[self._executable], REQUIRED_VERSION): + warning = u'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % ( + self._executable, self._executable, REQUIRED_VERSION) + self._downloader.report_warning(warning) + @staticmethod def get_versions(): programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe'] return dict((program, get_version(program)) for program in programs) - def _get_executable(self): + @property + def _executable(self): if self._downloader.params.get('prefer_ffmpeg', False): prefs = ('ffmpeg', 'avconv') else: @@ -62,16 +75,15 @@ class FFmpegPostProcessor(PostProcessor): return None def _uses_avconv(self): - return self._get_executable() == 'avconv' + return self._executable == 'avconv' def run_ffmpeg_multiple_files(self, input_paths, out_path, opts): - if not self._get_executable(): - raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.') + self.check_version() files_cmd = [] for path in input_paths: files_cmd.extend(['-i', encodeFilename(path, True)]) - cmd = ([self._get_executable(), '-y'] + files_cmd + cmd = ([self._executable, '-y'] + files_cmd + [encodeArgument(o) for o in opts] + [encodeFilename(self._ffmpeg_filename_argument(out_path), True)]) @@ -204,14 +216,14 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)): self._downloader.to_screen(u'[youtube] Post-process file %s exists, skipping' % new_path) else: - self._downloader.to_screen(u'[' + self._get_executable() + '] Destination: ' + new_path) + self._downloader.to_screen(u'[' + self._executable + '] Destination: ' + new_path) self.run_ffmpeg(path, new_path, acodec, more_opts) except: etype,e,tb = sys.exc_info() if isinstance(e, AudioConversionError): msg = u'audio conversion failed: ' + e.msg else: - msg = u'error running ' + self._get_executable() + msg = u'error running ' + self._executable raise PostProcessingError(msg) # Try to update the date time for extracted audio file. diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 9287edd8d..6c0c39ca5 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -1723,3 +1723,16 @@ def limit_length(s, length): if len(s) > length: return s[:length - len(ELLIPSES)] + ELLIPSES return s + + +def version_tuple(v): + return [int(e) for e in v.split('.')] + + +def is_outdated_version(version, limit, assume_new=True): + if not version: + return not assume_new + try: + return version_tuple(version) < version_tuple(limit) + except ValueError: + return not assume_new