1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2005-2008, Edward Hervey <bilboed@bilboed.com>
6
# 2008, Alessandro Decina <alessandro.decina@collabora.co.uk>
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.
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.
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.
24
from urllib import unquote
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
34
# FIXME: define a proper hierarchy
37
class ObjectFactoryError(Exception):
41
class ObjectFactoryStreamError(ObjectFactoryError):
45
class ObjectFactory(Signallable, Loggable):
47
Base class for all factory implementations.
49
Factories are objects that create GStreamer bins to produce, process or
52
@ivar name: Factory name.
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.
67
@ivar bins: Bins controlled by the factory.
68
@type bins: List of C{gst.Bin}
71
def __init__(self, name=""):
72
Loggable.__init__(self)
73
self.info("name:%s", name)
76
self.input_streams = []
77
self.output_streams = []
78
self.duration = gst.CLOCK_TIME_NONE
79
self._default_duration = gst.CLOCK_TIME_NONE
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
89
duration = gst.CLOCK_TIME_NONE
93
def _setDefaultDuration(self, default_duration):
94
self._default_duration = default_duration
96
default_duration = property(_getDefaultDuration, _setDefaultDuration)
101
while icon is None and factory.parent:
102
icon = factory.parent._icon
103
factory = factory.parent
107
def _setIcon(self, icon):
110
icon = property(_getIcon, _setIcon)
112
def _addStream(self, stream, stream_list):
113
if stream in stream_list:
114
raise ObjectFactoryStreamError('stream already added')
116
stream_list.append(stream)
118
def addInputStream(self, stream):
120
Add a stream to the list of inputs the factory can consume.
122
@param stream: Stream
123
@type stream: Instance of a L{MultimediaStream} derived class
125
self._addStream(stream, self.input_streams)
127
def removeInputStream(self, stream):
129
Remove a stream from the list of inputs the factory can consume.
131
@param stream: Stream
132
@type stream: Instance of a L{MultimediaStream} derived class
134
self.input_streams.remove(stream)
136
def addOutputStream(self, stream):
138
Add a stream to the list of outputs the factory can produce.
140
@param stream: Stream
141
@type stream: Instance of a L{MultimediaStream} derived class
143
self._addStream(stream, self.output_streams)
145
def removeOutputStream(self, stream):
147
Remove a stream from the list of inputs the factory can produce.
149
@param stream: Stream
150
@type stream: Instance of a L{MultimediaStream} derived class
152
self.output_streams.remove(stream)
154
def getOutputStreams(self, stream_classes=None):
156
Return the output streams.
158
If specified, only the stream of the provided steam classes will be
161
@param stream_classes: If specified, the L{MultimediaStream} classes to
163
@type stream_classes: one or many L{MultimediaStream} classes
164
@return: The output streams.
165
@rtype: List of L{MultimediaStream}
167
return [stream for stream in self.output_streams
168
if stream_classes is None or isinstance(stream, stream_classes)]
170
def getInputStreams(self, stream_classes=None):
172
Return the input streams.
174
If specified, only the stream of the provided steam classes will be
177
@param stream_classes: If specified, the L{MultimediaStream} classes to
179
@type stream_classes: one or many L{MultimediaStream} classes
180
@return: The input streams.
181
@rtype: List of L{MultimediaStream}
183
return [stream for stream in self.input_streams
184
if stream_classes is None or isinstance(stream, stream_classes)]
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.
196
return "<%s: %s>" % (self.__class__.__name__, self.name)
198
def getInterpolatedProperties(self, stream):
202
class SourceFactory(ObjectFactory):
204
Base class for factories that produce output and have no input.
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}
213
'bin-created': ['bin'],
214
'bin-released': ['bin']
217
ffscale_factory = 'ffvideoscale'
219
# make this an attribute to inject it from tests
220
singleDecodeBinClass = SingleDecodeBin
222
def __init__(self, uri, name=''):
223
name = name or os.path.basename(unquote(uri))
224
ObjectFactory.__init__(self, name)
227
self.current_bins = 0
228
self._filtercaps = gst.Caps("video/x-raw-rgb;video/x-raw-yuv")
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)
240
def makeBin(self, output_stream=None):
242
Create a bin that outputs the stream described by C{output_stream}.
244
If C{output_stream} is None, it's up to the implementations to return a
245
suitable "default" bin.
247
@param output_stream: A L{MultimediaStream}
254
compatible_stream = None
255
self.debug("stream %r", output_stream)
257
if output_stream is not None:
258
self.debug("output_streams:%r", self.output_streams)
260
# get the best stream from self.output_streams that matches
262
stream_map_rank = match_stream_groups([output_stream],
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")
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")
274
if self.max_bins != -1 and self.current_bins == self.max_bins:
275
raise ObjectFactoryError('no bins available')
277
bin = self._makeBin(compatible_stream)
279
self.bins.append(bin)
280
self.current_bins += 1
281
self.emit('bin-created', bin)
285
def releaseBin(self, bin):
287
Release a bin created with L{makeBin}.
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.
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)
300
def _makeBin(self, output_stream):
301
if output_stream is None:
302
return self._makeDefaultBin()
304
return self._makeStreamBin(output_stream)
306
def _makeDefaultBin(self):
308
Return a bin that decodes all the available streams.
310
This is generally used to get an overview of the source media before
311
splitting it in separate streams.
313
bin = gst.Bin("%s" % self.name)
314
src = gst.element_make_from_uri(gst.URI_SRC, self.uri)
316
dbin = gst.element_factory_make("decodebin2")
318
dbin = gst.element_factory_make("decodebin")
320
src.link_pads_full("src", dbin, "sink", gst.PAD_LINK_CHECK_NOTHING)
322
dbin.connect("new-decoded-pad", self._binNewDecodedPadCb, bin)
323
dbin.connect("removed-decoded-pad", self._binRemovedDecodedPadCb, bin)
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)
333
def _binRemovedDecodedPadCb(self, unused_dbin, pad, bin):
334
ghost_pad = bin.get_pad(pad.get_name())
335
bin.remove_pad(ghost_pad)
337
def _releaseBin(self, bin):
338
if hasattr(bin, "decodebin"):
340
# bin is a bin returned from makeDefaultBin
341
bin.decodebin.disconnect_by_func(self._binNewDecodedPadCb)
342
bin.decodebin.disconnect_by_func(self._binRemovedDecodedPadCb)
344
# bin is a stream bin
345
bin.decodebin.disconnect_by_func(self._singlePadAddedCb)
346
bin.decodebin.disconnect_by_func(self._singlePadRemovedCb)
349
if hasattr(bin, "child"):
350
bin.child.set_state(gst.STATE_NULL)
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)
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)
372
if hasattr(bin, "ghostpad"):
373
# singledecodebin found something on this pad
374
bin.ghostpad.set_active(False)
375
bin.remove_pad(bin.ghostpad)
378
def _makeStreamBinReal(self, output_stream):
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)
386
def _makeStreamBin(self, output_stream, child_bin=None):
387
self.debug("output_stream:%r", output_stream)
388
b = self._makeStreamBinReal(output_stream)
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)
395
if hasattr(b, "decodebin"):
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()
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()
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"))
420
topbin.ghostpad = gst.GhostPad("src", pad)
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)
427
def _singlePadRemovedCb(self, dbin, pad, topbin):
428
self.debug("dbin:%r, pad:%r, topbin:%r", dbin, pad, topbin)
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)
435
topbin.remove_pad(topbin.ghostpad)
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"))
443
def addInputStream(self, stream):
444
raise AssertionError("source factories can't have input streams")
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"]
456
for bin in self.bins:
457
if hasattr(bin, "capsfilter"):
458
bin.capsfilter.props.caps = caps_copy
460
b.capsfilter.props.caps = caps_copy
461
self._filtercaps = caps_copy
463
def _addCommonVideoElements(self, video_bin, output_stream, child_bin=None):
465
video_bin.child = child_bin
466
video_bin.add(child_bin)
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
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
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")
487
video_bin.csp = gst.element_factory_make("identity")
489
video_bin.alpha = gst.element_factory_make("alpha", "internal-alpha")
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")
497
video_bin.scale = gst.element_factory_make("videoscale")
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)
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,
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()
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)
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()
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)
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()
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)
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()
550
class SinkFactory(ObjectFactory):
552
Base class for factories that consume input and have no output.
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}
561
'bin-created': ['bin'],
562
'bin-released': ['bin']
565
def __init__(self, name=''):
566
ObjectFactory.__init__(self, name)
568
self.current_bins = 0
570
def makeBin(self, input_stream=None):
572
Create a bin that consumes the stream described by C{input_stream}.
574
If C{input_stream} is None, it's up to the implementations to return a
575
suitable "default" bin.
577
@param input_stream: A L{MultimediaStream}
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
593
if compatible_stream is None:
594
raise ObjectFactoryError('unknown stream')
596
if self.max_bins != -1 and self.current_bins == self.max_bins:
597
raise ObjectFactoryError('no bins available')
599
bin = self._makeBin(input_stream)
601
self.bins.append(bin)
602
self.current_bins += 1
603
self.emit('bin-created', bin)
607
def _makeBin(self, input_stream=None):
608
raise NotImplementedError()
610
def requestNewInputStream(self, bin, input_stream):
612
Request a new input stream on a bin.
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.
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")
628
def _requestNewInputStream(self, bin, input_stream):
629
raise NotImplementedError
631
def releaseBin(self, bin):
633
Release a bin created with L{makeBin}.
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.
638
bin.set_state(gst.STATE_NULL)
639
self._releaseBin(bin)
640
self.bins.remove(bin)
641
self.current_bins -= 1
643
self.emit('bin-released', bin)
645
def _releaseBin(self, bin):
646
# default implementation does nothing
649
def addOutputStream(self, stream):
650
raise AssertionError("sink factories can't have output streams")
653
class OperationFactory(ObjectFactory):
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}
663
'bin-created': ['bin'],
664
'bin-released': ['bin']
667
def __init__(self, name=''):
668
ObjectFactory.__init__(self, name)
670
self.current_bins = 0
672
def makeBin(self, input_stream=None, output_stream=None):
674
Create a bin that consumes the stream described by C{input_stream}.
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.
679
@param input_stream: A L{MultimediaStream}
680
@param output_stream: A L{MultimediaStream}
687
if input_stream is not None and \
688
input_stream not in self.input_streams:
689
raise ObjectFactoryError('unknown stream')
691
bin = self._makeBin(input_stream)
693
self.bins.append(bin)
694
self.current_bins += 1
695
self.emit('bin-created', bin)
699
def _makeBin(self, input_stream=None, output_stream=None):
700
raise NotImplementedError()
702
def requestNewInputStream(self, bin, input_stream):
704
Request a new input stream on a bin.
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.
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")
720
def _requestNewInputStream(self, bin, input_stream):
721
raise NotImplementedError
723
def releaseBin(self, bin):
725
Release a bin created with L{makeBin}.
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.
730
bin.set_state(gst.STATE_NULL)
731
self._releaseBin(bin)
732
self.bins.remove(bin)
733
self.current_bins -= 1
735
self.emit('bin-released', bin)
737
def _releaseBin(self, bin):
738
# default implementation does nothing
742
class LiveSourceFactory(SourceFactory):
744
Base class for factories that produce live streams.
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
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
756
self.default_duration = default_duration
759
class RandomAccessSourceFactory(SourceFactory):
761
Base class for source factories that support random access.
763
@ivar offset: Offset in nanoseconds from the beginning of the stream.
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}
774
def __init__(self, uri, name='',
775
offset=0, offset_length=gst.CLOCK_TIME_NONE):
777
self.offset_length = offset_length
779
SourceFactory.__init__(self, uri, name)
781
def _getAbsOffset(self):
782
if self.parent is None:
785
parent_offset = self.parent.offset
786
parent_length = self.parent.offset_length
788
offset = min(self.parent.offset + self.offset,
789
self.parent.offset + self.parent.offset_length)
793
abs_offset = property(_getAbsOffset)
795
def _getAbsOffsetLength(self):
796
if self.parent is None:
797
offset_length = self.offset_length
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
806
abs_offset_length = property(_getAbsOffsetLength)