# HG changeset patch # User Thierry Florac # Date 1449243328 -3600 # Node ID 1784464dfe6276630222ea2775216a4e0299b522 # Parent 30a34a5cb19c7ffd099f3f16da0499390caeb0cf Switch to libAV tools and improve errors management diff -r 30a34a5cb19c -r 1784464dfe62 src/pyams_media/ffbase.py --- a/src/pyams_media/ffbase.py Fri Dec 04 16:34:56 2015 +0100 +++ b/src/pyams_media/ffbase.py Fri Dec 04 16:35:28 2015 +0100 @@ -49,64 +49,96 @@ re_videoline = re.compile("^\s*Stream #(\d+:\d+?)\(?([A-Za-z]*)\)?: Video: (.*?), (.*?), (.*?), (.*?)$") re_audioline = re.compile("^\s*Stream #(\d+:\d+?)\(?([A-Za-z]*)\)?: Audio: (.*?), (\d+?) Hz, (.*?), (.*?), (\d+?) kb\/s$") - def __init__(self, cmd="ffmpeg"): + def __init__(self, cmd="avconv"): self.__ffmpeg__ = cmd - def __exec__(self, *args): + def __exec__(self, *args, **kwargs): """Build and execute a command line""" + + def create_temp_file(arg): + suffix = '.tmp' + if IFile.providedBy(arg): + # IFile object + content_type = get_magic_content_type(arg.data) + if content_type: + suffix = mimetypes.guess_extension(content_type) or suffix + output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) + output.write(arg.data) + elif hasattr(arg, 'read'): + # file-like object + arg.reset() + content_type = get_magic_content_type(arg.read(4096)) + if content_type: + suffix = mimetypes.guess_extension(content_type) or suffix + output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) + try: + arg.reset() + except: + pass + data = arg.read(INPUT_BLOCK_SIZE) + while data: + output.write(data) + data = arg.read(INPUT_BLOCK_SIZE) + else: + # string or bytes + content_type = get_magic_content_type(arg) + if content_type: + suffix = mimetypes.guess_extension(content_type) or suffix + output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) + output.write(arg) + output.file.flush() + return output + cmdline = [self.__ffmpeg__] - if self.__ffmpeg__ == 'ffmpeg': - cmdline.append('-y') + if self.__ffmpeg__ == 'avconv': + cmdline.extend(['-y', '-loglevel', 'error']) + allow_stdin = kwargs.get('allow_stdin', True) use_stdin = None for arg in args: if IFile.providedBy(arg): if len(args) == 2: # FFmpeg can't get media info from an input pipe # We have to write media content to temporary file - suffix = '.tmp' - content_type = get_magic_content_type(arg.data) - if content_type: - suffix = mimetypes.guess_extension(content_type) or suffix - output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) - output.write(arg.data) - output.file.flush() + output = create_temp_file(arg) cmdline.append(output.name) else: - use_stdin = arg - cmdline.append('-') + if allow_stdin: + use_stdin = arg + cmdline.append('-') + else: + output = create_temp_file(arg) + cmdline.append(output.name) elif hasattr(arg, 'read'): # StringIO or any file like object if len(args) == 2: # FFmpeg can't get media info from an input pipe # We have to write media content to temporary file - arg.reset() - content_type = get_magic_content_type(arg.read(4096)) - suffix = mimetypes.guess_extension(content_type) if content_type else '.tmp' - output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) - try: - arg.reset() - except: - pass - data = arg.read(INPUT_BLOCK_SIZE) - while data: - output.write(data) - data = arg.read(INPUT_BLOCK_SIZE) - output.file.flush() + output = create_temp_file(arg) cmdline.append(output.name) else: - use_stdin = arg - cmdline.append('-') + if allow_stdin: + use_stdin = arg + cmdline.append('-') + else: + output = create_temp_file(arg) + cmdline.append(output.name) else: cmdline.append(arg) - logger.debug("Running FFmpeg command line: {0}".format(cmdline)) + logger.debug("Running libAV command line: {0}".format(cmdline)) p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE) if use_stdin is not None: if IFile.providedBy(use_stdin): - return p.communicate(use_stdin.data) + stdout, stderr = p.communicate(use_stdin.data) else: use_stdin.reset() - return p.communicate(use_stdin.read()) + stdout, stderr = p.communicate(use_stdin.read()) + if b'partial file' in stderr: + # the video file can't be converted through pipe... + logger.debug("Can't use piped conversion. Switch to temporary file...") + return self.__exec__(*args, allow_stdin=False) else: - return p.communicate() + stdout, stderr = p.communicate() + logger.debug(stderr) + return stdout, stderr def render(self, effectchain, output): """Create a new file by chaining audio/video effects""" @@ -148,10 +180,11 @@ metadata = [] if IFile.providedBy(input) or isinstance(input, str) or hasattr(input, 'read'): input = [input, ] - for i in range(0, len(input) * 2, 2): - input.insert(i, "-i") - if self.__ffmpeg__ == 'ffprobe': - input.extend(['-show_format', '-show_streams', '-print_format', 'json']) + if self.__ffmpeg__ == 'avconv': + for i in range(0, len(input) * 2, 2): + input.insert(i, "-i") + if self.__ffmpeg__ == 'avprobe': + input.extend(['-show_format', '-show_streams', '-of', 'json']) probe = self.__exec__(*input)[0] # stdout metadata = json.loads(probe.decode()) else: @@ -222,11 +255,14 @@ cmd.append("%s" % value) return cmd - def get_output(self, format=None, target='-', get_stderr=False): + def get_output(self, format=None, target='-'): if (format is None) and hasattr(self, '__metadata__'): format = self.__metadata__.get('vtype') - stdout, stderr = FFmpeg().__exec__(*self.cmdline() + ['-f', format, target]) - return (stdout, stderr) if get_stderr else stdout + cmdline = self.cmdline() + ['-f', format, target] + stdout, stderr = FFmpeg().__exec__(*cmdline) + return {'output': stdout, + 'errors': stderr, + 'cmdline': ' '.join(map(str, cmdline))} def restore(self): """ @@ -288,11 +324,11 @@ def bitrate(self, b=None): """ set video bitrate """ if b: - self.__effects__["b"] = "%sk" % int(b) - return self.__effects__.get("b") + self.__effects__["b:v"] = "%sk" % int(b) + return self.__effects__.get("b:v") def unset_bitrate(self): - del self.__effects__["b"] + del self.__effects__["b:v"] def vframes(self, vframes=None): """ set number of video frames to record """ @@ -577,11 +613,11 @@ def audiobitrate(self, ab=64): """ set audio bitrate (kbit/s)""" - self.__effects__["ab"] = int(ab) - return self.__effects__["ab"] + self.__effects__["b:a"] = "%sk" % int(ab) + return self.__effects__["b:a"] def unset_audiobitrate(self): - del self.__effects__["ab"] + del self.__effects__["b:a"] def audiochannels(self, ac=1): """ set number of audio channels """ diff -r 30a34a5cb19c -r 1784464dfe62 src/pyams_media/ffdocument.py --- a/src/pyams_media/ffdocument.py Fri Dec 04 16:34:56 2015 +0100 +++ b/src/pyams_media/ffdocument.py Fri Dec 04 16:35:28 2015 +0100 @@ -36,7 +36,7 @@ if info is not None: self.__metadata__ = info else: - info = FFmpeg('ffprobe').info(file) + info = FFmpeg('avprobe').info(file) if info: self.__metadata__ = info else: