cis: torrent files creation
[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
106     log_file = 'log/FFmpegThumbExtractor.log'
107
108     def extract_thumb(self, seek_pos, resolution="120x90", index=0):
109         output_file = self.get_output_file_name(index)
110
111         args = self.prog_bin + ' -y -i "' + self.input_file \
112                 + '" -f rawvideo -vcodec mjpeg' + (' -ss ' + str(seek_pos)) \
113                 + " -vframes 1 -an -s " + resolution + ' "' \
114                 + output_file + '"'
115
116         # READ handler for process's output.
117         p = subprocess.Popen(args, shell=True,
118                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
119         pipe = p.stdout
120         
121         # WRITE handler for logging.
122         log = open(self.log_file, 'w')
123         log.write(args + '\n')
124
125         while True:
126             line = pipe.readline()
127             if len(line) == 0:
128                 break
129             log.write(line)
130
131         exit_code = p.wait()
132         if exit_code > 0:
133             raise api_exceptions.ThumbExtractionException( \
134                     'FFmpeg exited with code ' + str(exit_code) + '.')
135
136         # FFmpeg bug: when no key frame is found from seek_pos to the
137         # end of file an empty image file is created.
138         if os.path.getsize(output_file) == 0L:
139             os.unlink(output_file)
140             raise api_exceptions.ThumbExtractionException( \
141                     'FFmpeg created an empty file.')
142
143     def get_video_duration(self):
144         return FFmpegAVInfo.get_video_duration(self.input_file)
145
146
147 class FFmpegAVInfo(base.BaseAVInfo):
148     
149     prog_bin = "ffprobe"
150
151     @staticmethod
152     def get_video_duration(input_file):
153         args = FFmpegAVInfo.prog_bin + ' -show_format "' \
154                 + input_file + '"'
155
156         # READ handler for process's output.
157         p = subprocess.Popen(args, shell=True,
158                 stdout=subprocess.PIPE, stderr=open(os.devnull, 'w'))
159         pipe = p.stdout
160
161         # Parse ffprobe's output.
162         while True:
163             line = pipe.readline()
164             if len(line) == 0:
165                 break
166             
167             # Search for the line which contains duration information.
168             m = re.match(r"duration=([\d\.]+)", line)
169             if m is not None:
170                 return float(m.group(1))
171
172         exit_code = p.wait()
173         if exit_code > 0:
174             raise api_exceptions.ThumbExtractionException( \
175                     'FFmpeg exited with code ' + str(exit_code) + '.')