CIS: CIWorker code written, but needs debugging
[living-lab-site.git] / cis / api / base.py
1 #!/usr/bin/env python
2
3 """
4 Base classes for the external programs API.
5 """
6
7 import api_exceptions
8 import re
9 import cis_util
10 import random
11
12 class BaseTranscoder:
13     """
14     Abstraction of the API class for the transcoder program. 
15     """
16
17     prog_bin = None
18     input_file = None
19     output_file = None
20     dest_path = ''
21     name = None
22
23     # Recommended formats.
24     containers = {
25         "avi": None,
26         "flv": None,
27         "mp4": None,
28         "ogg": None,
29         "webm": None,
30         "mpegts": None
31     }
32     # File extensions by container. First value is for audio files and the
33     # second one is for (audio-)video files.
34     extensions = {
35         "avi": ["avi", "avi"],
36         "flv": ["flv", "flv"],
37         "mp4": ["mp4", "mp4"],
38         "ogg": ["oga", "ogv"],
39         "webm": ["webm", "webm"],
40         "mpegts": ["mts", "mts"]
41     }
42     a_codecs = {
43         "mp3": None,
44         "vorbis": None
45     }
46     v_codecs = {
47         "h264": None,
48         "theora": None,
49         "vp8": None
50     }
51
52     def __init__(self, input_file, name=None, prog_bin=None):
53         self.input_file = input_file
54         if prog_bin is not None:
55             self.prog_bin = prog_bin
56         
57         if name is None:
58             name = cis_util.get_name(input_file)
59
60         self.name = name
61
62     def transcode(self, container, a_codec=None, v_codec=None,
63             a_bitrate=None, a_samplingrate=None, a_channels=None,
64             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
65         """
66         Transcodes the input file to an audio-video file.
67
68         @param container: possible values are listed in containers member 
69         as keys
70         @param a_codec possible values are listed in a_codecs member as keys
71         @param v_codec possible values are listed in v_codecs member as keys
72         @param a_bitrate (numeric) audio bit rate
73         @param a_samplingrate (numeric) audio sampling rate in Hz
74         @param a_channels (numeric) number of audio channels
75         @param v_bitrate (numeric) video bit rate
76         @param v_framerate (numeric) number of frames per second for a video
77         @param v_resolution (string) video image size as <width>x<height>
78         @param v_dar video display aspect ratio as <den>x<num> or float
79         @return output file name
80         """
81
82         # Check parameters.
83         if a_codec is None and v_codec is None:
84             raise ValueError('No audio or video codec specified.')
85
86         if a_codec is not None and type(a_codec) is not str:
87             raise TypeError('Audio codec must be string.')
88
89         if v_codec is not None and type(v_codec) is not str:
90             raise TypeError('Video codec must be string.')
91
92         if a_samplingrate is not None and type(a_samplingrate) is not int:
93             raise TypeError('Audio sampling rate must be an integer.')
94
95         if a_channels is not None and type(a_channels) is not int:
96             raise TypeError('Audio channels parameter must be an integer.')
97
98         if v_framerate is not None and type(v_framerate) is not int:
99             raise TypeError('Video frate rate must be an integer.')
100
101         if v_resolution is not None \
102                 and re.match('[\d]+x[\d]+', v_resolution) is None:
103             raise ValueError('Video resolution must be a string like <width>x<height>.')
104
105         if v_dar is not None and (type(v_dar) is not float \
106                 and re.match('[\d]+:[\d]+', v_dar) is None):
107             raise ValueError('Video display aspect ratio must be a float or a string like <den>:<num>.')
108
109         self.output_file = self.dest_path + self.name
110         if v_resolution is not None:
111             self.output_file += '_'
112             self.output_file += v_resolution[(v_resolution.rindex('x')+1):]
113             self.output_file += 'p'
114         ext = self.tr_extension(container, (v_codec is not None))
115         if ext is not None:
116             self.output_file += '.' + ext
117
118         return self._transcode(self.tr_container(container),
119                 self.tr_a_codec(a_codec), self.tr_v_codec(v_codec),
120                 a_bitrate, a_samplingrate, a_channels,
121                 v_bitrate, v_framerate, v_resolution, v_dar)
122
123     def _transcode(self, container, a_codec=None, v_codec=None,
124             a_bitrate=None, a_samplingrate=None, a_channels=None,
125             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
126         """
127         Called by transcode; must be overridden by a child class which
128         effectively transcodes the input file.
129
130         @return output file name
131         """
132         pass
133
134     def tr_container(self, name):
135         """ Translates container API name into external program identifier."""
136
137         if not self.containers.has_key(name) or self.containers[name] is None:
138             raise api_exceptions.NotImplementedException("Container " + name)
139
140         return self.containers[name]
141
142     def tr_extension(self, name, video=True):
143         """ Translates container API name into file extension."""
144
145         if video is True:
146             i = 1
147         else:
148             i = 0
149
150         if not self.extensions.has_key(name) or self.extensions[name] is None:
151             return None
152
153         return self.extensions[name][i]
154
155     def tr_a_codec(self, name):
156         """ Translates audio codec API name into external program identifier."""
157
158         if not self.a_codecs.has_key(name) or self.a_codecs[name] is None:
159             raise api_exceptions.NotImplementedException("Audio Codec " + name)
160
161         return self.a_codecs[name]
162
163     def tr_v_codec(self, name):
164         """ Translates video codec API name into external program identifier."""
165
166         if not self.v_codecs.has_key(name) or self.v_codecs[name] is None:
167             raise api_exceptions.NotImplementedException("Video Codec " + name)
168
169         return self.v_codecs[name]
170
171
172 class BaseThumbExtractor:
173     """
174     Abstraction of the API class for the thumbnail extraction program. 
175
176     Thumbnail extracted are in JPEG format.
177     """
178
179     prog_bin = None
180     input_file = None
181     dest_path = ''
182     name = None
183     
184     def __init__(self, input_file, name=None, prog_bin=None):
185         self.input_file = input_file
186         if prog_bin is not None:
187             self.prog_bin = prog_bin
188         
189         if name is None:
190             name = cis_util.get_name(input_file)
191
192         self.name = name
193
194     def extract_thumb(self, seek_pos, resolution="120x90", index=0):
195         """
196         Extracts a thumbnail from the video from a specified position
197         expressed in seconds (int/float).
198
199         index: an index appended to the image name in order to avoid
200         overwriting.
201         """
202         pass
203
204     def extract_random_thumb(self, resolution="120x90", index=0):
205         """
206         Extracts a thumbnail from the video from a random position.
207         """
208         duration = self.get_video_duration()
209         seek_pos = random.random() * duration
210         self.extract_thumb(seek_pos, resolution, index)
211
212     def extract_summary_thumbs(self, count, resolution="120x90"):
213         """
214         Extracts a series of thumbnails from a video by taking several
215         snapshots.
216
217         The snapshots are taken from equally spaced positions such that
218         `count` thumbs are extracted.
219         """
220         duration = self.get_video_duration()
221         interval = duration / (count + 1)
222         
223         n_thumbs_extracted = 0
224         seek_pos = interval
225         for index in range (0, count):
226             thumb_extracted = True
227             try:
228                 self.extract_thumb(seek_pos, resolution, n_thumbs_extracted)
229             except api_exceptions.ThumbExtractionException as e:
230                 thumb_extracted = False
231
232             if thumb_extracted:
233                 n_thumbs_extracted += 1
234
235             seek_pos += interval
236
237         return n_thumbs_extracted
238
239     def get_output_file_name(self, index):
240         """ Returns the name required as output file name based on index. """
241         output_file_name = self.dest_path + self.name \
242                 + '_t' + ("%02d" % index) + '.jpg'
243         return output_file_name
244
245     def get_video_duration(self):
246         pass
247
248
249 class BaseFileTransferer:
250     """
251     Ensures file transfer from the Web Server to the CIS (here).
252     
253     Several implementations can be done by extending this class for
254     file transfer protocol such as FTP, SCP, RSYNC, HTTP etc.
255     """
256     
257     local_path = ''
258     remote_path = ''
259     
260     def __init__(self, local_path='', remote_path=''):
261         """ Initialize by setting local and remote paths for file transfer. """
262         self.local_path = local_path
263         self.remote_path = remote_path
264
265     def __del__(self):
266         self.close()
267
268     def get(self, files):
269         """
270         Transfers files locally from the Web Server.
271
272         files: a list of file name strings
273         """
274         pass
275
276     def put(self, files):
277         """
278         Transfers files from the Web Server locally.
279
280         files: a list of file name strings
281         """
282         pass
283
284     def close(self):
285         """
286         This method should be called when the instance is no longer required.
287
288         Class's destructor calls this method.
289         """
290         pass
291
292
293 class BaseAVInfo:
294     @staticmethod
295     def get_video_duration(input_file, formated=False):
296         """
297         Returns the number of seconds of a video (int/float) if formated is
298         False and a string for duration formated as [HH:]:mm:ss otherwise.
299         """
300         pass