~jm-leddy/ubuntu/quantal/transmageddon/lowercaseify_gst

« back to all changes in this revision

Viewing changes to src/discoverer.py

  • Committer: Package Import Robot
  • Author(s): Alessio Treglia
  • Date: 2011-12-03 22:27:04 UTC
  • mto: This revision was merged to the branch mainline in revision 7.
  • Revision ID: package-import@ubuntu.com-20111203222704-r27w4kf72dygyobf
Tags: upstream-0.20
ImportĀ upstreamĀ versionĀ 0.20

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
    inputaudiocaps = {}
54
 
    inputvideocaps = {}
55
 
 
56
 
    videowidth = 0
57
 
    videoheight = 0
58
 
    videorate = 0
59
 
 
60
 
    audiofloat = False
61
 
    audiorate = 0
62
 
    audiodepth = 0
63
 
    audiowidth = 0
64
 
    audiochannels = 0
65
 
 
66
 
    audiolength = 0L
67
 
    videolength = 0L
68
 
 
69
 
    is_video = False
70
 
    is_audio = False
71
 
 
72
 
    otherstreams = []
73
 
 
74
 
    finished = False
75
 
    sinknumber = 0
76
 
    tags = {}
77
 
 
78
 
 
79
 
    def __init__(self, filename, max_interleave=1.0, timeout=3000):
80
 
        """
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.
88
 
        """
89
 
        gobject.GObject.__init__(self)
90
 
 
91
 
        self.mimetype = None
92
 
 
93
 
        self.containercaps = False
94
 
 
95
 
        self.audiocaps = {}
96
 
        self.videocaps = {}
97
 
        self.inputaudiocaps = {}
98
 
        self.inputvideocaps = {}
99
 
 
100
 
        self.videowidth = 0
101
 
        self.videoheight = 0
102
 
        self.videorate = gst.Fraction(0,1)
103
 
 
104
 
        self.audiofloat = False
105
 
        self.audiorate = 0
106
 
        self.audiodepth = 0
107
 
        self.audiowidth = 0
108
 
        self.audiochannels = 0
109
 
 
110
 
        self.audiolength = 0L
111
 
        self.videolength = 0L
112
 
 
113
 
        self.is_video = False
114
 
        self.is_audio = False
115
 
 
116
 
        self.otherstreams = []
117
 
 
118
 
        self.finished = False
119
 
        self.tags = {}
120
 
        self._success = False
121
 
        self._nomorepads = False
122
 
 
123
 
        self._timeoutid = 0
124
 
        self._timeout = timeout
125
 
        self._max_interleave = max_interleave
126
 
 
127
 
        if not os.path.isfile(filename):
128
 
            self.debug("File '%s' does not exist, finished" % filename)
129
 
            self.finished = True
130
 
            return
131
 
 
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")
140
 
 
141
 
 
142
 
        # callbacks
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)
148
 
 
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)
154
 
        else:
155
 
            self._finished(True)
156
 
 
157
 
    def _finished(self, success=False):
158
 
        self.debug("success:%d" % success)
159
 
        self._success = success
160
 
        self.bus.remove_signal_watch()
161
 
        if self._timeoutid:
162
 
            gobject.source_remove(self._timeoutid)
163
 
            self._timeoutid = 0
164
 
        gobject.idle_add(self._stop)
165
 
        return False
166
 
 
167
 
    def _stop(self):
168
 
        self.debug("success:%d" % self._success)
169
 
        self.finished = True
170
 
        self.set_state(gst.STATE_READY)
171
 
        self.debug("about to emit signal")
172
 
        self.emit('discovered', self._success)
173
 
 
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")
183
 
            self._finished()
184
 
 
185
 
    def discover(self):
186
 
        """Find the information on the given file asynchronously"""
187
 
        self.debug("starting discovery")
188
 
        if self.finished:
189
 
            self.emit('discovered', False)
190
 
            return
191
 
 
192
 
        self.bus = self.get_bus()
193
 
        self.bus.add_signal_watch()
194
 
        self.bus.connect("message", self._bus_message_cb)
195
 
 
196
 
        # 3s timeout
197
 
        self._timeoutid = gobject.timeout_add(self._timeout, self._timed_out_or_eos)
198
 
 
199
 
        self.info("setting to PLAY")
200
 
        if not self.set_state(gst.STATE_PLAYING):
201
 
            self._finished()
202
 
 
203
 
    def _time_to_string(self, value):
204
 
        """
205
 
        transform a value in nanoseconds into a human-readable string
206
 
        """
207
 
        ms = value / gst.MSECOND
208
 
        sec = ms / 1000
209
 
        ms = ms % 1000
210
 
        min = sec / 60
211
 
        sec = sec % 60
212
 
        return "%2dm %2ds %3d" % (min, sec, ms)
213
 
 
214
 
    def print_info(self):
215
 
        """prints out the information on the given file"""
216
 
        if not self.finished:
217
 
            return
218
 
        if not self.mimetype:
219
 
            print "Unknown media type"
220
 
            return
221
 
        print "Mime Type :\t", self.mimetype
222
 
        if not self.is_video and not self.is_audio:
223
 
            return
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:
227
 
            print "Video :"
228
 
            print "Incoming video format :" + str(self.inputvideocaps)
229
 
            print "\t%d x %d @ %d/%d fps" % (self.videowidth,
230
 
                                            self.videoheight,
231
 
                                            self.videorate.num, self.videorate.denom)
232
 
            if self.tags.has_key("video-codec"):
233
 
                print "\tCodec :", self.tags.pop("video-codec")
234
 
        if self.is_audio:
235
 
            print "Audio :"
236
 
            print "Incoming audio format :" + str(self.inputaudiocaps)
237
 
            if self.audiofloat:
238
 
                print "\t%d channels(s) : %dHz @ %dbits (float)" % (self.audiochannels,
239
 
                                                                    self.audiorate,
240
 
                                                                    self.audiowidth)
241
 
            else:
242
 
                print "\t%d channels(s) : %dHz @ %dbits (int)" % (self.audiochannels,
243
 
                                                                  self.audiorate,
244
 
                                                                  self.audiodepth)
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
250
 
        if self.tags:
251
 
            print "Additional information :"
252
 
            for tag in self.tags.keys():
253
 
                print "%20s :\t" % tag, self.tags[tag]
254
 
 
255
 
    def _no_more_pads_cb(self, dbin):
256
 
        self.info("no more pads")
257
 
        self._nomorepads = True
258
 
 
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:
265
 
            self.finished = True
266
 
            self._finished()
267
 
 
268
 
    def _have_type_cb(self, typefind, prob, caps):
269
 
        self.mimetype = caps.to_string()
270
 
 
271
 
    def _notify_caps_cb(self, pad, args):
272
 
        caps = pad.get_negotiated_caps()
273
 
        if not caps:
274
 
            pad.info("no negotiated caps available")
275
 
            return
276
 
        pad.info("caps:%s" % caps.to_string())
277
 
        # the caps are fixed
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),))
285
 
            else:
286
 
                pad.info("got duration : %d [format:%d]" % (length, format))
287
 
        else:
288
 
            length = -1
289
 
            gst.warning("duration query failed")
290
 
 
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
300
 
            else:
301
 
                self.audiodepth = caps[0]["depth"]
302
 
            if self._nomorepads and ((not self.is_video) or self.videocaps):
303
 
                self._finished(True)
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):
311
 
                self._finished(True)
312
 
 
313
 
    def _decodebin_autoplug_continue_cb(self, dbin, pad, caps):
314
 
        if self.containercaps == False:
315
 
            self.containercaps = caps
316
 
        else:
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"):
324
 
                       if caps.is_fixed():
325
 
                           blacklist = ['rate','channels','bitrate','block_align','mode','subbands'
326
 
                                        ,'allocation','bitpool','blocks','width','depth'
327
 
                                        ,'codec_data']
328
 
                           for x in caps:
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:
337
 
                  if caps.is_fixed():
338
 
                      blacklist = ['height','width','framerate','depth','codec_data','pixel-aspect-ratio','format']
339
 
                      for x in caps:
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
345
 
        return True
346
 
 
347
 
 
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():
353
 
            self.is_audio = True
354
 
        elif "video" in caps.to_string():
355
 
            self.is_video = True
356
 
        else:
357
 
            self.warning("got a different caps.. %s" % caps.to_string())
358
 
            return
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)
362
 
            return
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"))
367
 
        self.sinknumber += 1
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
378
 
 
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)
389
 
 
390
 
        self.add(fakesink, queue)
391
 
        queue.link(fakesink)
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')