X-Git-Url: http://p2p-next.cs.pub.ro/gitweb/?p=living-lab-site.git;a=blobdiff_plain;f=cis%2Fapi%2Fffmpeg.py;h=197449788ad6e529956ee7354a72e072bf65ec25;hp=35650eeb9348ca9dca85d40a29859563454e02ba;hb=080b37a97e93691b3ba1c4aa3982a143167115a7;hpb=42cb85273f6ef35a40c183f1184313bbc06afa05 diff --git a/cis/api/ffmpeg.py b/cis/api/ffmpeg.py index 35650ee..1974497 100644 --- a/cis/api/ffmpeg.py +++ b/cis/api/ffmpeg.py @@ -1,5 +1,6 @@ #!/usr/bin/env python + """ 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 re import os +import math class FFmpegTranscoder(base.BaseTranscoder): """ @@ -17,7 +20,7 @@ class FFmpegTranscoder(base.BaseTranscoder): prog_bin = "ffmpeg" - log_file = 'log/ffmpeg.log' + log_file = 'log/FFmpegTranscoder.log' 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): - 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: @@ -70,10 +73,6 @@ class FFmpegTranscoder(base.BaseTranscoder): # 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, @@ -82,6 +81,7 @@ class FFmpegTranscoder(base.BaseTranscoder): # WRITE handler for logging. log = open(self.log_file, 'w') + log.write(args + '\n') while True: line = pipe.readline() @@ -89,7 +89,110 @@ class FFmpegTranscoder(base.BaseTranscoder): 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() + + 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) + '.')