~timo-jyrinki/ubuntu/trusty/pitivi/backport_utopic_fixes

« back to all changes in this revision

Viewing changes to pitivi/factories/base.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 15:28:16 UTC
  • mfrom: (6.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140405152816-6lijoax4cngiz5j5
Tags: 0.93-3
* debian/control:
  + Depend on python-gi (>= 3.10), older versions do not work
    with pitivi (Closes: #732813).
  + Add missing dependency on gir1.2-clutter-gst-2.0 (Closes: #743692).
  + Add suggests on gir1.2-notify-0.7 and gir1.2-gnomedesktop-3.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       base.py
4
 
#
5
 
# Copyright (c) 2005-2008, Edward Hervey <bilboed@bilboed.com>
6
 
#               2008, Alessandro Decina <alessandro.decina@collabora.co.uk>
7
 
#
8
 
# This program 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 program 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 program; if not, write to the
20
 
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21
 
# Boston, MA 02110-1301, USA.
22
 
 
23
 
import os.path
24
 
from urllib import unquote
25
 
import gst
26
 
 
27
 
from pitivi.log.loggable import Loggable
28
 
from pitivi.elements.singledecodebin import SingleDecodeBin
29
 
from pitivi.signalinterface import Signallable
30
 
from pitivi.stream import match_stream_groups, AudioStream, VideoStream, \
31
 
        STREAM_MATCH_COMPATIBLE_CAPS
32
 
from pitivi.utils import formatPercent
33
 
 
34
 
# FIXME: define a proper hierarchy
35
 
 
36
 
 
37
 
class ObjectFactoryError(Exception):
38
 
    pass
39
 
 
40
 
 
41
 
class ObjectFactoryStreamError(ObjectFactoryError):
42
 
    pass
43
 
 
44
 
 
45
 
class ObjectFactory(Signallable, Loggable):
46
 
    """
47
 
    Base class for all factory implementations.
48
 
 
49
 
    Factories are objects that create GStreamer bins to produce, process or
50
 
    render streams.
51
 
 
52
 
    @ivar name: Factory name.
53
 
    @type name: C{str}
54
 
    @ivar input_streams: List of input streams.
55
 
    @type input_streams: C{list}
56
 
    @ivar output_streams: List of output streams.
57
 
    @type output_streams: C{list}
58
 
    @ivar duration: Duration in nanoseconds.
59
 
    @type duration: C{int}
60
 
    @ivar default_duration: Default duration in nanoseconds. For most factories,
61
 
    L{duration} and L{default_duration} are equivalent. Factories that have an
62
 
    infinite duration may specify a default duration that will be used when they
63
 
    are added to the timeline.
64
 
    @type default_duration: C{int}
65
 
    @ivar icon: Icon associated with the factory.
66
 
    @type icon: C{str}
67
 
    @ivar bins: Bins controlled by the factory.
68
 
    @type bins: List of C{gst.Bin}
69
 
    """
70
 
 
71
 
    def __init__(self, name=""):
72
 
        Loggable.__init__(self)
73
 
        self.info("name:%s", name)
74
 
        self.parent = None
75
 
        self.name = name
76
 
        self.input_streams = []
77
 
        self.output_streams = []
78
 
        self.duration = gst.CLOCK_TIME_NONE
79
 
        self._default_duration = gst.CLOCK_TIME_NONE
80
 
        self._icon = None
81
 
        self.bins = []
82
 
 
83
 
    def _getDefaultDuration(self):
84
 
        if self._default_duration != gst.CLOCK_TIME_NONE:
85
 
            duration = self._default_duration
86
 
        elif self.duration != gst.CLOCK_TIME_NONE:
87
 
            duration = self.duration
88
 
        else:
89
 
            duration = gst.CLOCK_TIME_NONE
90
 
 
91
 
        return duration
92
 
 
93
 
    def _setDefaultDuration(self, default_duration):
94
 
        self._default_duration = default_duration
95
 
 
96
 
    default_duration = property(_getDefaultDuration, _setDefaultDuration)
97
 
 
98
 
    def _getIcon(self):
99
 
        icon = self._icon
100
 
        factory = self
101
 
        while icon is None and factory.parent:
102
 
            icon = factory.parent._icon
103
 
            factory = factory.parent
104
 
 
105
 
        return icon
106
 
 
107
 
    def _setIcon(self, icon):
108
 
        self._icon = icon
109
 
 
110
 
    icon = property(_getIcon, _setIcon)
111
 
 
112
 
    def _addStream(self, stream, stream_list):
113
 
        if stream in stream_list:
114
 
            raise ObjectFactoryStreamError('stream already added')
115
 
 
116
 
        stream_list.append(stream)
117
 
 
118
 
    def addInputStream(self, stream):
119
 
        """
120
 
        Add a stream to the list of inputs the factory can consume.
121
 
 
122
 
        @param stream: Stream
123
 
        @type stream: Instance of a L{MultimediaStream} derived class
124
 
        """
125
 
        self._addStream(stream, self.input_streams)
126
 
 
127
 
    def removeInputStream(self, stream):
128
 
        """
129
 
        Remove a stream from the list of inputs the factory can consume.
130
 
 
131
 
        @param stream: Stream
132
 
        @type stream: Instance of a L{MultimediaStream} derived class
133
 
        """
134
 
        self.input_streams.remove(stream)
135
 
 
136
 
    def addOutputStream(self, stream):
137
 
        """
138
 
        Add a stream to the list of outputs the factory can produce.
139
 
 
140
 
        @param stream: Stream
141
 
        @type stream: Instance of a L{MultimediaStream} derived class
142
 
        """
143
 
        self._addStream(stream, self.output_streams)
144
 
 
145
 
    def removeOutputStream(self, stream):
146
 
        """
147
 
        Remove a stream from the list of inputs the factory can produce.
148
 
 
149
 
        @param stream: Stream
150
 
        @type stream: Instance of a L{MultimediaStream} derived class
151
 
        """
152
 
        self.output_streams.remove(stream)
153
 
 
154
 
    def getOutputStreams(self, stream_classes=None):
155
 
        """
156
 
        Return the output streams.
157
 
 
158
 
        If specified, only the stream of the provided steam classes will be
159
 
        returned.
160
 
 
161
 
        @param stream_classes: If specified, the L{MultimediaStream} classes to
162
 
        filter with.
163
 
        @type stream_classes: one or many L{MultimediaStream} classes
164
 
        @return: The output streams.
165
 
        @rtype: List of L{MultimediaStream}
166
 
        """
167
 
        return [stream for stream in self.output_streams
168
 
                if stream_classes is None or isinstance(stream, stream_classes)]
169
 
 
170
 
    def getInputStreams(self, stream_classes=None):
171
 
        """
172
 
        Return the input streams.
173
 
 
174
 
        If specified, only the stream of the provided steam classes will be
175
 
        returned.
176
 
 
177
 
        @param stream_classes: If specified, the L{MultimediaStream} classes to
178
 
        filter with.
179
 
        @type stream_classes: one or many L{MultimediaStream} classes
180
 
        @return: The input streams.
181
 
        @rtype: List of L{MultimediaStream}
182
 
        """
183
 
        return [stream for stream in self.input_streams
184
 
                if stream_classes is None or isinstance(stream, stream_classes)]
185
 
 
186
 
    def clean(self):
187
 
        """
188
 
        Clean up a factory.
189
 
 
190
 
        Some factories allocate resources that have to be cleaned when a factory
191
 
        is not needed anymore.
192
 
        This should be the last method called on a factory before its disposed.
193
 
        """
194
 
 
195
 
    def __str__(self):
196
 
        return "<%s: %s>" % (self.__class__.__name__, self.name)
197
 
 
198
 
    def getInterpolatedProperties(self, stream):
199
 
        return {}
200
 
 
201
 
 
202
 
class SourceFactory(ObjectFactory):
203
 
    """
204
 
    Base class for factories that produce output and have no input.
205
 
 
206
 
    @ivar max_bins: Max number of bins the factory can create.
207
 
    @type max_bins: C{int}
208
 
    @ivar current_bins: Number of bin instances created and not released.
209
 
    @type current_bins: C{int}
210
 
    """
211
 
 
212
 
    __signals__ = {
213
 
        'bin-created': ['bin'],
214
 
        'bin-released': ['bin']
215
 
    }
216
 
 
217
 
    ffscale_factory = 'ffvideoscale'
218
 
 
219
 
    # make this an attribute to inject it from tests
220
 
    singleDecodeBinClass = SingleDecodeBin
221
 
 
222
 
    def __init__(self, uri, name=''):
223
 
        name = name or os.path.basename(unquote(uri))
224
 
        ObjectFactory.__init__(self, name)
225
 
        self.uri = uri
226
 
        self.max_bins = -1
227
 
        self.current_bins = 0
228
 
        self._filtercaps = gst.Caps("video/x-raw-rgb;video/x-raw-yuv")
229
 
 
230
 
    def getInterpolatedProperties(self, stream):
231
 
        self.debug("stream:%r", stream)
232
 
        props = ObjectFactory.getInterpolatedProperties(self, stream)
233
 
        if isinstance(stream, AudioStream):
234
 
            props.update({"volume": (0.0, 2.0, formatPercent)})
235
 
        elif isinstance(stream, VideoStream):
236
 
            props.update({"alpha": (0.0, 1.0, formatPercent)})
237
 
        self.debug("returning %r", props)
238
 
        return props
239
 
 
240
 
    def makeBin(self, output_stream=None):
241
 
        """
242
 
        Create a bin that outputs the stream described by C{output_stream}.
243
 
 
244
 
        If C{output_stream} is None, it's up to the implementations to return a
245
 
        suitable "default" bin.
246
 
 
247
 
        @param output_stream: A L{MultimediaStream}
248
 
        @return: The bin.
249
 
        @rtype: C{gst.Bin}
250
 
 
251
 
        @see: L{releaseBin}
252
 
        """
253
 
 
254
 
        compatible_stream = None
255
 
        self.debug("stream %r", output_stream)
256
 
 
257
 
        if output_stream is not None:
258
 
            self.debug("output_streams:%r", self.output_streams)
259
 
 
260
 
            # get the best stream from self.output_streams that matches
261
 
            # output_stream
262
 
            stream_map_rank = match_stream_groups([output_stream],
263
 
                    self.output_streams)
264
 
            stream_map = dict(stream_map_rank.keys())
265
 
            if output_stream not in stream_map:
266
 
                self.warning("stream not available in map %r", stream_map)
267
 
                raise ObjectFactoryError("can not create stream")
268
 
 
269
 
            compatible_stream = stream_map[output_stream]
270
 
            rank = stream_map_rank[output_stream, compatible_stream]
271
 
            if rank < STREAM_MATCH_COMPATIBLE_CAPS:
272
 
                raise ObjectFactoryError("can not create stream")
273
 
 
274
 
        if self.max_bins != -1 and self.current_bins == self.max_bins:
275
 
            raise ObjectFactoryError('no bins available')
276
 
 
277
 
        bin = self._makeBin(compatible_stream)
278
 
        bin.factory = self
279
 
        self.bins.append(bin)
280
 
        self.current_bins += 1
281
 
        self.emit('bin-created', bin)
282
 
 
283
 
        return bin
284
 
 
285
 
    def releaseBin(self, bin):
286
 
        """
287
 
        Release a bin created with L{makeBin}.
288
 
 
289
 
        Some factories can create a limited number of bins or implement caching.
290
 
        You should call C{releaseBin} once you are done using a bin.
291
 
        """
292
 
        bin.set_state(gst.STATE_NULL)
293
 
        self._releaseBin(bin)
294
 
        self.debug("Finally releasing %r", bin)
295
 
        self.current_bins -= 1
296
 
        self.bins.remove(bin)
297
 
        self.emit('bin-released', bin)
298
 
        del bin.factory
299
 
 
300
 
    def _makeBin(self, output_stream):
301
 
        if output_stream is None:
302
 
            return self._makeDefaultBin()
303
 
 
304
 
        return self._makeStreamBin(output_stream)
305
 
 
306
 
    def _makeDefaultBin(self):
307
 
        """
308
 
        Return a bin that decodes all the available streams.
309
 
 
310
 
        This is generally used to get an overview of the source media before
311
 
        splitting it in separate streams.
312
 
        """
313
 
        bin = gst.Bin("%s" % self.name)
314
 
        src = gst.element_make_from_uri(gst.URI_SRC, self.uri)
315
 
        try:
316
 
            dbin = gst.element_factory_make("decodebin2")
317
 
        except:
318
 
            dbin = gst.element_factory_make("decodebin")
319
 
        bin.add(src, dbin)
320
 
        src.link_pads_full("src", dbin, "sink", gst.PAD_LINK_CHECK_NOTHING)
321
 
 
322
 
        dbin.connect("new-decoded-pad", self._binNewDecodedPadCb, bin)
323
 
        dbin.connect("removed-decoded-pad", self._binRemovedDecodedPadCb, bin)
324
 
 
325
 
        bin.decodebin = dbin
326
 
        return bin
327
 
 
328
 
    def _binNewDecodedPadCb(self, unused_dbin, pad, unused_is_last, bin):
329
 
        ghost_pad = gst.GhostPad(pad.get_name(), pad)
330
 
        ghost_pad.set_active(True)
331
 
        bin.add_pad(ghost_pad)
332
 
 
333
 
    def _binRemovedDecodedPadCb(self, unused_dbin, pad, bin):
334
 
        ghost_pad = bin.get_pad(pad.get_name())
335
 
        bin.remove_pad(ghost_pad)
336
 
 
337
 
    def _releaseBin(self, bin):
338
 
        if hasattr(bin, "decodebin"):
339
 
            try:
340
 
                # bin is a bin returned from makeDefaultBin
341
 
                bin.decodebin.disconnect_by_func(self._binNewDecodedPadCb)
342
 
                bin.decodebin.disconnect_by_func(self._binRemovedDecodedPadCb)
343
 
            except TypeError:
344
 
                # bin is a stream bin
345
 
                bin.decodebin.disconnect_by_func(self._singlePadAddedCb)
346
 
                bin.decodebin.disconnect_by_func(self._singlePadRemovedCb)
347
 
            del bin.decodebin
348
 
 
349
 
        if hasattr(bin, "child"):
350
 
            bin.child.set_state(gst.STATE_NULL)
351
 
            del bin.child
352
 
 
353
 
        if hasattr(bin, "volume"):
354
 
            # only audio bins have a volume element
355
 
            for elt in [bin.aconv, bin.ares, bin.arate, bin.volume]:
356
 
                elt.set_state(gst.STATE_NULL)
357
 
                bin.remove(elt)
358
 
            del bin.volume
359
 
            del bin.aconv
360
 
            del bin.ares
361
 
            del bin.arate
362
 
        elif hasattr(bin, "alpha"):
363
 
            for elt in [bin.csp, bin.queue, bin.alpha, bin.capsfilter, bin.scale]:
364
 
                elt.set_state(gst.STATE_NULL)
365
 
                bin.remove(elt)
366
 
            del bin.queue
367
 
            del bin.csp
368
 
            del bin.alpha
369
 
            del bin.capsfilter
370
 
            del bin.scale
371
 
 
372
 
        if hasattr(bin, "ghostpad"):
373
 
            # singledecodebin found something on this pad
374
 
            bin.ghostpad.set_active(False)
375
 
            bin.remove_pad(bin.ghostpad)
376
 
            del bin.ghostpad
377
 
 
378
 
    def _makeStreamBinReal(self, output_stream):
379
 
        b = gst.Bin()
380
 
        b.decodebin = self.singleDecodeBinClass(uri=self.uri, caps=output_stream.caps,
381
 
                                           stream=output_stream)
382
 
        b.decodebin.connect("pad-added", self._singlePadAddedCb, b)
383
 
        b.decodebin.connect("pad-removed", self._singlePadRemovedCb, b)
384
 
        return b
385
 
 
386
 
    def _makeStreamBin(self, output_stream, child_bin=None):
387
 
        self.debug("output_stream:%r", output_stream)
388
 
        b = self._makeStreamBinReal(output_stream)
389
 
 
390
 
        if isinstance(output_stream, AudioStream):
391
 
            self._addCommonAudioElements(b, output_stream)
392
 
        elif isinstance(output_stream, VideoStream):
393
 
            self._addCommonVideoElements(b, output_stream, child_bin)
394
 
 
395
 
        if hasattr(b, "decodebin"):
396
 
            b.add(b.decodebin)
397
 
        return b
398
 
 
399
 
    def _singlePadAddedCb(self, dbin, pad, topbin):
400
 
        self.debug("dbin:%r, pad:%r, topbin:%r", dbin, pad, topbin)
401
 
        if hasattr(topbin, "child"):
402
 
            topbin.child.sync_state_with_parent()
403
 
        if hasattr(topbin, "volume"):
404
 
            # make sure audio elements reach our same state. This is needed
405
 
            # since those elements are still unlinked downstream at this point,
406
 
            # so state change order doesn't happen in the usual
407
 
            # downstream-to-upstream way.
408
 
            for element in [topbin.aconv, topbin.ares, topbin.arate, topbin.volume]:
409
 
                element.sync_state_with_parent()
410
 
 
411
 
            pad.link_full(topbin.aconv.get_pad("sink"), gst.PAD_LINK_CHECK_NOTHING)
412
 
            topbin.ghostpad = gst.GhostPad("src", topbin.volume.get_pad("src"))
413
 
        elif hasattr(topbin, "alpha"):
414
 
            for element in [topbin.queue, topbin.scale, topbin.csp, topbin.alpha, topbin.capsfilter]:
415
 
                element.sync_state_with_parent()
416
 
 
417
 
            pad.link_full(topbin.queue.get_pad("sink"), gst.PAD_LINK_CHECK_NOTHING)
418
 
            topbin.ghostpad = gst.GhostPad("src", topbin.capsfilter.get_pad("src"))
419
 
        else:
420
 
            topbin.ghostpad = gst.GhostPad("src", pad)
421
 
 
422
 
        if pad.props.caps is not None:
423
 
            topbin.ghostpad.set_caps(pad.props.caps)
424
 
        topbin.ghostpad.set_active(True)
425
 
        topbin.add_pad(topbin.ghostpad)
426
 
 
427
 
    def _singlePadRemovedCb(self, dbin, pad, topbin):
428
 
        self.debug("dbin:%r, pad:%r, topbin:%r", dbin, pad, topbin)
429
 
 
430
 
        # work around for http://bugzilla.gnome.org/show_bug.cgi?id=590735
431
 
        if hasattr(topbin, "ghostpad"):
432
 
            die = gst.Pad("die", gst.PAD_SRC)
433
 
            topbin.ghostpad.set_target(die)
434
 
 
435
 
            topbin.remove_pad(topbin.ghostpad)
436
 
            del topbin.ghostpad
437
 
 
438
 
        if hasattr(topbin, "volume"):
439
 
            pad.unlink(topbin.aconv.get_pad("sink"))
440
 
        elif hasattr(topbin, "alpha"):
441
 
            pad.unlink(topbin.queue.get_pad("sink"))
442
 
 
443
 
    def addInputStream(self, stream):
444
 
        raise AssertionError("source factories can't have input streams")
445
 
 
446
 
    def setFilterCaps(self, caps, b=None):
447
 
        caps_copy = gst.Caps(caps)
448
 
        for structure in caps_copy:
449
 
            # remove framerate as we don't adjust framerate here
450
 
            if structure.has_field("framerate"):
451
 
                del structure["framerate"]
452
 
            # remove format as we will have converted to AYUV/ARGB
453
 
            if structure.has_field("format"):
454
 
                del structure["format"]
455
 
        if b is None:
456
 
            for bin in self.bins:
457
 
                if hasattr(bin, "capsfilter"):
458
 
                    bin.capsfilter.props.caps = caps_copy
459
 
        else:
460
 
            b.capsfilter.props.caps = caps_copy
461
 
        self._filtercaps = caps_copy
462
 
 
463
 
    def _addCommonVideoElements(self, video_bin, output_stream, child_bin=None):
464
 
        if child_bin:
465
 
            video_bin.child = child_bin
466
 
            video_bin.add(child_bin)
467
 
 
468
 
        video_bin.queue = gst.element_factory_make("queue", "internal-queue")
469
 
        video_bin.queue.props.max_size_bytes = 0
470
 
        video_bin.queue.props.max_size_time = 0
471
 
        video_bin.queue.props.max_size_buffers = 3
472
 
 
473
 
        # all video needs to be AYUV, but the colorspace conversion
474
 
        # element depends on the input. if there is no alpha we need to
475
 
        # add ffmpegcolorspace. if we have an argb or rgba stream, we need
476
 
        # alphacolor to preserve the alpha channel (ffmpeg clobbers it).
477
 
        # if we have an ayuv stream we don't want any colorspace
478
 
        # converter.
479
 
 
480
 
        if not output_stream.has_alpha():
481
 
            video_bin.csp = gst.element_factory_make("ffmpegcolorspace",
482
 
                "internal-colorspace")
483
 
        elif output_stream.videotype == 'video/x-raw-rgb':
484
 
            video_bin.csp = gst.element_factory_make("alphacolor",
485
 
                "internal-alphacolor")
486
 
        else:
487
 
            video_bin.csp = gst.element_factory_make("identity")
488
 
 
489
 
        video_bin.alpha = gst.element_factory_make("alpha", "internal-alpha")
490
 
 
491
 
        try:
492
 
            video_bin.alpha.props.prefer_passthrough = True
493
 
        except AttributeError:
494
 
            self.warning("User has old version of alpha. "
495
 
                         "prefer-passthrough not enabled")
496
 
 
497
 
        video_bin.scale = gst.element_factory_make("videoscale")
498
 
        try:
499
 
            video_bin.scale.props.add_borders = True
500
 
        except AttributeError:
501
 
            self.warning("User has old version of videoscale. "
502
 
                    "add-border not enabled")
503
 
        video_bin.capsfilter = gst.element_factory_make("capsfilter",
504
 
                "capsfilter-proj-settings")
505
 
        self.setFilterCaps(self._filtercaps, video_bin)
506
 
 
507
 
        video_bin.add(video_bin.queue, video_bin.scale, video_bin.csp,
508
 
                video_bin.alpha, video_bin.capsfilter)
509
 
        video_bin.queue.link_pads_full("src", video_bin.csp, "sink", gst.PAD_LINK_CHECK_NOTHING)
510
 
        video_bin.csp.link_pads_full("src", video_bin.scale, "sink", gst.PAD_LINK_CHECK_NOTHING)
511
 
        if child_bin is not None:
512
 
            gst.element_link_many(video_bin.scale, video_bin.child,
513
 
                    video_bin.alpha)
514
 
            video_bin.alpha.link_pads_full("src", video_bin.capsfilter, "sink", gst.PAD_LINK_CHECK_NOTHING)
515
 
            video_bin.child.sync_state_with_parent()
516
 
        else:
517
 
            video_bin.scale.link_pads_full("src", video_bin.alpha, "sink", gst.PAD_LINK_CHECK_NOTHING)
518
 
            video_bin.alpha.link_pads_full("src", video_bin.capsfilter, "sink", gst.PAD_LINK_CHECK_NOTHING)
519
 
 
520
 
        video_bin.capsfilter.sync_state_with_parent()
521
 
        video_bin.scale.sync_state_with_parent()
522
 
        video_bin.queue.sync_state_with_parent()
523
 
        video_bin.csp.sync_state_with_parent()
524
 
        video_bin.alpha.sync_state_with_parent()
525
 
 
526
 
    def _addCommonAudioElements(self, audio_bin, output_stream):
527
 
        self.debug("Adding volume element")
528
 
        # add a volume element
529
 
        audio_bin.aconv = gst.element_factory_make("audioconvert", "internal-aconv")
530
 
        audio_bin.ares = gst.element_factory_make("audioresample", "internal-audioresample")
531
 
        # Fix audio jitter of up to 40ms
532
 
        audio_bin.arate = gst.element_factory_make("audiorate", "internal-audiorate")
533
 
        audio_bin.arate.props.tolerance = 40 * gst.MSECOND
534
 
        audio_bin.volume = gst.element_factory_make("volume", "internal-volume")
535
 
        audio_bin.add(audio_bin.volume, audio_bin.ares, audio_bin.aconv, audio_bin.arate)
536
 
        #if child_bin:
537
 
        #    gst.element_link_many(audio_bin.aconv, audio_bin.ares, audio_bin.arate, audio_bin.child, audio_bin.volume)
538
 
        #    audio_bin.child.sync_state_with_parent()
539
 
        #else:
540
 
        audio_bin.aconv.link_pads_full("src", audio_bin.ares, "sink", gst.PAD_LINK_CHECK_NOTHING)
541
 
        audio_bin.ares.link_pads_full("src", audio_bin.arate, "sink", gst.PAD_LINK_CHECK_NOTHING)
542
 
        audio_bin.arate.link_pads_full("src", audio_bin.volume, "sink", gst.PAD_LINK_CHECK_NOTHING)
543
 
 
544
 
        audio_bin.aconv.sync_state_with_parent()
545
 
        audio_bin.ares.sync_state_with_parent()
546
 
        audio_bin.arate.sync_state_with_parent()
547
 
        audio_bin.volume.sync_state_with_parent()
548
 
 
549
 
 
550
 
class SinkFactory(ObjectFactory):
551
 
    """
552
 
    Base class for factories that consume input and have no output.
553
 
 
554
 
    @ivar max_bins: Max number of bins the factory can create.
555
 
    @type max_bins: C{int}
556
 
    @ivar current_bins: Number of bin instances created and not released.
557
 
    @type current_bins: C{int}
558
 
    """
559
 
 
560
 
    __signals__ = {
561
 
        'bin-created': ['bin'],
562
 
        'bin-released': ['bin']
563
 
    }
564
 
 
565
 
    def __init__(self, name=''):
566
 
        ObjectFactory.__init__(self, name)
567
 
        self.max_bins = -1
568
 
        self.current_bins = 0
569
 
 
570
 
    def makeBin(self, input_stream=None):
571
 
        """
572
 
        Create a bin that consumes the stream described by C{input_stream}.
573
 
 
574
 
        If C{input_stream} is None, it's up to the implementations to return a
575
 
        suitable "default" bin.
576
 
 
577
 
        @param input_stream: A L{MultimediaStream}
578
 
        @return: The bin.
579
 
        @rtype: C{gst.Bin}
580
 
 
581
 
        @see: L{releaseBin}
582
 
        """
583
 
 
584
 
        self.debug("stream %r", input_stream)
585
 
        compatible_stream = None
586
 
        if input_stream is not None:
587
 
            self.debug("Streams %r", self.input_streams)
588
 
            for stream in self.input_streams:
589
 
                if input_stream.isCompatible(stream):
590
 
                    compatible_stream = stream
591
 
                    break
592
 
 
593
 
            if compatible_stream is None:
594
 
                raise ObjectFactoryError('unknown stream')
595
 
 
596
 
        if self.max_bins != -1 and self.current_bins == self.max_bins:
597
 
            raise ObjectFactoryError('no bins available')
598
 
 
599
 
        bin = self._makeBin(input_stream)
600
 
        bin.factory = self
601
 
        self.bins.append(bin)
602
 
        self.current_bins += 1
603
 
        self.emit('bin-created', bin)
604
 
 
605
 
        return bin
606
 
 
607
 
    def _makeBin(self, input_stream=None):
608
 
        raise NotImplementedError()
609
 
 
610
 
    def requestNewInputStream(self, bin, input_stream):
611
 
        """
612
 
        Request a new input stream on a bin.
613
 
 
614
 
        @param bin: The C{gst.Bin} on which we request a new stream.
615
 
        @param input_stream: The new input C{MultimediaStream} we're requesting.
616
 
        @raise ObjectFactoryStreamError: If the L{input_stream} isn't compatible
617
 
        with one of the factory's L{input_streams}.
618
 
        @return: The pad corresponding to the newly created input stream.
619
 
        @rtype: C{gst.Pad}
620
 
        """
621
 
        if not hasattr(bin, 'factory') or bin.factory != self:
622
 
            raise ObjectFactoryError("The provided bin isn't handled by this Factory")
623
 
        for ins in self.input_streams:
624
 
            if ins.isCompatible(input_stream):
625
 
                return self._requestNewInputStream(bin, input_stream)
626
 
        raise ObjectFactoryError("Incompatible stream")
627
 
 
628
 
    def _requestNewInputStream(self, bin, input_stream):
629
 
        raise NotImplementedError
630
 
 
631
 
    def releaseBin(self, bin):
632
 
        """
633
 
        Release a bin created with L{makeBin}.
634
 
 
635
 
        Some factories can create a limited number of bins or implement caching.
636
 
        You should call C{releaseBin} once you are done using a bin.
637
 
        """
638
 
        bin.set_state(gst.STATE_NULL)
639
 
        self._releaseBin(bin)
640
 
        self.bins.remove(bin)
641
 
        self.current_bins -= 1
642
 
        del bin.factory
643
 
        self.emit('bin-released', bin)
644
 
 
645
 
    def _releaseBin(self, bin):
646
 
        # default implementation does nothing
647
 
        pass
648
 
 
649
 
    def addOutputStream(self, stream):
650
 
        raise AssertionError("sink factories can't have output streams")
651
 
 
652
 
 
653
 
class OperationFactory(ObjectFactory):
654
 
    """
655
 
    Base class for factories that process data (inputs data AND outputs data).
656
 
    @ivar max_bins: Max number of bins the factory can create.
657
 
    @type max_bins: C{int}
658
 
    @ivar current_bins: Number of bin instances created and not released.
659
 
    @type current_bins: C{int}
660
 
    """
661
 
 
662
 
    __signals__ = {
663
 
        'bin-created': ['bin'],
664
 
        'bin-released': ['bin']
665
 
    }
666
 
 
667
 
    def __init__(self, name=''):
668
 
        ObjectFactory.__init__(self, name)
669
 
        self.max_bins = -1
670
 
        self.current_bins = 0
671
 
 
672
 
    def makeBin(self, input_stream=None, output_stream=None):
673
 
        """
674
 
        Create a bin that consumes the stream described by C{input_stream}.
675
 
 
676
 
        If C{input_stream} and/or C{output_stream} are None, it's up to the
677
 
        implementations to return a suitable "default" bin.
678
 
 
679
 
        @param input_stream: A L{MultimediaStream}
680
 
        @param output_stream: A L{MultimediaStream}
681
 
        @return: The bin.
682
 
        @rtype: C{gst.Bin}
683
 
 
684
 
        @see: L{releaseBin}
685
 
        """
686
 
 
687
 
        if input_stream is not None and \
688
 
                input_stream not in self.input_streams:
689
 
            raise ObjectFactoryError('unknown stream')
690
 
 
691
 
        bin = self._makeBin(input_stream)
692
 
        bin.factory = self
693
 
        self.bins.append(bin)
694
 
        self.current_bins += 1
695
 
        self.emit('bin-created', bin)
696
 
 
697
 
        return bin
698
 
 
699
 
    def _makeBin(self, input_stream=None, output_stream=None):
700
 
        raise NotImplementedError()
701
 
 
702
 
    def requestNewInputStream(self, bin, input_stream):
703
 
        """
704
 
        Request a new input stream on a bin.
705
 
 
706
 
        @param bin: The C{gst.Bin} on which we request a new stream.
707
 
        @param input_stream: The new input C{MultimediaStream} we're requesting.
708
 
        @raise ObjectFactoryStreamError: If the L{input_stream} isn't compatible
709
 
        with one of the factory's L{input_streams}.
710
 
        @return: The pad corresponding to the newly created input stream.
711
 
        @rtype: C{gst.Pad}
712
 
        """
713
 
        if not hasattr(bin, 'factory') or bin.factory != self:
714
 
            raise ObjectFactoryError("The provided bin isn't handled by this Factory")
715
 
        for ins in self.input_streams:
716
 
            if ins.isCompatible(input_stream):
717
 
                return self._requestNewInputStream(bin, input_stream)
718
 
        raise ObjectFactoryError("Incompatible stream")
719
 
 
720
 
    def _requestNewInputStream(self, bin, input_stream):
721
 
        raise NotImplementedError
722
 
 
723
 
    def releaseBin(self, bin):
724
 
        """
725
 
        Release a bin created with L{makeBin}.
726
 
 
727
 
        Some factories can create a limited number of bins or implement caching.
728
 
        You should call C{releaseBin} once you are done using a bin.
729
 
        """
730
 
        bin.set_state(gst.STATE_NULL)
731
 
        self._releaseBin(bin)
732
 
        self.bins.remove(bin)
733
 
        self.current_bins -= 1
734
 
        del bin.factory
735
 
        self.emit('bin-released', bin)
736
 
 
737
 
    def _releaseBin(self, bin):
738
 
        # default implementation does nothing
739
 
        pass
740
 
 
741
 
 
742
 
class LiveSourceFactory(SourceFactory):
743
 
    """
744
 
    Base class for factories that produce live streams.
745
 
 
746
 
    The duration of a live source is unknown and it's possibly infinite. The
747
 
    default duration is set to 5 seconds to a live source can be managed in a
748
 
    timeline.
749
 
    """
750
 
 
751
 
    def __init__(self, uri, name='', default_duration=None):
752
 
        SourceFactory.__init__(self, uri, name)
753
 
        if default_duration is None:
754
 
            default_duration = 5 * gst.SECOND
755
 
 
756
 
        self.default_duration = default_duration
757
 
 
758
 
 
759
 
class RandomAccessSourceFactory(SourceFactory):
760
 
    """
761
 
    Base class for source factories that support random access.
762
 
 
763
 
    @ivar offset: Offset in nanoseconds from the beginning of the stream.
764
 
    @type offset: C{int}
765
 
    @ivar offset_length: Length in nanoseconds.
766
 
    @type offset_length: C{int}
767
 
    @ivar abs_offset: Absolute offset from the beginning of the stream.
768
 
    @type abs_offset: C{int}
769
 
    @ivar abs_offset_length: Length in nanoseconds, clamped to avoid overflowing
770
 
    the parent's length if any.
771
 
    @type abs_offset_length: C{int}
772
 
    """
773
 
 
774
 
    def __init__(self, uri, name='',
775
 
            offset=0, offset_length=gst.CLOCK_TIME_NONE):
776
 
        self.offset = offset
777
 
        self.offset_length = offset_length
778
 
 
779
 
        SourceFactory.__init__(self, uri, name)
780
 
 
781
 
    def _getAbsOffset(self):
782
 
        if self.parent is None:
783
 
            offset = self.offset
784
 
        else:
785
 
            parent_offset = self.parent.offset
786
 
            parent_length = self.parent.offset_length
787
 
 
788
 
            offset = min(self.parent.offset + self.offset,
789
 
                    self.parent.offset + self.parent.offset_length)
790
 
 
791
 
        return offset
792
 
 
793
 
    abs_offset = property(_getAbsOffset)
794
 
 
795
 
    def _getAbsOffsetLength(self):
796
 
        if self.parent is None:
797
 
            offset_length = self.offset_length
798
 
        else:
799
 
            parent_end = self.parent.abs_offset + self.parent.abs_offset_length
800
 
            end = self.abs_offset + self.offset_length
801
 
            abs_end = min(end, parent_end)
802
 
            offset_length = abs_end - self.abs_offset
803
 
 
804
 
        return offset_length
805
 
 
806
 
    abs_offset_length = property(_getAbsOffsetLength)