cis notified web server of a job completion; upload form interface and validation...
[living-lab-site.git] / cis / api / ffmpeg.py
index 35650ee..1974497 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 
 #!/usr/bin/env python
 
+
 """
 Classes derived from BaseTranscoder and BaseThumbExtractor for transcoding of
 videos and thumbnail extraction from videos using FFmpeg CLI program.
 """
 Classes derived from BaseTranscoder and BaseThumbExtractor for transcoding of
 videos and thumbnail extraction from videos using FFmpeg CLI program.
@@ -8,7 +9,9 @@ videos and thumbnail extraction from videos using FFmpeg CLI program.
 import base
 import cis_exceptions
 import subprocess
 import base
 import cis_exceptions
 import subprocess
+import re
 import os
 import os
+import math
 
 class FFmpegTranscoder(base.BaseTranscoder):
     """
 
 class FFmpegTranscoder(base.BaseTranscoder):
     """
@@ -17,7 +20,7 @@ class FFmpegTranscoder(base.BaseTranscoder):
 
     prog_bin = "ffmpeg"
 
 
     prog_bin = "ffmpeg"
 
-    log_file = 'log/ffmpeg.log'
+    log_file = 'log/FFmpegTranscoder.log'
 
     containers = {
         "avi": "avi",
 
     containers = {
         "avi": "avi",
@@ -41,7 +44,7 @@ class FFmpegTranscoder(base.BaseTranscoder):
             a_bitrate=None, a_samplingrate=None, a_channels=None,
             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
 
             a_bitrate=None, a_samplingrate=None, a_channels=None,
             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
 
-        args = self.prog_bin + ' -i "' + self.input_file + '" -f ' + container
+        args = self.prog_bin + ' -y -i "' + self.input_file + '" -f ' + container
         
         # Audio
         if a_codec != None:
         
         # Audio
         if a_codec != None:
@@ -70,10 +73,6 @@ class FFmpegTranscoder(base.BaseTranscoder):
         
         # Output file.
         args += ' "' + self.output_file + '"'
         
         # Output file.
         args += ' "' + self.output_file + '"'
-        try:
-            os.unlink(self.output_file)
-        except OSError:
-            pass
             
         # READ handler for process's output.
         p = subprocess.Popen(args, shell=True, 
             
         # READ handler for process's output.
         p = subprocess.Popen(args, shell=True, 
@@ -82,6 +81,7 @@ class FFmpegTranscoder(base.BaseTranscoder):
 
         # WRITE handler for logging.
         log = open(self.log_file, 'w')
 
         # WRITE handler for logging.
         log = open(self.log_file, 'w')
+        log.write(args + '\n')
 
         while True:
             line = pipe.readline()
 
         while True:
             line = pipe.readline()
@@ -89,7 +89,110 @@ class FFmpegTranscoder(base.BaseTranscoder):
                 break
             log.write(line)
 
                 break
             log.write(line)
 
-        if p.poll() > 0:
-            raise cis_exceptions.TranscodingException
+        exit_code = p.wait()
+        if exit_code > 0:
+            raise cis_exceptions.TranscodingException( \
+                    'FFmpeg exited with code ' + str(exit_code) + '.')
 
         log.close()
 
         log.close()
+
+        return self.output_file
+
+
+class FFmpegThumbExtractor(base.BaseThumbExtractor):
+    """
+    FFmpeg CLI API for video thumbnail extraction.
+    """
+
+    prog_bin = "ffmpeg"
+
+    log_file = 'log/FFmpegThumbExtractor.log'
+
+    def extract_thumb(self, seek_pos, resolution="120x90", index=0):
+        output_file = self.get_output_file_name(index)
+
+        args = self.prog_bin + ' -y -i "' + self.input_file \
+                + '" -f rawvideo -vcodec mjpeg' + (' -ss ' + str(seek_pos)) \
+                + " -vframes 1 -an -s " + resolution + ' "' \
+                + output_file + '"'
+
+        # READ handler for process's output.
+        p = subprocess.Popen(args, shell=True,
+                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        pipe = p.stdout
+        
+        # WRITE handler for logging.
+        log = open(self.log_file, 'w')
+        log.write(args + '\n')
+
+        while True:
+            line = pipe.readline()
+            if len(line) == 0:
+                break
+            log.write(line)
+
+        exit_code = p.wait()
+        if exit_code > 0:
+            raise cis_exceptions.ThumbExtractionException( \
+                    'FFmpeg exited with code ' + str(exit_code) + '.')
+
+        # FFmpeg bug: when no key frame is found from seek_pos to the
+        # end of file an empty image file is created.
+        if os.path.getsize(output_file) == 0L:
+            os.unlink(output_file)
+            raise cis_exceptions.ThumbExtractionException( \
+                    'FFmpeg created an empty file.')
+
+    def get_video_duration(self):
+        return FFprobeAVInfo.get_video_duration(self.input_file)
+
+
+class FFprobeAVInfo(base.BaseAVInfo):
+    
+    prog_bin = "ffprobe"
+
+    log_file = 'log/FFprobeAVInfo.log'
+
+    @staticmethod
+    def get_video_duration(input_file, formated=False):
+        args = FFprobeAVInfo.prog_bin + ' -show_format "' \
+                + input_file + '"'
+
+        # READ handler for process's output.
+        p = subprocess.Popen(args, shell=True,
+                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        pipe = p.stdout
+
+        # WRITE handler for logging.
+        log = open(FFprobeAVInfo.log_file, 'w')
+        log.write(args + '\n')
+
+        # Parse ffprobe's output.
+        while True:
+            line = pipe.readline()
+            if len(line) == 0:
+                break
+            log.write(line)
+            
+            # Search for the line which contains duration information.
+            m = re.match(r"duration=([\d\.]+)", line)
+            if m is not None:
+                seconds = float(m.group(1))
+                if not formated:
+                    return seconds
+                else:
+                    seconds = math.floor(seconds)
+                    minutes = math.floor(seconds / 60)
+                    seconds = seconds % 60
+                    if minutes >= 60:
+                        hours = math.floor(minutes / 60)
+                        minutes = minutes % 60
+                        
+                        return "%02d:%02d:%02d" % (hours, minutes, seconds)
+                    else:
+                        return "%02d:%02d" % (minutes, seconds)
+
+        exit_code = p.wait()
+        if exit_code > 0:
+            raise cis_exceptions.AVInfoException( \
+                    'ffprobe exited with code ' + str(exit_code) + '.')