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

« back to all changes in this revision

Viewing changes to pitivi/action.py

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       pitivi/action.py
4
 
#
5
 
# Copyright (c) 2009, Edward Hervey <bilboed@bilboed.com>
6
 
#
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.
11
 
#
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.
16
 
#
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.
21
 
 
22
 
"""
23
 
Pipeline actions
24
 
"""
25
 
 
26
 
"""@var states: Something
27
 
@type states: C{ActionState}"""
28
 
states = (STATE_NOT_ACTIVE,
29
 
          STATE_ACTIVE) = range(2)
30
 
 
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
35
 
 
36
 
# TODO : Create a convenience class for Links
37
 
 
38
 
 
39
 
# FIXME : define/document a proper hierarchy
40
 
class ActionError(Exception):
41
 
    pass
42
 
 
43
 
 
44
 
class Action(Signallable, Loggable):
45
 
    """
46
 
    Pipeline action.
47
 
 
48
 
    Controls the elements of a L{Pipeline}, including their creation,
49
 
    activation, and linking.
50
 
 
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).
55
 
 
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.
71
 
    """
72
 
 
73
 
    compatible_producers = [SourceFactory]
74
 
    compatible_consumers = [SinkFactory]
75
 
 
76
 
    queue_size = 1
77
 
 
78
 
    __signals__ = {
79
 
        "state-changed": ["state"]
80
 
        }
81
 
 
82
 
    def __init__(self):
83
 
        Loggable.__init__(self)
84
 
        self.state = STATE_NOT_ACTIVE
85
 
        self.producers = []
86
 
        self.consumers = []
87
 
        self.pipeline = None
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
93
 
 
94
 
    #{ Activation methods
95
 
 
96
 
    def activate(self):
97
 
        """
98
 
        Activate the action.
99
 
 
100
 
        For each of the consumers/producers it will create the relevant
101
 
        GStreamer objects for the Pipeline (if they don't already exist).
102
 
 
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}.
106
 
 
107
 
        @return: Whether the L{Action} was activated (True) or not.
108
 
        @rtype: L{bool}
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
113
 
        state.
114
 
        """
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")
120
 
            return
121
 
        self._ensurePipelineObjects()
122
 
        self.state = STATE_ACTIVE
123
 
        self.emit('state-changed', self.state)
124
 
        self.debug("... done activating")
125
 
 
126
 
    def deactivate(self):
127
 
        """
128
 
        De-activate the Action.
129
 
 
130
 
        @precondition: The associated L{Pipeline} must be in the NULL or READY
131
 
        state.
132
 
 
133
 
        @see: L{activate}
134
 
 
135
 
        @return: Whether the L{Action} was de-activated (True) or not.
136
 
        @rtype: L{bool}
137
 
        @raise PipelineError: If the L{Pipeline} is not in the NULL or READY
138
 
        state.
139
 
        """
140
 
        self.debug("De-Activating...")
141
 
        if self.state == STATE_NOT_ACTIVE:
142
 
            raise ActionError()
143
 
 
144
 
        if self.pipeline == None:
145
 
            self.warning("Attempting to deactivate Action without a Pipeline")
146
 
            # yes, gracefully return
147
 
            return
148
 
        self._releasePipelineObjects()
149
 
        self.state = STATE_NOT_ACTIVE
150
 
        self.emit('state-changed', self.state)
151
 
        self.debug("... done de-activating")
152
 
 
153
 
    def isActive(self):
154
 
        """
155
 
        Whether the Action is active or not
156
 
 
157
 
        @see: L{activate}, L{deactivate}
158
 
 
159
 
        @return: True if the Action is active.
160
 
        @rtype: L{bool}
161
 
        """
162
 
        return self.state == STATE_ACTIVE
163
 
 
164
 
    #{ Pipeline methods
165
 
 
166
 
    def setPipeline(self, pipeline):
167
 
        """
168
 
        Set the L{Action} on the given L{Pipeline}.
169
 
 
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}.
178
 
        """
179
 
        if self.pipeline == pipeline:
180
 
            self.debug("New pipeline is the same as the currently set one")
181
 
            return
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
187
 
 
188
 
    def unsetPipeline(self):
189
 
        """
190
 
        Remove the L{Action} from the currently set L{Pipeline}.
191
 
 
192
 
        @see: L{setPipeline}
193
 
 
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
197
 
        L{Pipeline}.
198
 
        @raise ActionError: If the L{Action} is active.
199
 
        """
200
 
        if self.state != STATE_NOT_ACTIVE:
201
 
            raise ActionError("Action is active, can't unset Pipeline")
202
 
        self.pipeline = None
203
 
 
204
 
    #{ ObjectFactory methods
205
 
 
206
 
    def addProducers(self, *producers):
207
 
        """
208
 
        Add the given L{ObjectFactory}s as producers of the L{Action}.
209
 
 
210
 
        @type producers: List of L{ObjectFactory}
211
 
        @raise ActionError: If the L{Action} is active.
212
 
        """
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 != []:
218
 
            for p in producers:
219
 
                val = False
220
 
                for t in self.compatible_producers:
221
 
                    if isinstance(p, t):
222
 
                        val = True
223
 
                        continue
224
 
                if not val:
225
 
                    raise ActionError("Some producers are not of the compatible type")
226
 
        for p in producers:
227
 
            if not p in self.producers:
228
 
                self.debug("really adding %r to our producers", p)
229
 
                self.producers.append(p)
230
 
 
231
 
    def removeProducers(self, *producers):
232
 
        """
233
 
        Remove the given L{ObjectFactory}s as producers of the L{Action}.
234
 
 
235
 
        @see: L{addProducers}
236
 
 
237
 
        @type producers: List of L{ObjectFactory}
238
 
        @raise ActionError: If the L{Action} is active.
239
 
        """
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
243
 
        for p in producers:
244
 
            try:
245
 
                self.producers.remove(p)
246
 
            except ValueError:
247
 
                raise ActionError()
248
 
 
249
 
    def addConsumers(self, *consumers):
250
 
        """
251
 
        Set the given L{ObjectFactory}s as consumers of the L{Action}.
252
 
 
253
 
        @type consumers: List of L{ObjectFactory}
254
 
        @raise ActionError: If the L{Action} is active.
255
 
        """
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 != []:
261
 
            for p in consumers:
262
 
                val = False
263
 
                for t in self.compatible_consumers:
264
 
                    if isinstance(p, t):
265
 
                        val = True
266
 
                        continue
267
 
                if not val:
268
 
                    raise ActionError("Some consumers are not of the compatible type")
269
 
        for p in consumers:
270
 
            if not p in self.consumers:
271
 
                self.debug("really adding %r to our consumers", p)
272
 
                self.consumers.append(p)
273
 
 
274
 
    def removeConsumers(self, *consumers):
275
 
        """
276
 
        Remove the given L{ObjectFactory}s as consumers of the L{Action}.
277
 
 
278
 
        @see: L{addConsumers}
279
 
        @type consumers: List of L{ObjectFactory}
280
 
        @raise ActionError: If the L{Action} is active.
281
 
        """
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
285
 
        for p in consumers:
286
 
            try:
287
 
                self.consumers.remove(p)
288
 
            except ValueError:
289
 
                raise ActionError()
290
 
 
291
 
    #{ Link methods
292
 
 
293
 
    def setLink(self, producer, consumer, producerstream=None,
294
 
                consumerstream=None):
295
 
        """
296
 
        Set a relationship (link) between producer and consumer.
297
 
 
298
 
        If the Producer and/or Consumer isn't already set to this L{Action},
299
 
        this method will attempt to add them.
300
 
 
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
315
 
        producer.
316
 
        @raise ActionError: If the consumerstream isn't available on the
317
 
        consumer.
318
 
        @raise ActionError: If the producer and consumer are incompatible.
319
 
        @raise ActionError: If the link is already set.
320
 
        """
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
324
 
        # store the link
325
 
        if self.isActive():
326
 
            raise ActionError("Can't add link when active")
327
 
 
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")
334
 
 
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")
339
 
 
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")
344
 
 
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)
351
 
 
352
 
    def removeLink(self, producer, consumer, producerstream=None,
353
 
                   consumerstream=None):
354
 
        """
355
 
        Remove a relationship (link) between producer and consumer.
356
 
 
357
 
        @see: L{setLink}
358
 
 
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
373
 
        """
374
 
        if self.isActive():
375
 
            raise ActionError("Action active")
376
 
 
377
 
        alink = (producer, consumer, producerstream, consumerstream)
378
 
        if not alink in self._links:
379
 
            raise ActionError("Link doesn't exist !")
380
 
 
381
 
        self._links.remove(alink)
382
 
 
383
 
    def getLinks(self, autolink=True):
384
 
        """
385
 
        Returns the Links setup for this Action.
386
 
 
387
 
        Sub-classes can override this to fine-tune the linking:
388
 
         - Specify streams of producers
389
 
         - Specify streams of consumers
390
 
         - Add Extra links
391
 
 
392
 
        Sub-classes should chain-up to the parent class method BEFORE doing
393
 
        anything with the link list.
394
 
 
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})
401
 
        """
402
 
        links = self._links[:]
403
 
        if links == [] and autolink:
404
 
            links = self._links = self.autoLink()
405
 
        self.debug("Returning %d links", len(links))
406
 
        return links
407
 
 
408
 
    def autoLink(self):
409
 
        """
410
 
        Based on the available consumers and producers, returns a list of
411
 
        compatibles C{Link}s.
412
 
 
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().
416
 
 
417
 
        @see: L{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.
421
 
        """
422
 
        self.debug("Creating automatic links")
423
 
        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
434
 
                    if len(compat) > 1:
435
 
                        self.warning("%r", compat)
436
 
                        raise ActionError("Too many compatible streams in consumer")
437
 
                    if len(compat) == 1:
438
 
                        self.debug("    Got a compatible stream !")
439
 
                        links.append((producer, consumer,
440
 
                                producer_stream, compat[0]))
441
 
        return links
442
 
 
443
 
    #{ Dynamic Stream handling
444
 
 
445
 
    def handleNewStream(self, producer, stream):
446
 
        """
447
 
        Handle the given stream of the given producer.
448
 
 
449
 
        Called by the Pipeline when one of the producers controlled by this
450
 
        action produces a new Stream
451
 
 
452
 
        Subclasses can override this method and chain-up to the parent class
453
 
        method *before* doing their own processing.
454
 
 
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}
460
 
        @rtype: C{bool}
461
 
        """
462
 
        self.debug("producer:%r, stream:%s", producer, stream.caps)
463
 
 
464
 
        waspending = False
465
 
 
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))
476
 
                    waspending = True
477
 
                    self.debug("Successfully linked pending stream, removing "
478
 
                            "it from temp list")
479
 
                    self._pending_links.remove((prod, cons,
480
 
                            prodstream, consstream))
481
 
                    self._pd = getattr(self, '_pd', [])
482
 
                    self._pd.append((producer, stream))
483
 
 
484
 
        if not waspending:
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")
491
 
                    return True
492
 
 
493
 
        # 3. Dynamic linking, ask if someone can handle this if nothing else did
494
 
        # up to now.
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)
503
 
 
504
 
        self.debug("returning %r", waspending)
505
 
        return waspending
506
 
 
507
 
    def streamRemoved(self, producer, stream):
508
 
        """
509
 
        A stream has been removed from one of the producers controlled by this
510
 
        action.
511
 
 
512
 
        Called by the Pipeline.
513
 
        """
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:
518
 
                continue
519
 
 
520
 
            # release tee/queue usage for that stream
521
 
            self.pipeline.releaseQueueForFactoryStream(dyn_consumer,
522
 
                    dyn_consumer_stream)
523
 
            self.pipeline.releaseBinForFactoryStream(dyn_consumer,
524
 
                    dyn_consumer_stream)
525
 
            self.pipeline.releaseTeeForFactoryStream(dyn_producer,
526
 
                    dyn_producer_stream)
527
 
            self.pipeline.releaseBinForFactoryStream(dyn_producer,
528
 
                    dyn_producer_stream)
529
 
 
530
 
            self._dyn_links.remove((dyn_producer, dyn_consumer,
531
 
                    dyn_producer_stream, dyn_consumer_stream))
532
 
            break
533
 
 
534
 
    def getDynamicLinks(self, producer, stream):
535
 
        """
536
 
        Return a list of links to handle the given producer/stream.
537
 
 
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}.
543
 
 
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
549
 
        """
550
 
        return []
551
 
    #}
552
 
 
553
 
    def _ensurePipelineObjects(self):
554
 
        """
555
 
        Makes sure all objects needed in the pipeline are properly created.
556
 
 
557
 
        @precondition: All checks relative to pipeline/action/factory validity
558
 
        must be done.
559
 
        @raise ActionError: If some producers or consumers remain unused.
560
 
        """
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)
564
 
 
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)
568
 
 
569
 
        # Get the links
570
 
        links = self.getLinks()
571
 
        # ensure all links are used
572
 
        cplinks = links[:]
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))
577
 
 
578
 
        if cplinks != []:
579
 
            raise ActionError("Some links are not used !")
580
 
 
581
 
        # clear dynamic-stream variables
582
 
        self._dyn_links = []
583
 
        self._pending_links = []
584
 
        self._dynconsumers = []
585
 
 
586
 
        for link in links:
587
 
            self._activateLink(*link)
588
 
 
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
592
 
 
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)
597
 
 
598
 
        self.info("Ensuring a bin exists for our producer")
599
 
        self.pipeline.getBinForFactoryStream(producer, prodstream)
600
 
 
601
 
        # Make sure we have tees for our (producer,stream)s
602
 
        try:
603
 
            tee = self.pipeline.getTeeForFactoryStream(producer, prodstream,
604
 
                                                     automake=True)
605
 
 
606
 
        except PipelineError, e:
607
 
            if not init:
608
 
                self.debug("Could not create link %s" % e)
609
 
                return False
610
 
 
611
 
            self.debug("Stream will be created dynamically")
612
 
            self._pending_links.append((producer, consumer, prodstream, consstream))
613
 
            return True
614
 
 
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)
619
 
 
620
 
        self.info("Got our bin for our consumer: %r", bin)
621
 
        if not init:
622
 
            # we set the sink to paused, since we are adding this link during
623
 
            # auto-plugging
624
 
            bin.sync_state_with_parent()
625
 
 
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)
630
 
 
631
 
        # FIXME: where should this be unlinked?
632
 
        """
633
 
        sinkpad = q.get_pad('sink')
634
 
        if sinkpad.is_linked():
635
 
            sinkpad.get_peer().unlink(sinkpad)
636
 
        """
637
 
 
638
 
        self.info("linking the tee to the queue")
639
 
        # Link tees to queues
640
 
        tee.link(queue)
641
 
 
642
 
        self.info("done")
643
 
        return True
644
 
 
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)
652
 
            try:
653
 
                self.pipeline.releaseTeeForFactoryStream(producer, prodstream)
654
 
            except PipelineError:
655
 
                # FIXME: _really_ create an exception hierarchy
656
 
 
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)
661
 
 
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 = []
666
 
 
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)
674
 
        self._dyn_links = []
675
 
 
676
 
        # try to clean producers that were never linked, if any
677
 
        for producer in self.producers:
678
 
            try:
679
 
                self.pipeline.releaseBinForFactoryStream(producer)
680
 
            except PipelineError:
681
 
                # FIXME: use a strictier exception hierarchy
682
 
                pass
683
 
 
684
 
        for consumer in self.consumers:
685
 
            try:
686
 
                self.pipeline.releaseBinForFactoryStream(consumer)
687
 
            except PipelineError:
688
 
                # FIXME: use a strictier exception hierarchy
689
 
                pass
690
 
 
691
 
 
692
 
class ViewAction(Action):
693
 
    """
694
 
    An action used to view sources.
695
 
 
696
 
    Will automatically connect stream from the controlled producer to the given
697
 
    sinks.
698
 
    """
699
 
    # FIXME : how to get default handlers ?
700
 
    # ==> Use plumber in the meantime
701
 
    # FIXME : How to handle multiple video sinks (and XID) ?
702
 
 
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
708
 
        self.sync = True
709
 
 
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)
720
 
 
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))
728
 
        return res
729
 
 
730
 
    def setSync(self, sync=True):
731
 
        """
732
 
        Whether the sinks should sync against the running clock and
733
 
        emit QoS events upstream.
734
 
        """
735
 
        self.sync = sync
736
 
        if self.videosink:
737
 
            self.videosink.setSync(sync)
738
 
        if self.audiosink:
739
 
            self.audiosink.setSync(sync)
740
 
 
741
 
 
742
 
class RenderAction(Action):
743
 
    """
744
 
    An Action to render sources.
745
 
 
746
 
    Handles a L{RenderSinkFactory}.
747
 
    """
748
 
 
749
 
    compatible_consumers = [RenderSinkFactory]
750
 
    # Use a queue of 5s to allow for big interleave
751
 
    queue_size = 5