8d04272bac19dd2ac0edccf052bba6341dedfb00
[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
15 class FFmpegTranscoder(base.BaseTranscoder):
16     """
17     FFmpeg CLI API for video transcoding.
18     """
19
20     prog_bin = "ffmpeg"
21
22     log_file = 'log/FFmpegTranscoder.log'
23
24     containers = {
25         "avi": "avi",
26         "flv": "flv",
27         "mp4": "mp4",
28         "ogg": "ogg",
29         "webm": "webm",
30         "mpegts": "mpegts"
31     }
32     a_codecs = {
33         "mp3": "libmp3lame",
34         "vorbis": "libvorbis"
35     }
36     v_codecs = {
37         "h264": "libx264",
38         "theora": "libtheora",
39         "vp8": "libvpx"
40     }
41
42     def _transcode(self, container, a_codec=None, v_codec=None,
43             a_bitrate=None, a_samplingrate=None, a_channels=None,
44             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
45
46         args = self.prog_bin + ' -y -i "' + self.input_file + '" -f ' + container
47         
48         # Audio
49         if a_codec != None:
50             args += ' -acodec ' + a_codec
51             if a_bitrate != None:
52                 args += ' -ab ' + str(a_bitrate)
53             if a_samplingrate != None:
54                 args += ' -ar ' + str(a_samplingrate)
55             if a_channels != None:
56                 args += ' -ac ' + str(a_channels)
57         
58         # Video
59         if v_codec != None:
60             args += ' -vcodec ' + v_codec
61             # Video codec specific options.
62             if v_codec == 'libx264':
63                 args += ' -vpre normal'
64             if v_bitrate != None:
65                 args += ' -b ' + str(v_bitrate)
66             if v_framerate != None:
67                 args += ' -r ' + str(v_framerate)
68             if v_resolution != None:
69                 args += ' -s ' + v_resolution
70             if v_dar != None:
71                 args += ' -aspect ' + v_dar
72         
73         # Output file.
74         args += ' "' + self.output_file + '"'
75             
76         # READ handler for process's output.
77         p = subprocess.Popen(args, shell=True, 
78                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
79         pipe = p.stdout
80
81         # WRITE handler for logging.
82         log = open(self.log_file, 'w')
83         log.write(args + '\n')
84
85         while True:
86             line = pipe.readline()
87             if len(line) == 0:
88                 break
89             log.write(line)
90
91         exit_code = p.wait()
92         if exit_code > 0:
93             raise api_exceptions.TranscodingException( \
94                     'FFmpeg exited with code ' + str(exit_code) + '.')
95
96         log.close()
97
98
99 class FFmpegThumbExtractor(base.BaseThumbExtractor):
100     """
101     FFmpeg CLI API for video thumbnail extraction.
102     """
103
104     prog_bin = "ffmpeg"
105     info_prog_bin = "ffprobe"
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         args = self.info_prog_bin + ' -show_format "' \
146                 + self.input_file + '"'
147
148         # READ handler for process's output.
149         p = subprocess.Popen(args, shell=True,
150                 stdout=subprocess.PIPE, stderr=open(os.devnull, 'w'))
151         pipe = p.stdout
152
153         # Parse ffprobe's output.
154         while True:
155             line = pipe.readline()
156             if len(line) == 0:
157                 break
158             
159             # Search for the line which contains duration information.
160             m = re.match(r"duration=([\d\.]+)", line)
161             if m is not None:
162                 return float(m.group(1))
163
164         exit_code = p.wait()
165         if exit_code > 0:
166             raise api_exceptions.ThumbExtractionException( \
167                     'FFmpeg exited with code ' + str(exit_code) + '.')
168