058c9b7faf32e66bfe2e7e71d53c894f71c874a8
[living-lab-site.git] / cis / api / avhandling.py
1 #!/usr/bin/env python
2
3
4 """
5 Classes derived from BaseTranscoder and BaseThumbExtractor for transcoding of
6 videos and thumbnail extraction from videos using FFmpeg CLI program.
7 """
8
9 import base
10 import api_exceptions
11 import subprocess
12 import re
13 import os
14 import math
15
16 class FFmpegTranscoder(base.BaseTranscoder):
17     """
18     FFmpeg CLI API for video transcoding.
19     """
20
21     prog_bin = "ffmpeg"
22
23     log_file = 'log/FFmpegTranscoder.log'
24
25     containers = {
26         "avi": "avi",
27         "flv": "flv",
28         "mp4": "mp4",
29         "ogg": "ogg",
30         "webm": "webm",
31         "mpegts": "mpegts"
32     }
33     a_codecs = {
34         "mp3": "libmp3lame",
35         "vorbis": "libvorbis"
36     }
37     v_codecs = {
38         "h264": "libx264",
39         "theora": "libtheora",
40         "vp8": "libvpx"
41     }
42
43     def _transcode(self, container, a_codec=None, v_codec=None,
44             a_bitrate=None, a_samplingrate=None, a_channels=None,
45             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
46
47         args = self.prog_bin + ' -y -i "' + self.input_file + '" -f ' + container
48         
49         # Audio
50         if a_codec != None:
51             args += ' -acodec ' + a_codec
52             if a_bitrate != None:
53                 args += ' -ab ' + str(a_bitrate)
54             if a_samplingrate != None:
55                 args += ' -ar ' + str(a_samplingrate)
56             if a_channels != None:
57                 args += ' -ac ' + str(a_channels)
58         
59         # Video
60         if v_codec != None:
61             args += ' -vcodec ' + v_codec
62             # Video codec specific options.
63             if v_codec == 'libx264':
64                 args += ' -vpre normal'
65             if v_bitrate != None:
66                 args += ' -b ' + str(v_bitrate)
67             if v_framerate != None:
68                 args += ' -r ' + str(v_framerate)
69             if v_resolution != None:
70                 args += ' -s ' + v_resolution
71             if v_dar != None:
72                 args += ' -aspect ' + v_dar
73         
74         # Output file.
75         args += ' "' + self.output_file + '"'
76             
77         # READ handler for process's output.
78         p = subprocess.Popen(args, shell=True, 
79                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
80         pipe = p.stdout
81
82         # WRITE handler for logging.
83         log = open(self.log_file, 'w')
84         log.write(args + '\n')
85
86         while True:
87             line = pipe.readline()
88             if len(line) == 0:
89                 break
90             log.write(line)
91
92         exit_code = p.wait()
93         if exit_code > 0:
94             raise api_exceptions.TranscodingException( \
95                     'FFmpeg exited with code ' + str(exit_code) + '.')
96
97         log.close()
98
99
100 class FFmpegThumbExtractor(base.BaseThumbExtractor):
101     """
102     FFmpeg CLI API for video thumbnail extraction.
103     """
104
105     prog_bin = "ffmpeg"
106
107     log_file = 'log/FFmpegThumbExtractor.log'
108
109     def extract_thumb(self, seek_pos, resolution="120x90", index=0):
110         output_file = self.get_output_file_name(index)
111
112         args = self.prog_bin + ' -y -i "' + self.input_file \
113                 + '" -f rawvideo -vcodec mjpeg' + (' -ss ' + str(seek_pos)) \
114                 + " -vframes 1 -an -s " + resolution + ' "' \
115                 + output_file + '"'
116
117         # READ handler for process's output.
118         p = subprocess.Popen(args, shell=True,
119                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
120         pipe = p.stdout
121         
122         # WRITE handler for logging.
123         log = open(self.log_file, 'w')
124         log.write(args + '\n')
125
126         while True:
127             line = pipe.readline()
128             if len(line) == 0:
129                 break
130             log.write(line)
131
132         exit_code = p.wait()
133         if exit_code > 0:
134             raise api_exceptions.ThumbExtractionException( \
135                     'FFmpeg exited with code ' + str(exit_code) + '.')
136
137         # FFmpeg bug: when no key frame is found from seek_pos to the
138         # end of file an empty image file is created.
139         if os.path.getsize(output_file) == 0L:
140             os.unlink(output_file)
141             raise api_exceptions.ThumbExtractionException( \
142                     'FFmpeg created an empty file.')
143
144     def get_video_duration(self):
145         return FFmpegAVInfo.get_video_duration(self.input_file)
146
147
148 class FFmpegAVInfo(base.BaseAVInfo):
149     
150     prog_bin = "ffprobe"
151
152     @staticmethod
153     def get_video_duration(input_file, formated=False):
154         args = FFmpegAVInfo.prog_bin + ' -show_format "' \
155                 + input_file + '"'
156
157         # READ handler for process's output.
158         p = subprocess.Popen(args, shell=True,
159                 stdout=subprocess.PIPE, stderr=open(os.devnull, 'w'))
160         pipe = p.stdout
161
162         # Parse ffprobe's output.
163         while True:
164             line = pipe.readline()
165             if len(line) == 0:
166                 break
167             
168             # Search for the line which contains duration information.
169             m = re.match(r"duration=([\d\.]+)", line)
170             if m is not None:
171                 seconds = float(m.group(1))
172                 if not formated:
173                     return seconds
174                 else:
175                     seconds = math.floor(seconds)
176                     minutes = math.floor(seconds / 60)
177                     seconds = seconds % 60
178                     if minutes >= 60:
179                         hours = math.floor(minutes / 60)
180                         minutes = minutes % 60
181                         
182                         return "%02d:%02d:%02d" % (hours, minutes, seconds)
183                     else:
184                         return "%02d:%02d" % (minutes, seconds)
185
186         exit_code = p.wait()
187         if exit_code > 0:
188             raise api_exceptions.AVInfoException( \
189                     'ffprobe exited with code ' + str(exit_code) + '.')