Switch to libAV tools and improve errors management
authorThierry Florac <thierry.florac@onf.fr>
Fri, 04 Dec 2015 16:35:28 +0100
changeset 21 1784464dfe62
parent 20 30a34a5cb19c
child 22 b5da674fd06c
Switch to libAV tools and improve errors management
src/pyams_media/ffbase.py
src/pyams_media/ffdocument.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 """
--- 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: