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, ))
79
def __init__(self, filename, max_interleave=1.0, timeout=3000):
81
filename: str; absolute path of the file to be discovered.
82
max_interleave: int or float; the maximum frame interleave in seconds.
83
The value must be greater than the input file frame interleave
84
or the discoverer may not find out all input file's streams.
85
The default value is 1 second and you shouldn't have to change it,
86
changing it mean larger discovering time and bigger memory usage.
87
timeout: int; duration in ms for the discovery to complete.
89
gobject.GObject.__init__(self)
93
self.containercaps = False
97
self.inputaudiocaps = {}
98
self.inputvideocaps = {}
102
self.videorate = gst.Fraction(0,1)
104
self.audiofloat = False
108
self.audiochannels = 0
110
self.audiolength = 0L
111
self.videolength = 0L
113
self.is_video = False
114
self.is_audio = False
116
self.otherstreams = []
118
self.finished = False
120
self._success = False
121
self._nomorepads = False
124
self._timeout = timeout
125
self._max_interleave = max_interleave
127
if not os.path.isfile(filename):
128
self.debug("File '%s' does not exist, finished" % filename)
132
# the initial elements of the pipeline
133
self.src = gst.element_factory_make("filesrc")
134
self.src.set_property("location", filename)
135
self.src.set_property("blocksize", 1000000)
136
self.dbin = gst.element_factory_make("decodebin2")
137
self.add(self.src, self.dbin)
138
self.src.link(self.dbin)
139
self.typefind = self.dbin.get_by_name("typefind")
143
self.typefind.connect("have-type", self._have_type_cb)
144
self.dbin.connect("new-decoded-pad", self._new_decoded_pad_cb)
145
self.dbin.connect("autoplug-continue", self._decodebin_autoplug_continue_cb)
146
self.dbin.connect("no-more-pads", self._no_more_pads_cb)
147
self.dbin.connect("unknown-type", self._unknown_type_cb)
149
def _timed_out_or_eos(self):
150
if (not self.is_audio and not self.is_video) or \
151
(self.is_audio and not self.audiocaps) or \
152
(self.is_video and not self.videocaps):
153
self._finished(False)
157
def _finished(self, success=False):
158
self.debug("success:%d" % success)
159
self._success = success
160
self.bus.remove_signal_watch()
162
gobject.source_remove(self._timeoutid)
164
gobject.idle_add(self._stop)
168
self.debug("success:%d" % self._success)
170
self.set_state(gst.STATE_READY)
171
self.debug("about to emit signal")
172
self.emit('discovered', self._success)
174
def _bus_message_cb(self, bus, message):
175
if message.type == gst.MESSAGE_EOS:
176
self.debug("Got EOS")
177
self._timed_out_or_eos()
178
elif message.type == gst.MESSAGE_TAG:
179
for key in message.parse_tag().keys():
180
self.tags[key] = message.structure[key]
181
elif message.type == gst.MESSAGE_ERROR:
182
self.debug("Got error")
186
"""Find the information on the given file asynchronously"""
187
self.debug("starting discovery")
189
self.emit('discovered', False)
192
self.bus = self.get_bus()
193
self.bus.add_signal_watch()
194
self.bus.connect("message", self._bus_message_cb)
197
self._timeoutid = gobject.timeout_add(self._timeout, self._timed_out_or_eos)
199
self.info("setting to PLAY")
200
if not self.set_state(gst.STATE_PLAYING):
203
def _time_to_string(self, value):
205
transform a value in nanoseconds into a human-readable string
207
ms = value / gst.MSECOND
212
return "%2dm %2ds %3d" % (min, sec, ms)
214
def print_info(self):
215
"""prints out the information on the given file"""
216
if not self.finished:
218
if not self.mimetype:
219
print "Unknown media type"
221
print "Mime Type :\t", self.mimetype
222
if not self.is_video and not self.is_audio:
224
print "Length :\t", self._time_to_string(max(self.audiolength, self.videolength))
225
print "\tAudio:", self._time_to_string(self.audiolength), "\tVideo:", self._time_to_string(self.videolength)
226
if self.is_video and self.videorate:
228
print "Incoming video format :" + str(self.inputvideocaps)
229
print "\t%d x %d @ %d/%d fps" % (self.videowidth,
231
self.videorate.num, self.videorate.denom)
232
if self.tags.has_key("video-codec"):
233
print "\tCodec :", self.tags.pop("video-codec")
236
print "Incoming audio format :" + str(self.inputaudiocaps)
238
print "\t%d channels(s) : %dHz @ %dbits (float)" % (self.audiochannels,
242
print "\t%d channels(s) : %dHz @ %dbits (int)" % (self.audiochannels,
245
if self.tags.has_key("audio-codec"):
246
print "\tCodec :", self.tags.pop("audio-codec")
247
for stream in self.otherstreams:
248
if not stream == self.mimetype:
249
print "Other unsuported Multimedia stream :", stream
251
print "Additional information :"
252
for tag in self.tags.keys():
253
print "%20s :\t" % tag, self.tags[tag]
255
def _no_more_pads_cb(self, dbin):
256
self.info("no more pads")
257
self._nomorepads = True
259
def _unknown_type_cb(self, dbin, pad, caps):
260
self.debug("unknown type : %s" % caps.to_string())
261
# if we get an unknown type and we don't already have an
262
# audio or video pad, we are finished !
263
self.otherstreams.append(caps.to_string())
264
if not self.is_video and not self.is_audio:
268
def _have_type_cb(self, typefind, prob, caps):
269
self.mimetype = caps.to_string()
271
def _notify_caps_cb(self, pad, args):
272
caps = pad.get_negotiated_caps()
274
pad.info("no negotiated caps available")
276
pad.info("caps:%s" % caps.to_string())
278
# We now get the total length of that stream
279
q = gst.query_new_duration(gst.FORMAT_TIME)
280
pad.info("sending duration query")
281
if pad.get_peer().query(q):
282
format, length = q.parse_duration()
283
if format == gst.FORMAT_TIME:
284
pad.info("got duration (time) : %s" % (gst.TIME_ARGS(length),))
286
pad.info("got duration : %d [format:%d]" % (length, format))
289
gst.warning("duration query failed")
291
# We store the caps and length in the proper location
292
if "audio" in caps.to_string():
293
self.audiocaps = caps
294
self.audiolength = length
295
self.audiorate = caps[0]["rate"]
296
self.audiowidth = caps[0]["width"]
297
self.audiochannels = caps[0]["channels"]
298
if "x-raw-float" in caps.to_string():
299
self.audiofloat = True
301
self.audiodepth = caps[0]["depth"]
302
if self._nomorepads and ((not self.is_video) or self.videocaps):
304
elif "video" in caps.to_string():
305
self.videocaps = caps
306
self.videolength = length
307
self.videowidth = caps[0]["width"]
308
self.videoheight = caps[0]["height"]
309
self.videorate = caps[0]["framerate"]
310
if self._nomorepads and ((not self.is_audio) or self.audiocaps):
313
def _decodebin_autoplug_continue_cb(self, dbin, pad, caps):
314
if self.containercaps == False:
315
self.containercaps = caps
317
# print "autoplug continue returns " + str(caps)
318
c = caps[0].get_name()
319
# print "c is " + str(c)
320
if c.startswith("audio/"):
321
if c.startswith("audio/x-raw") == False:
322
# print "discoverer: c is audio " + str(c)
323
# if caps[0].has_field("rate"):
325
blacklist = ['rate','channels','bitrate','block_align','mode','subbands'
326
,'allocation','bitpool','blocks','width','depth'
329
aresult = caps[0].get_name();
330
for attr in caps[0].keys():
331
if attr not in blacklist:
332
aresult += ","+attr+"="+str(caps[0][attr])
333
self.inputaudiocaps = aresult
334
# print "discoverer: self.inputaudiocaps " + str(self.inputaudiocaps)
335
elif c.startswith("video/") or c.startswith("image/"):
336
if c.startswith("video/x-raw-yuv") == False or c.startswith("video/x-raw-rgb") == False:
338
blacklist = ['height','width','framerate','depth','codec_data','pixel-aspect-ratio','format']
340
vresult = caps[0].get_name();
341
for attr in caps[0].keys():
342
if attr not in blacklist:
343
vresult += ","+attr+"="+str(caps[0][attr])
344
self.inputvideocaps = vresult
348
def _new_decoded_pad_cb(self, dbin, pad, is_last):
349
# Does the file contain got audio or video ?
350
caps = pad.get_caps()
351
gst.info("caps:%s" % caps.to_string())
352
if "audio" in caps.to_string():
354
elif "video" in caps.to_string():
357
self.warning("got a different caps.. %s" % caps.to_string())
359
if is_last and not self.is_video and not self.is_audio:
360
self.debug("is last, not video or audio")
361
self._finished(False)
363
# we connect a fakesink to the new pad...
364
pad.info("adding queue->fakesink")
365
fakesink = gst.element_factory_make("fakesink", "fakesink%d-%s" %
366
(self.sinknumber, "audio" in caps.to_string() and "audio" or "video"))
368
queue = gst.element_factory_make("queue")
369
# we want the queue to buffer up to the specified amount of data
370
# before outputting. This enables us to cope with formats
371
# that don't create their source pads straight away,
372
# but instead wait for the first buffer of that stream.
373
# The specified time must be greater than the input file
374
# frame interleave for the discoverer to work properly.
375
queue.props.min_threshold_time = int(self._max_interleave * gst.SECOND)
376
queue.props.max_size_time = int(2 * self._max_interleave * gst.SECOND)
377
queue.props.max_size_bytes = 0
379
# If durations are bad on the buffers (common for video decoders), we'll
380
# never reach the min_threshold_time or max_size_time. So, set a
381
# max size in buffers, and if reached, disable the min_threshold_time.
382
# This ensures we don't fail to discover with various ffmpeg
383
# demuxers/decoders that provide bogus (or no) duration.
384
queue.props.max_size_buffers = int(100 * self._max_interleave)
385
def _disable_min_threshold_cb(queue):
386
queue.props.min_threshold_time = 0
387
queue.disconnect(signal_id)
388
signal_id = queue.connect('overrun', _disable_min_threshold_cb)
390
self.add(fakesink, queue)
392
sinkpad = fakesink.get_pad("sink")
393
queuepad = queue.get_pad("sink")
394
# ... and connect a callback for when the caps are fixed
395
sinkpad.connect("notify::caps", self._notify_caps_cb)
396
if pad.link(queuepad):
397
pad.warning("##### Couldn't link pad to queue")
398
queue.set_state(gst.STATE_PLAYING)
399
fakesink.set_state(gst.STATE_PLAYING)
400
gst.info('finished here')