1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2009, Edward Hervey <bilboed@bilboed.com>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# Lesser General Public License for more details.
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this program; if not, write to the
19
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20
# Boston, MA 02110-1301, USA.
26
"""@var states: Something
27
@type states: C{ActionState}"""
28
states = (STATE_NOT_ACTIVE,
29
STATE_ACTIVE) = range(2)
31
from pitivi.signalinterface import Signallable
32
from pitivi.factories.base import SourceFactory, SinkFactory
33
from pitivi.encode import RenderSinkFactory
34
from pitivi.log.loggable import Loggable
36
# TODO : Create a convenience class for Links
39
# FIXME : define/document a proper hierarchy
40
class ActionError(Exception):
44
class Action(Signallable, Loggable):
48
Controls the elements of a L{Pipeline}, including their creation,
49
activation, and linking.
51
Subclasses can also offer higher-level actions that automatically create
52
the Producers(s)/Consumer(s), thereby simplifying the work required to do
53
a certain multimedia 'Action' (Ex: Automatically create the appropriate
54
Consumer for rendering a producer stream to a file).
56
@ivar state: Whether the action is active or not
57
@type state: C{ActionState}
58
@ivar producers: The producers controlled by this L{Action}.
59
@type producers: List of L{SourceFactory}
60
@ivar consumers: The consumers controlled by this L{Action}.
61
@type consumers: List of L{SinkFactory}
62
@ivar pipeline: The L{Pipeline} controlled by this L{Action}.
63
@type pipeline: L{Pipeline}
64
@cvar compatible_producers: The list of compatible factories that
65
this L{Action} can handle as producers.
66
@type compatible_producers: List of L{ObjectFactory} classes
67
@cvar compatible_consumers: The list of compatible factories that
68
this L{Action} can handle as consumers.
69
@type compatible_consumers: List of L{ObjectFactory} classes
70
@cvar queue_size: Default queueing size (in seconds) to use for links.
73
compatible_producers = [SourceFactory]
74
compatible_consumers = [SinkFactory]
79
"state-changed": ["state"]
83
Loggable.__init__(self)
84
self.state = STATE_NOT_ACTIVE
88
self._links = [] # list of (producer, consumer, prodstream, consstream)
89
self._pending_links = [] # list of links that still need to be connected
90
self._pending_links_elements = []
91
self._dyn_links = [] # list of links added at RunTime, will be removed when deactivated
92
self._dynconsumers = [] # consumers that we added at RunTime
100
For each of the consumers/producers it will create the relevant
101
GStreamer objects for the Pipeline (if they don't already exist).
103
@precondition: Must be set to a L{Pipeline}
104
@precondition: The Pipeline must be in the NULL/READY state.
105
@precondition: All consumers/producers must be set on the L{Pipeline}.
107
@return: Whether the L{Action} was activated (True) or not.
109
@raise ActionError: If the L{Action} isn't set to a L{Pipeline}, or one
110
of the consumers/producers isn't set on the Pipeline.
111
@raise ActionError: If some producers or consumers remain unused.
112
@raise PipelineError: If the L{Pipeline} is not in the NULL or READY
115
self.debug("Activating...")
116
if self.pipeline is None:
117
raise ActionError("Action isn't set to a Pipeline")
118
if self.state == STATE_ACTIVE:
119
self.debug("Action already activated, returning")
121
self._ensurePipelineObjects()
122
self.state = STATE_ACTIVE
123
self.emit('state-changed', self.state)
124
self.debug("... done activating")
126
def deactivate(self):
128
De-activate the Action.
130
@precondition: The associated L{Pipeline} must be in the NULL or READY
135
@return: Whether the L{Action} was de-activated (True) or not.
137
@raise PipelineError: If the L{Pipeline} is not in the NULL or READY
140
self.debug("De-Activating...")
141
if self.state == STATE_NOT_ACTIVE:
144
if self.pipeline == None:
145
self.warning("Attempting to deactivate Action without a Pipeline")
146
# yes, gracefully return
148
self._releasePipelineObjects()
149
self.state = STATE_NOT_ACTIVE
150
self.emit('state-changed', self.state)
151
self.debug("... done de-activating")
155
Whether the Action is active or not
157
@see: L{activate}, L{deactivate}
159
@return: True if the Action is active.
162
return self.state == STATE_ACTIVE
166
def setPipeline(self, pipeline):
168
Set the L{Action} on the given L{Pipeline}.
170
@param pipeline: The L{Pipeline} to set the L{Action} onto.
171
@type pipeline: L{Pipeline}
172
@warning: This method should only be used by L{Pipeline}s when the given
173
L{Action} is set on them.
174
@precondition: The L{Action} must not be set to any other L{Pipeline}
175
when this method is called.
176
@raise ActionError: If the L{Action} is active or the pipeline is set to
177
a different L{Pipeline}.
179
if self.pipeline == pipeline:
180
self.debug("New pipeline is the same as the currently set one")
182
if self.pipeline != None:
183
raise ActionError("Action already set to a Pipeline")
184
if self.state != STATE_NOT_ACTIVE:
185
raise ActionError("Action is active, can't change Pipeline")
186
self.pipeline = pipeline
188
def unsetPipeline(self):
190
Remove the L{Action} from the currently set L{Pipeline}.
194
@warning: This method should only be used by L{Pipeline}s when the given
195
L{Action} is removed from them.
196
@precondition: The L{Action} must be deactivated before it can be removed from a
198
@raise ActionError: If the L{Action} is active.
200
if self.state != STATE_NOT_ACTIVE:
201
raise ActionError("Action is active, can't unset Pipeline")
204
#{ ObjectFactory methods
206
def addProducers(self, *producers):
208
Add the given L{ObjectFactory}s as producers of the L{Action}.
210
@type producers: List of L{ObjectFactory}
211
@raise ActionError: If the L{Action} is active.
213
self.debug("producers:%r", (producers, ))
214
if self.state != STATE_NOT_ACTIVE:
215
raise ActionError("Action is active, can't add Producers")
216
# make sure producers are of the valid type
217
if self.compatible_producers != []:
220
for t in self.compatible_producers:
225
raise ActionError("Some producers are not of the compatible type")
227
if not p in self.producers:
228
self.debug("really adding %r to our producers", p)
229
self.producers.append(p)
231
def removeProducers(self, *producers):
233
Remove the given L{ObjectFactory}s as producers of the L{Action}.
235
@see: L{addProducers}
237
@type producers: List of L{ObjectFactory}
238
@raise ActionError: If the L{Action} is active.
240
if self.state != STATE_NOT_ACTIVE:
241
raise ActionError("Action is active, can't remove Producers")
242
# FIXME : figure out what to do in regards with links
245
self.producers.remove(p)
249
def addConsumers(self, *consumers):
251
Set the given L{ObjectFactory}s as consumers of the L{Action}.
253
@type consumers: List of L{ObjectFactory}
254
@raise ActionError: If the L{Action} is active.
256
self.debug("consumers: %r", consumers)
257
if self.state != STATE_NOT_ACTIVE:
258
raise ActionError("Action is active, can't add Producers")
259
# make sure consumers are of the valid type
260
if self.compatible_consumers != []:
263
for t in self.compatible_consumers:
268
raise ActionError("Some consumers are not of the compatible type")
270
if not p in self.consumers:
271
self.debug("really adding %r to our consumers", p)
272
self.consumers.append(p)
274
def removeConsumers(self, *consumers):
276
Remove the given L{ObjectFactory}s as consumers of the L{Action}.
278
@see: L{addConsumers}
279
@type consumers: List of L{ObjectFactory}
280
@raise ActionError: If the L{Action} is active.
282
if self.state != STATE_NOT_ACTIVE:
283
raise ActionError("Action is active, can't remove Consumers")
284
# FIXME : figure out what to do in regards with links
287
self.consumers.remove(p)
293
def setLink(self, producer, consumer, producerstream=None,
294
consumerstream=None):
296
Set a relationship (link) between producer and consumer.
298
If the Producer and/or Consumer isn't already set to this L{Action},
299
this method will attempt to add them.
301
@param producer: The producer we wish to link.
302
@type producer: L{ObjectFactory}
303
@param consumer: The consumer we wish to link.
304
@type consumer: L{ObjectFactory}
305
@param producerstream: The L{MultimediaStream} to use from the producer. If not
306
specified, the L{Action} will figure out a compatible L{MultimediaStream} between
307
the producer and consumer.
308
@type producerstream: L{MultimediaStream}
309
@param consumerstream: The L{MultimediaStream} to use from the consumer. If not
310
specified, the L{Action} will figure out a compatible L{MultimediaStream} between
311
the consumer and consumer.
312
@type consumerstream: L{MultimediaStream}
313
@raise ActionError: If the L{Action} is active.
314
@raise ActionError: If the producerstream isn't available on the
316
@raise ActionError: If the consumerstream isn't available on the
318
@raise ActionError: If the producer and consumer are incompatible.
319
@raise ActionError: If the link is already set.
321
# If streams are specified, make sure they exist in their respective factories
322
# Make sure producer and consumer are compatible
323
# If needed, add producer and consumer to ourselves
326
raise ActionError("Can't add link when active")
328
if producerstream is not None \
329
and not producerstream in producer.getOutputStreams():
330
raise ActionError("Stream specified isn't available in producer")
331
if consumerstream is not None \
332
and not consumerstream in consumer.getInputStreams():
333
raise ActionError("Stream specified isn't available in consumer")
335
# check if the streams are compatible
336
if producerstream is not None and consumerstream is not None:
337
if not producerstream.isCompatible(consumerstream):
338
raise ActionError("Specified streams are not compatible")
340
# finally check if that link isn't already set
341
linktoadd = (producer, consumer, producerstream, consumerstream)
342
if linktoadd in self._links:
343
raise ActionError("Link already present")
345
# now, lets' see if we are already controlling the consumer and producer
346
if producer not in self.producers:
347
self.addProducers(producer)
348
if consumer not in self.consumers:
349
self.addConsumers(consumer)
350
self._links.append(linktoadd)
352
def removeLink(self, producer, consumer, producerstream=None,
353
consumerstream=None):
355
Remove a relationship (link) between producer and consumer.
359
@param producer: The producer we wish to unlink.
360
@type producer: L{ObjectFactory}
361
@param consumer: The consumer we wish to unlink.
362
@type consumer: L{ObjectFactory}
363
@param producerstream: The L{MultimediaStream} to use from the producer. If not
364
specified, the L{Action} will figure out a compatible L{MultimediaStream} between
365
the producer and consumer.
366
@type producerstream: L{MultimediaStream}.
367
@param consumerstream: The L{MultimediaStream} to use from the consumer. If not
368
specified, the L{Action} will figure out a compatible L{MultimediaStream} between
369
the consumer and consumer.
370
@type consumerstream: L{MultimediaStream}.
371
@raise ActionError: If the L{Action} is active.
372
@raise ActionError: If the link didn't exist
375
raise ActionError("Action active")
377
alink = (producer, consumer, producerstream, consumerstream)
378
if not alink in self._links:
379
raise ActionError("Link doesn't exist !")
381
self._links.remove(alink)
383
def getLinks(self, autolink=True):
385
Returns the Links setup for this Action.
387
Sub-classes can override this to fine-tune the linking:
388
- Specify streams of producers
389
- Specify streams of consumers
392
Sub-classes should chain-up to the parent class method BEFORE doing
393
anything with the link list.
395
@param autolink: If True and there were no links specified previously by
396
setLink(), then a list of Links will be automatically created based on
397
the available producers, consumers and their respective streams.
398
@type autolink: C{bool}
399
@return: A list of Links
400
@rtype: List of (C{Producer}, C{Consumer}, C{ProducerStream}, C{ConsumerStream})
402
links = self._links[:]
403
if links == [] and autolink:
404
links = self._links = self.autoLink()
405
self.debug("Returning %d links", len(links))
410
Based on the available consumers and producers, returns a list of
411
compatibles C{Link}s.
413
Sub-classes can override this method (without chaining up) to do their
414
own auto-linking algorithm, although it is more recommended to
415
implement getLinks().
418
@raise ActionError: If there is any ambiguity as to which producerstream
419
should be linked to which consumerstream.
420
@return: List of compatible Links.
422
self.debug("Creating automatic links")
424
# iterate producers and their output streams
425
for producer in self.producers:
426
self.debug("producer %r", producer)
427
for producer_stream in producer.getOutputStreams():
428
self.debug(" stream %r", producer_stream)
429
# for each, figure out a compatible (consumer, stream)
430
for consumer in self.consumers:
431
self.debug(" consumer %r", consumer)
432
compat = consumer.getInputStreams(type(producer_stream))
433
# in case of ambiguity, raise an exception
435
self.warning("%r", compat)
436
raise ActionError("Too many compatible streams in consumer")
438
self.debug(" Got a compatible stream !")
439
links.append((producer, consumer,
440
producer_stream, compat[0]))
443
#{ Dynamic Stream handling
445
def handleNewStream(self, producer, stream):
447
Handle the given stream of the given producer.
449
Called by the Pipeline when one of the producers controlled by this
450
action produces a new Stream
452
Subclasses can override this method and chain-up to the parent class
453
method *before* doing their own processing.
455
@param producer: The producer of the stream
456
@type producer: L{SourceFactory}
457
@param stream: The new stream
458
@type stream: L{MultimediaStream}
459
@return: C{True} if the Stream is/was handled by the Action, else C{False}
462
self.debug("producer:%r, stream:%s", producer, stream.caps)
466
# 1. Check if it's one of our pendings pads
467
self.debug("First trying pending links (%d)", len(self._pending_links))
468
pl = self._pending_links[:]
469
for prod, cons, prodstream, consstream in pl:
470
self.debug(" producer:%r, stream:%s", prod, prodstream)
471
self.debug(" consumer:%r, stream:%s", cons, consstream)
472
if prod == producer and (prodstream == None or \
473
prodstream.isCompatible(stream)):
474
if self._activateLink(prod, cons, stream, consstream):
475
self._pending_links_elements.append((prod, stream))
477
self.debug("Successfully linked pending stream, removing "
479
self._pending_links.remove((prod, cons,
480
prodstream, consstream))
481
self._pd = getattr(self, '_pd', [])
482
self._pd.append((producer, stream))
485
self.debug("Checking to see if we haven't already handled it")
486
# 2. If it's not one of the pending links, It could also be one of the
487
# links we've *already* handled
488
for prod, cons, ps, cs in self.getLinks():
489
if prod == producer and ps.isCompatible(stream):
490
self.debug("Already handled that link, returning True")
493
# 3. Dynamic linking, ask if someone can handle this if nothing else did
495
self.debug("Asking subclasses if they want to add any links for given link")
496
for prod, cons, prodstream, consstream in self.getDynamicLinks(producer, stream):
497
if not cons in self.consumers and not cons in self._dynconsumers:
498
# we need to add that new consumer
499
self._dynconsumers.append(cons)
500
self._dyn_links.append((prod, cons, prodstream, consstream))
501
waspending |= self._activateLink(prod, cons,
502
prodstream, consstream, init=False)
504
self.debug("returning %r", waspending)
507
def streamRemoved(self, producer, stream):
509
A stream has been removed from one of the producers controlled by this
512
Called by the Pipeline.
514
self.debug("producer:%r, stream:%r", producer, stream)
515
for dyn_producer, dyn_consumer, \
516
dyn_producer_stream, dyn_consumer_stream in list(self._dyn_links):
517
if producer != dyn_producer or stream != dyn_producer_stream:
520
# release tee/queue usage for that stream
521
self.pipeline.releaseQueueForFactoryStream(dyn_consumer,
523
self.pipeline.releaseBinForFactoryStream(dyn_consumer,
525
self.pipeline.releaseTeeForFactoryStream(dyn_producer,
527
self.pipeline.releaseBinForFactoryStream(dyn_producer,
530
self._dyn_links.remove((dyn_producer, dyn_consumer,
531
dyn_producer_stream, dyn_consumer_stream))
534
def getDynamicLinks(self, producer, stream):
536
Return a list of links to handle the given producer/stream.
538
Subclasses can override this to give links for streams that appear
539
dynamically and should chain up to the parent-class implementation
540
BEFORE their own implementation (i.e. adding to the list they get).
541
If new producers are given, they will be dynamically added to the
542
C{Action} and the controlled C{Pipeline}.
544
@param producer: A producer.
545
@type producer: C{SourceFactory}
546
@param stream: The stream to handle
547
@type stream: C{MultimediaStream}
548
@return: a list of links
553
def _ensurePipelineObjects(self):
555
Makes sure all objects needed in the pipeline are properly created.
557
@precondition: All checks relative to pipeline/action/factory validity
559
@raise ActionError: If some producers or consumers remain unused.
561
# make sure all producers we control have a bin (for dynamic streams)
562
for producer in self.producers:
563
self.pipeline.getBinForFactoryStream(producer, automake=True)
565
# also inform the pipeline about the consumers we're gonna use
566
for producer in self.consumers:
567
self.pipeline.getBinForFactoryStream(producer, automake=True)
570
links = self.getLinks()
571
# ensure all links are used
573
for producer, consumer, producer_stream, consumer_stream in links:
574
if producer in self.producers and consumer in self.consumers:
575
cplinks.remove((producer, consumer,
576
producer_stream, consumer_stream))
579
raise ActionError("Some links are not used !")
581
# clear dynamic-stream variables
583
self._pending_links = []
584
self._dynconsumers = []
587
self._activateLink(*link)
589
def _activateLink(self, producer, consumer, prodstream, consstream, init=True):
590
# FIXME: we import PipelineError here to avoid a circular import
591
from pitivi.pipeline import PipelineError
593
# activate the given Link, returns True if it was (already) activated
594
# if init is True, then remember the pending link
595
self.debug("producer:%r, consumer:%r, prodstream:%r, consstream:%r", \
596
producer, consumer, prodstream, consstream)
598
self.info("Ensuring a bin exists for our producer")
599
self.pipeline.getBinForFactoryStream(producer, prodstream)
601
# Make sure we have tees for our (producer,stream)s
603
tee = self.pipeline.getTeeForFactoryStream(producer, prodstream,
606
except PipelineError, e:
608
self.debug("Could not create link %s" % e)
611
self.debug("Stream will be created dynamically")
612
self._pending_links.append((producer, consumer, prodstream, consstream))
615
self.info("Getting a bin for our consumer")
616
# Make sure we have a bin for our consumer
617
bin = self.pipeline.getBinForFactoryStream(consumer,
618
consstream, automake=True)
620
self.info("Got our bin for our consumer: %r", bin)
622
# we set the sink to paused, since we are adding this link during
624
bin.sync_state_with_parent()
626
self.info("Getting the Queue for that consumer/stream")
627
# Make sure we have queues for our (consumer, stream)s
628
queue = self.pipeline.getQueueForFactoryStream(consumer, consstream,
629
automake=True, queuesize=self.queue_size)
631
# FIXME: where should this be unlinked?
633
sinkpad = q.get_pad('sink')
634
if sinkpad.is_linked():
635
sinkpad.get_peer().unlink(sinkpad)
638
self.info("linking the tee to the queue")
639
# Link tees to queues
645
def _releasePipelineObjects(self):
646
from pitivi.pipeline import PipelineError
647
self.debug("Releasing pipeline objects")
648
for producer, consumer, prodstream, consstream in self.getLinks():
649
# release tee/queue usage for that stream
650
self.pipeline.releaseQueueForFactoryStream(consumer, consstream)
651
self.pipeline.releaseBinForFactoryStream(consumer, consstream)
653
self.pipeline.releaseTeeForFactoryStream(producer, prodstream)
654
except PipelineError:
655
# FIXME: _really_ create an exception hierarchy
657
# this happens if the producer is part of a pending link that
658
# has not been activated yet
659
self.debug("producer has no tee.. pending link?")
660
self.pipeline.releaseBinForFactoryStream(producer, prodstream)
662
for producer, prodstream in self._pending_links_elements:
663
self.pipeline.releaseTeeForFactoryStream(producer, prodstream)
664
self.pipeline.releaseBinForFactoryStream(producer, prodstream)
665
self._pending_links_elements = []
667
# release dynamic links
668
for producer, consumer, prodstream, consstream in self._dyn_links:
669
# release tee/queue usage for that stream
670
self.pipeline.releaseQueueForFactoryStream(consumer, consstream)
671
self.pipeline.releaseBinForFactoryStream(consumer, consstream)
672
self.pipeline.releaseTeeForFactoryStream(producer, prodstream)
673
self.pipeline.releaseBinForFactoryStream(producer, prodstream)
676
# try to clean producers that were never linked, if any
677
for producer in self.producers:
679
self.pipeline.releaseBinForFactoryStream(producer)
680
except PipelineError:
681
# FIXME: use a strictier exception hierarchy
684
for consumer in self.consumers:
686
self.pipeline.releaseBinForFactoryStream(consumer)
687
except PipelineError:
688
# FIXME: use a strictier exception hierarchy
692
class ViewAction(Action):
694
An action used to view sources.
696
Will automatically connect stream from the controlled producer to the given
699
# FIXME : how to get default handlers ?
700
# ==> Use plumber in the meantime
701
# FIXME : How to handle multiple video sinks (and XID) ?
703
def __init__(self, *args, **kwargs):
704
Action.__init__(self, *args, **kwargs)
705
self.debug("Creating new ViewAction")
706
self.videosink = None
707
self.audiosink = None
710
def getDynamicLinks(self, producer, stream):
711
self.debug("producer:%r, stream:%r, sync:%r",
712
producer, stream, self.sync)
713
from pitivi.plumber import DefaultAudioSink, DefaultVideoSink
714
from pitivi.stream import AudioStream, VideoStream
715
res = Action.getDynamicLinks(self, producer, stream)
716
if isinstance(stream, VideoStream):
717
consumer = DefaultVideoSink()
718
self.videosink = consumer
719
self.videosink.setSync(self.sync)
721
res.append((producer, consumer, stream, None))
722
# only link audio streams if we're synchronized
723
elif isinstance(stream, AudioStream) and self.sync:
724
consumer = DefaultAudioSink()
725
self.audiosink = consumer
726
self.audiosink.setSync(self.sync)
727
res.append((producer, consumer, stream, None))
730
def setSync(self, sync=True):
732
Whether the sinks should sync against the running clock and
733
emit QoS events upstream.
737
self.videosink.setSync(sync)
739
self.audiosink.setSync(sync)
742
class RenderAction(Action):
744
An Action to render sources.
746
Handles a L{RenderSinkFactory}.
749
compatible_consumers = [RenderSinkFactory]
750
# Use a queue of 5s to allow for big interleave