2 # vi:si:et:sw=4:sts=4:ts=4
5 # (c) 2005-2008 Edward Hervey <bilboed at bilboed dot com>
6 # Discovers multimedia information on files
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.
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.
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
23 Class and functions for getting multimedia information about files
32 from gst.extend.pygobject import gsignal
34 class Discoverer(gst.Pipeline):
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.
40 The 'discovered' callback has one boolean argument, which is True if the
41 file contains decodable multimedia streams.
44 'discovered' : (gobject.SIGNAL_RUN_FIRST,
46 (gobject.TYPE_BOOLEAN, ))
77 def __init__(self, filename, max_interleave=1.0, timeout=3000):
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.
87 gobject.GObject.__init__(self)
96 self.videorate = gst.Fraction(0,1)
98 self.audiofloat = False
102 self.audiochannels = 0
104 self.audiolength = 0L
105 self.videolength = 0L
107 self.is_video = False
108 self.is_audio = False
110 self.otherstreams = []
112 self.finished = False
114 self._success = False
115 self._nomorepads = False
118 self._timeout = timeout
119 self._max_interleave = max_interleave
121 if not os.path.isfile(filename):
122 self.debug("File '%s' does not exist, finished" % filename)
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")
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)
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)
149 def _finished(self, success=False):
150 self.debug("success:%d" % success)
151 self._success = success
152 self.bus.remove_signal_watch()
154 gobject.source_remove(self._timeoutid)
156 gobject.idle_add(self._stop)
160 self.debug("success:%d" % self._success)
162 self.set_state(gst.STATE_READY)
163 self.debug("about to emit signal")
164 self.emit('discovered', self._success)
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")
178 """Find the information on the given file asynchronously"""
179 self.debug("starting discovery")
181 self.emit('discovered', False)
184 self.bus = self.get_bus()
185 self.bus.add_signal_watch()
186 self.bus.connect("message", self._bus_message_cb)
189 self._timeoutid = gobject.timeout_add(self._timeout, self._timed_out_or_eos)
191 self.info("setting to PLAY")
192 if not self.set_state(gst.STATE_PLAYING):
195 def _time_to_string(self, value):
197 transform a value in nanoseconds into a human-readable string
199 ms = value / gst.MSECOND
204 return "%2dm %2ds %3d" % (min, sec, ms)
206 def print_info(self):
207 """prints out the information on the given file"""
208 if not self.finished:
210 if not self.mimetype:
211 print "Unknown media type"
213 print "Mime Type :\t", self.mimetype
214 if not self.is_video and not self.is_audio:
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:
220 print "\t%d x %d @ %d/%d fps" % (self.videowidth,
222 self.videorate.num, self.videorate.denom)
223 if self.tags.has_key("video-codec"):
224 print "\tCodec :", self.tags["video-codec"]
228 print "\t%d channels(s) : %dHz @ %dbits (float)" % (self.audiochannels,
232 print "\t%d channels(s) : %dHz @ %dbits (int)" % (self.audiochannels,
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
241 print "Additional information :"
242 for tag in self.tags.keys():
243 print "%20s :\t" % tag, self.tags[tag]
245 def _no_more_pads_cb(self, dbin):
246 self.info("no more pads")
247 self._nomorepads = True
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:
258 def _have_type_cb(self, typefind, prob, caps):
259 self.mimetype = caps.to_string()
261 def _notify_caps_cb(self, pad, args):
262 caps = pad.get_negotiated_caps()
264 pad.info("no negotiated caps available")
266 pad.info("caps:%s" % caps.to_string())
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),))
276 pad.info("got duration : %d [format:%d]" % (length, format))
279 gst.warning("duration query failed")
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
291 self.audiodepth = caps[0]["depth"]
292 if self._nomorepads and ((not self.is_video) or self.videocaps):
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):
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():
309 elif "video" in caps.to_string():
312 self.warning("got a different caps.. %s" % caps.to_string())
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)
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"))
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
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)
345 self.add(fakesink, queue)
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')