Initial import to Tizen
[profile/ivi/gstreamer-python.git] / gst / extend / discoverer.py
1 # -*- Mode: Python -*-
2 # vi:si:et:sw=4:sts=4:ts=4
3
4 # discoverer.py
5 # (c) 2005-2008 Edward Hervey <bilboed at bilboed dot com>
6 # Discovers multimedia information on files
7
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21
22 """
23 Class and functions for getting multimedia information about files
24 """
25
26 import os.path
27
28 import gobject
29
30 import gst
31
32 from gst.extend.pygobject import gsignal
33
34 class Discoverer(gst.Pipeline):
35     """
36     Discovers information about files.
37     This class is event-based and needs a mainloop to work properly.
38     Emits the 'discovered' signal when discovery is finished.
39
40     The 'discovered' callback has one boolean argument, which is True if the
41     file contains decodable multimedia streams.
42     """
43     __gsignals__ = {
44         'discovered' : (gobject.SIGNAL_RUN_FIRST,
45                         None,
46                         (gobject.TYPE_BOOLEAN, ))
47         }
48
49     mimetype = None
50
51     audiocaps = {}
52     videocaps = {}
53
54     videowidth = 0
55     videoheight = 0
56     videorate = 0
57
58     audiofloat = False
59     audiorate = 0
60     audiodepth = 0
61     audiowidth = 0
62     audiochannels = 0
63
64     audiolength = 0L
65     videolength = 0L
66
67     is_video = False
68     is_audio = False
69
70     otherstreams = []
71
72     finished = False
73     sinknumber = 0
74     tags = {}
75
76
77     def __init__(self, filename, max_interleave=1.0, timeout=3000):
78         """
79         filename: str; absolute path of the file to be discovered.
80         max_interleave: int or float; the maximum frame interleave in seconds.
81             The value must be greater than the input file frame interleave
82             or the discoverer may not find out all input file's streams.
83             The default value is 1 second and you shouldn't have to change it,
84             changing it mean larger discovering time and bigger memory usage.
85         timeout: int; duration in ms for the discovery to complete.
86         """
87         gobject.GObject.__init__(self)
88
89         self.mimetype = None
90
91         self.audiocaps = {}
92         self.videocaps = {}
93
94         self.videowidth = 0
95         self.videoheight = 0
96         self.videorate = gst.Fraction(0,1)
97
98         self.audiofloat = False
99         self.audiorate = 0
100         self.audiodepth = 0
101         self.audiowidth = 0
102         self.audiochannels = 0
103
104         self.audiolength = 0L
105         self.videolength = 0L
106
107         self.is_video = False
108         self.is_audio = False
109
110         self.otherstreams = []
111
112         self.finished = False
113         self.tags = {}
114         self._success = False
115         self._nomorepads = False
116
117         self._timeoutid = 0
118         self._timeout = timeout
119         self._max_interleave = max_interleave
120
121         if not os.path.isfile(filename):
122             self.debug("File '%s' does not exist, finished" % filename)
123             self.finished = True
124             return
125
126         # the initial elements of the pipeline
127         self.src = gst.element_factory_make("filesrc")
128         self.src.set_property("location", filename)
129         self.src.set_property("blocksize", 1000000)
130         self.dbin = gst.element_factory_make("decodebin")
131         self.add(self.src, self.dbin)
132         self.src.link(self.dbin)
133         self.typefind = self.dbin.get_by_name("typefind")
134
135         # callbacks
136         self.typefind.connect("have-type", self._have_type_cb)
137         self.dbin.connect("new-decoded-pad", self._new_decoded_pad_cb)
138         self.dbin.connect("no-more-pads", self._no_more_pads_cb)
139         self.dbin.connect("unknown-type", self._unknown_type_cb)
140
141     def _timed_out_or_eos(self):
142         if (not self.is_audio and not self.is_video) or \
143                 (self.is_audio and not self.audiocaps) or \
144                 (self.is_video and not self.videocaps):
145             self._finished(False)
146         else:
147             self._finished(True)
148
149     def _finished(self, success=False):
150         self.debug("success:%d" % success)
151         self._success = success
152         self.bus.remove_signal_watch()
153         if self._timeoutid:
154             gobject.source_remove(self._timeoutid)
155             self._timeoutid = 0
156         gobject.idle_add(self._stop)
157         return False
158
159     def _stop(self):
160         self.debug("success:%d" % self._success)
161         self.finished = True
162         self.set_state(gst.STATE_READY)
163         self.debug("about to emit signal")
164         self.emit('discovered', self._success)
165
166     def _bus_message_cb(self, bus, message):
167         if message.type == gst.MESSAGE_EOS:
168             self.debug("Got EOS")
169             self._timed_out_or_eos()
170         elif message.type == gst.MESSAGE_TAG:
171             for key in message.parse_tag().keys():
172                 self.tags[key] = message.structure[key]
173         elif message.type == gst.MESSAGE_ERROR:
174             self.debug("Got error")
175             self._finished()
176
177     def discover(self):
178         """Find the information on the given file asynchronously"""
179         self.debug("starting discovery")
180         if self.finished:
181             self.emit('discovered', False)
182             return
183
184         self.bus = self.get_bus()
185         self.bus.add_signal_watch()
186         self.bus.connect("message", self._bus_message_cb)
187
188         # 3s timeout
189         self._timeoutid = gobject.timeout_add(self._timeout, self._timed_out_or_eos)
190
191         self.info("setting to PLAY")
192         if not self.set_state(gst.STATE_PLAYING):
193             self._finished()
194
195     def _time_to_string(self, value):
196         """
197         transform a value in nanoseconds into a human-readable string
198         """
199         ms = value / gst.MSECOND
200         sec = ms / 1000
201         ms = ms % 1000
202         min = sec / 60
203         sec = sec % 60
204         return "%2dm %2ds %3d" % (min, sec, ms)
205
206     def print_info(self):
207         """prints out the information on the given file"""
208         if not self.finished:
209             return
210         if not self.mimetype:
211             print "Unknown media type"
212             return
213         print "Mime Type :\t", self.mimetype
214         if not self.is_video and not self.is_audio:
215             return
216         print "Length :\t", self._time_to_string(max(self.audiolength, self.videolength))
217         print "\tAudio:", self._time_to_string(self.audiolength), "\tVideo:", self._time_to_string(self.videolength)
218         if self.is_video and self.videorate:
219             print "Video :"
220             print "\t%d x %d @ %d/%d fps" % (self.videowidth,
221                                             self.videoheight,
222                                             self.videorate.num, self.videorate.denom)
223             if self.tags.has_key("video-codec"):
224                 print "\tCodec :", self.tags["video-codec"]
225         if self.is_audio:
226             print "Audio :"
227             if self.audiofloat:
228                 print "\t%d channels(s) : %dHz @ %dbits (float)" % (self.audiochannels,
229                                                                     self.audiorate,
230                                                                     self.audiowidth)
231             else:
232                 print "\t%d channels(s) : %dHz @ %dbits (int)" % (self.audiochannels,
233                                                                   self.audiorate,
234                                                                   self.audiodepth)
235             if self.tags.has_key("audio-codec"):
236                 print "\tCodec :", self.tags["audio-codec"]
237         for stream in self.otherstreams:
238             if not stream == self.mimetype:
239                 print "Other unsuported Multimedia stream :", stream
240         if self.tags:
241             print "Additional information :"
242             for tag in self.tags.keys():
243                 print "%20s :\t" % tag, self.tags[tag]
244
245     def _no_more_pads_cb(self, dbin):
246         self.info("no more pads")
247         self._nomorepads = True
248
249     def _unknown_type_cb(self, dbin, pad, caps):
250         self.debug("unknown type : %s" % caps.to_string())
251         # if we get an unknown type and we don't already have an
252         # audio or video pad, we are finished !
253         self.otherstreams.append(caps.to_string())
254         if not self.is_video and not self.is_audio:
255             self.finished = True
256             self._finished()
257
258     def _have_type_cb(self, typefind, prob, caps):
259         self.mimetype = caps.to_string()
260
261     def _notify_caps_cb(self, pad, args):
262         caps = pad.get_negotiated_caps()
263         if not caps:
264             pad.info("no negotiated caps available")
265             return
266         pad.info("caps:%s" % caps.to_string())
267         # the caps are fixed
268         # We now get the total length of that stream
269         q = gst.query_new_duration(gst.FORMAT_TIME)
270         pad.info("sending duration query")
271         if pad.get_peer().query(q):
272             format, length = q.parse_duration()
273             if format == gst.FORMAT_TIME:
274                 pad.info("got duration (time) : %s" % (gst.TIME_ARGS(length),))
275             else:
276                 pad.info("got duration : %d [format:%d]" % (length, format))
277         else:
278             length = -1
279             gst.warning("duration query failed")
280
281         # We store the caps and length in the proper location
282         if "audio" in caps.to_string():
283             self.audiocaps = caps
284             self.audiolength = length
285             self.audiorate = caps[0]["rate"]
286             self.audiowidth = caps[0]["width"]
287             self.audiochannels = caps[0]["channels"]
288             if "x-raw-float" in caps.to_string():
289                 self.audiofloat = True
290             else:
291                 self.audiodepth = caps[0]["depth"]
292             if self._nomorepads and ((not self.is_video) or self.videocaps):
293                 self._finished(True)
294         elif "video" in caps.to_string():
295             self.videocaps = caps
296             self.videolength = length
297             self.videowidth = caps[0]["width"]
298             self.videoheight = caps[0]["height"]
299             self.videorate = caps[0]["framerate"]
300             if self._nomorepads and ((not self.is_audio) or self.audiocaps):
301                 self._finished(True)
302
303     def _new_decoded_pad_cb(self, dbin, pad, is_last):
304         # Does the file contain got audio or video ?
305         caps = pad.get_caps()
306         gst.info("caps:%s" % caps.to_string())
307         if "audio" in caps.to_string():
308             self.is_audio = True
309         elif "video" in caps.to_string():
310             self.is_video = True
311         else:
312             self.warning("got a different caps.. %s" % caps.to_string())
313             return
314         if is_last and not self.is_video and not self.is_audio:
315             self.debug("is last, not video or audio")
316             self._finished(False)
317             return
318         # we connect a fakesink to the new pad...
319         pad.info("adding queue->fakesink")
320         fakesink = gst.element_factory_make("fakesink", "fakesink%d-%s" % 
321             (self.sinknumber, "audio" in caps.to_string() and "audio" or "video"))
322         self.sinknumber += 1
323         queue = gst.element_factory_make("queue")
324         # we want the queue to buffer up to the specified amount of data 
325         # before outputting. This enables us to cope with formats 
326         # that don't create their source pads straight away, 
327         # but instead wait for the first buffer of that stream.
328         # The specified time must be greater than the input file
329         # frame interleave for the discoverer to work properly.
330         queue.props.min_threshold_time = int(self._max_interleave * gst.SECOND)
331         queue.props.max_size_time = int(2 * self._max_interleave * gst.SECOND)
332         queue.props.max_size_bytes = 0
333
334         # If durations are bad on the buffers (common for video decoders), we'll
335         # never reach the min_threshold_time or max_size_time. So, set a
336         # max size in buffers, and if reached, disable the min_threshold_time.
337         # This ensures we don't fail to discover with various ffmpeg 
338         # demuxers/decoders that provide bogus (or no) duration.
339         queue.props.max_size_buffers = int(100 * self._max_interleave)
340         def _disable_min_threshold_cb(queue):
341             queue.props.min_threshold_time = 0
342             queue.disconnect(signal_id)
343         signal_id = queue.connect('overrun', _disable_min_threshold_cb)
344
345         self.add(fakesink, queue)
346         queue.link(fakesink)
347         sinkpad = fakesink.get_pad("sink")
348         queuepad = queue.get_pad("sink")
349         # ... and connect a callback for when the caps are fixed
350         sinkpad.connect("notify::caps", self._notify_caps_cb)
351         if pad.link(queuepad):
352             pad.warning("##### Couldn't link pad to queue")
353         queue.set_state(gst.STATE_PLAYING)
354         fakesink.set_state(gst.STATE_PLAYING)
355         gst.info('finished here')