~nico-inattendu/luciole/0.9

« back to all changes in this revision

Viewing changes to pitivi/factories/base.py

  • Committer: NicoInattendu
  • Date: 2011-02-28 18:27:56 UTC
  • mfrom: (123.1.54 luciole-with-sound)
  • Revision ID: nico@inattendu.org-20110228182756-weonszu8zpzermrl
initial merge with luciole-with-sound branch

Show diffs side-by-side

added added

removed removed

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