2
# PiTiVi , Non-linear video editor
6
# Copyright (c) 2005-2008, Edward Hervey <bilboed@bilboed.com>
7
# 2008, Alessandro Decina <alessandro.decina@collabora.co.uk>
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.
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.
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.
25
from urllib import unquote
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
35
# FIXME: define a proper hierarchy
36
class ObjectFactoryError(Exception):
39
class ObjectFactoryStreamError(ObjectFactoryError):
42
class ObjectFactory(Signallable, Loggable):
44
Base class for all factory implementations.
46
Factories are objects that create GStreamer bins to produce, process or
49
@ivar name: Factory name.
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.
64
@ivar bins: Bins controlled by the factory.
65
@type bins: List of C{gst.Bin}
68
def __init__(self, name=""):
69
Loggable.__init__(self)
70
self.info("name:%s", name)
73
self.input_streams = []
74
self.output_streams = []
75
self.duration = gst.CLOCK_TIME_NONE
76
self._default_duration = gst.CLOCK_TIME_NONE
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
86
duration = gst.CLOCK_TIME_NONE
90
def _setDefaultDuration(self, default_duration):
91
self._default_duration = default_duration
93
default_duration = property(_getDefaultDuration, _setDefaultDuration)
98
while icon is None and factory.parent:
99
icon = factory.parent._icon
100
factory = factory.parent
104
def _setIcon(self, icon):
107
icon = property(_getIcon, _setIcon)
109
def _addStream(self, stream, stream_list):
110
if stream in stream_list:
111
raise ObjectFactoryStreamError('stream already added')
113
stream_list.append(stream)
115
def addInputStream(self, stream):
117
Add a stream to the list of inputs the factory can consume.
119
@param stream: Stream
120
@type stream: Instance of a L{MultimediaStream} derived class
122
self._addStream(stream, self.input_streams)
124
def removeInputStream(self, stream):
126
Remove a stream from the list of inputs the factory can consume.
128
@param stream: Stream
129
@type stream: Instance of a L{MultimediaStream} derived class
131
self.input_streams.remove(stream)
133
def addOutputStream(self, stream):
135
Add a stream to the list of outputs the factory can produce.
137
@param stream: Stream
138
@type stream: Instance of a L{MultimediaStream} derived class
140
self._addStream(stream, self.output_streams)
142
def removeOutputStream(self, stream):
144
Remove a stream from the list of inputs the factory can produce.
146
@param stream: Stream
147
@type stream: Instance of a L{MultimediaStream} derived class
149
self.output_streams.remove(stream)
151
def getOutputStreams(self, stream_classes=None):
153
Return the output streams.
155
If specified, only the stream of the provided steam classes will be
158
@param stream_classes: If specified, the L{MultimediaStream} classes to
160
@type stream_classes: one or many L{MultimediaStream} classes
161
@return: The output streams.
162
@rtype: List of L{MultimediaStream}
164
return [stream for stream in self.output_streams
165
if stream_classes is None or isinstance(stream, stream_classes)]
167
def getInputStreams(self, stream_classes=None):
169
Return the input streams.
171
If specified, only the stream of the provided steam classes will be
174
@param stream_classes: If specified, the L{MultimediaStream} classes to
176
@type stream_classes: one or many L{MultimediaStream} classes
177
@return: The input streams.
178
@rtype: List of L{MultimediaStream}
180
return [stream for stream in self.input_streams
181
if stream_classes is None or isinstance(stream, stream_classes)]
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.
193
return "<%s: %s>" % (self.__class__.__name__, self.name)
195
def getInterpolatedProperties(self, stream):
198
class SourceFactory(ObjectFactory):
200
Base class for factories that produce output and have no input.
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}
209
'bin-created': ['bin'],
210
'bin-released': ['bin']
213
ffscale_factory = 'ffvideoscale'
215
# make this an attribute to inject it from tests
216
singleDecodeBinClass = SingleDecodeBin
218
def __init__(self, uri, name=''):
219
name = name or os.path.basename(unquote(uri))
220
ObjectFactory.__init__(self, name)
223
self.current_bins = 0
224
self._filtercaps = gst.Caps("video/x-raw-rgb;video/x-raw-yuv")
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)
236
def makeBin(self, output_stream=None):
238
Create a bin that outputs the stream described by C{output_stream}.
240
If C{output_stream} is None, it's up to the implementations to return a
241
suitable "default" bin.
243
@param output_stream: A L{MultimediaStream}
250
compatible_stream = None
251
self.debug("stream %r", output_stream)
253
if output_stream is not None:
254
self.debug("output_streams:%r", self.output_streams)
256
# get the best stream from self.output_streams that matches
258
stream_map_rank = match_stream_groups([output_stream],
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")
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")
270
if self.max_bins != -1 and self.current_bins == self.max_bins:
271
raise ObjectFactoryError('no bins available')
273
bin = self._makeBin(compatible_stream)
275
self.bins.append(bin)
276
self.current_bins += 1
277
self.emit('bin-created', bin)
281
def releaseBin(self, bin):
283
Release a bin created with L{makeBin}.
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.
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)
296
def _makeBin(self, output_stream):
297
if output_stream is None:
298
return self._makeDefaultBin()
300
return self._makeStreamBin(output_stream)
302
def _makeDefaultBin(self):
304
Return a bin that decodes all the available streams.
306
This is generally used to get an overview of the source media before
307
splitting it in separate streams.
309
bin = gst.Bin("%s" % self.name)
310
src = gst.element_make_from_uri(gst.URI_SRC, self.uri)
312
dbin = gst.element_factory_make("decodebin2")
314
dbin = gst.element_factory_make("decodebin")
318
dbin.connect("new-decoded-pad", self._binNewDecodedPadCb, bin)
319
dbin.connect("removed-decoded-pad", self._binRemovedDecodedPadCb, bin)
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)
329
def _binRemovedDecodedPadCb(self, unused_dbin, pad, bin):
330
ghost_pad = bin.get_pad(pad.get_name())
331
bin.remove_pad(ghost_pad)
333
def _releaseBin(self, bin):
334
if hasattr(bin, "decodebin"):
336
# bin is a bin returned from makeDefaultBin
337
bin.decodebin.disconnect_by_func(self._binNewDecodedPadCb)
338
bin.decodebin.disconnect_by_func(self._binRemovedDecodedPadCb)
340
# bin is a stream bin
341
bin.decodebin.disconnect_by_func(self._singlePadAddedCb)
342
bin.decodebin.disconnect_by_func(self._singlePadRemovedCb)
345
if hasattr(bin, "child"):
346
bin.child.set_state(gst.STATE_NULL)
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)
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)
368
if hasattr(bin, "ghostpad"):
369
# singledecodebin found something on this pad
370
bin.ghostpad.set_active(False)
371
bin.remove_pad(bin.ghostpad)
374
def _makeStreamBinReal(self, output_stream):
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)
382
def _makeStreamBin(self, output_stream, child_bin=None):
383
self.debug("output_stream:%r", output_stream)
384
b = self._makeStreamBinReal(output_stream)
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)
391
if hasattr(b, "decodebin"):
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()
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()
413
pad.link(topbin.queue.get_pad("sink"))
414
topbin.ghostpad = gst.GhostPad("src", topbin.capsfilter.get_pad("src"))
416
topbin.ghostpad = gst.GhostPad("src", pad)
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)
423
def _singlePadRemovedCb(self, dbin, pad, topbin):
424
self.debug("dbin:%r, pad:%r, topbin:%r", dbin, pad, topbin)
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)
431
topbin.remove_pad(topbin.ghostpad)
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"))
439
def addInputStream(self, stream):
440
raise AssertionError("source factories can't have input streams")
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"]
452
for bin in self.bins:
453
if hasattr(bin, "capsfilter"):
454
bin.capsfilter.props.caps = caps_copy
456
b.capsfilter.props.caps = caps_copy
457
self._filtercaps = caps_copy
459
def _addCommonVideoElements(self, video_bin, output_stream, child_bin=None):
461
video_bin.child = child_bin
462
video_bin.add(child_bin)
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
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
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")
483
video_bin.csp = gst.element_factory_make("identity")
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")
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)
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()
505
gst.element_link_many(video_bin.scale,
506
video_bin.alpha, video_bin.capsfilter)
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()
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)
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()
528
gst.element_link_many(audio_bin.aconv, audio_bin.ares, audio_bin.arate, audio_bin.volume)
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()
535
class SinkFactory(ObjectFactory):
537
Base class for factories that consume input and have no output.
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}
546
'bin-created': ['bin'],
547
'bin-released': ['bin']
550
def __init__(self, name=''):
551
ObjectFactory.__init__(self, name)
553
self.current_bins = 0
555
def makeBin(self, input_stream=None):
557
Create a bin that consumes the stream described by C{input_stream}.
559
If C{input_stream} is None, it's up to the implementations to return a
560
suitable "default" bin.
562
@param input_stream: A L{MultimediaStream}
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
578
if compatible_stream is None:
579
raise ObjectFactoryError('unknown stream')
581
if self.max_bins != -1 and self.current_bins == self.max_bins:
582
raise ObjectFactoryError('no bins available')
584
bin = self._makeBin(input_stream)
586
self.bins.append(bin)
587
self.current_bins += 1
588
self.emit('bin-created', bin)
592
def _makeBin(self, input_stream=None):
593
raise NotImplementedError()
595
def requestNewInputStream(self, bin, input_stream):
597
Request a new input stream on a bin.
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.
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")
613
def _requestNewInputStream(self, bin, input_stream):
614
raise NotImplementedError
616
def releaseBin(self, bin):
618
Release a bin created with L{makeBin}.
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.
623
bin.set_state(gst.STATE_NULL)
624
self._releaseBin(bin)
625
self.bins.remove(bin)
626
self.current_bins -= 1
628
self.emit('bin-released', bin)
630
def _releaseBin(self, bin):
631
# default implementation does nothing
634
def addOutputStream(self, stream):
635
raise AssertionError("sink factories can't have output streams")
637
class OperationFactory(ObjectFactory):
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}
647
'bin-created': ['bin'],
648
'bin-released': ['bin']
651
def __init__(self, name=''):
652
ObjectFactory.__init__(self, name)
654
self.current_bins = 0
656
def makeBin(self, input_stream=None, output_stream=None):
658
Create a bin that consumes the stream described by C{input_stream}.
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.
663
@param input_stream: A L{MultimediaStream}
664
@param output_stream: A L{MultimediaStream}
671
if input_stream is not None and \
672
input_stream not in self.input_streams:
673
raise ObjectFactoryError('unknown stream')
675
bin = self._makeBin(input_stream)
677
self.bins.append(bin)
678
self.current_bins += 1
679
self.emit('bin-created', bin)
683
def _makeBin(self, input_stream=None, output_stream=None):
684
raise NotImplementedError()
686
def requestNewInputStream(self, bin, input_stream):
688
Request a new input stream on a bin.
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.
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")
704
def _requestNewInputStream(self, bin, input_stream):
705
raise NotImplementedError
707
def releaseBin(self, bin):
709
Release a bin created with L{makeBin}.
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.
714
bin.set_state(gst.STATE_NULL)
715
self._releaseBin(bin)
716
self.bins.remove(bin)
717
self.current_bins -= 1
719
self.emit('bin-released', bin)
721
def _releaseBin(self, bin):
722
# default implementation does nothing
726
class LiveSourceFactory(SourceFactory):
728
Base class for factories that produce live streams.
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
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
740
self.default_duration = default_duration
742
class RandomAccessSourceFactory(SourceFactory):
744
Base class for source factories that support random access.
746
@ivar offset: Offset in nanoseconds from the beginning of the stream.
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}
757
def __init__(self, uri, name='',
758
offset=0, offset_length=gst.CLOCK_TIME_NONE):
760
self.offset_length = offset_length
762
SourceFactory.__init__(self, uri, name)
764
def _getAbsOffset(self):
765
if self.parent is None:
768
parent_offset = self.parent.offset
769
parent_length = self.parent.offset_length
771
offset = min(self.parent.offset + self.offset,
772
self.parent.offset + self.parent.offset_length)
776
abs_offset = property(_getAbsOffset)
778
def _getAbsOffsetLength(self):
779
if self.parent is None:
780
offset_length = self.offset_length
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
789
abs_offset_length = property(_getAbsOffsetLength)