~ubuntu-branches/ubuntu/trusty/pitivi/trusty

« back to all changes in this revision

Viewing changes to pitivi/timeline.py

  • Committer: Bazaar Package Importer
  • Author(s): Loic Minier
  • Date: 2007-01-31 15:32:37 UTC
  • mto: (3.2.1 hardy) (1.2.1 upstream) (6.1.1 sid)
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: james.westby@ubuntu.com-20070131153237-de4p8lipjv8x5x3b
Tags: upstream-0.10.2
ImportĀ upstreamĀ versionĀ 0.10.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       pitivi/timeline.py
4
 
#
5
 
# Copyright (c) 2005, 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., 59 Temple Place - Suite 330,
20
 
# Boston, MA 02111-1307, USA.
21
 
 
22
 
"""
23
 
Timeline and timeline objects
24
 
"""
25
 
 
26
 
import gobject
27
 
import gst
28
 
 
29
 
MEDIA_TYPE_NONE = 0
30
 
MEDIA_TYPE_AUDIO = 1
31
 
MEDIA_TYPE_VIDEO = 2
32
 
 
33
 
## * Object Hierarchy
34
 
 
35
 
##   Object
36
 
##    |
37
 
##    +---- Source
38
 
##    |    |
39
 
##    |    +---- FileSource
40
 
##    |    |
41
 
##    |    +---- LiveSource
42
 
##    |    |
43
 
##    |    +---- Composition
44
 
##    |
45
 
##    +---- Effect
46
 
##         |
47
 
##         +---- Simple Effect (1->1)
48
 
##         |
49
 
##         +---- Transition
50
 
##         |
51
 
##         +---- Complex Effect (N->1)
52
 
 
53
 
class Timeline(gobject.GObject):
54
 
    """
55
 
    Fully fledged timeline
56
 
    """
57
 
 
58
 
    # TODO make the compositions more versatile
59
 
    # for the time being we hardcode an audio and a video composition
60
 
    
61
 
    def __init__(self, project):
62
 
        gst.log("new Timeline for project %s" % project)
63
 
        gobject.GObject.__init__(self)
64
 
        self.project = project
65
 
 
66
 
        self.timeline = gst.Bin("timeline-" + project.name)
67
 
        self._fillContents()
68
 
 
69
 
        self.project.settings.connect_after("settings-changed", self._settingsChangedCb)
70
 
 
71
 
    def _fillContents(self):
72
 
        # TODO create the initial timeline according to the project settings
73
 
        self.audiocomp = TimelineComposition(media_type = MEDIA_TYPE_AUDIO, name="audiocomp")
74
 
        self.videocomp = TimelineComposition(media_type = MEDIA_TYPE_VIDEO, name="videocomp")
75
 
        self.videocomp.linkObject(self.audiocomp)
76
 
 
77
 
        self.timeline.add(self.audiocomp.gnlobject,
78
 
                          self.videocomp.gnlobject)
79
 
        self.audiocomp.gnlobject.connect("pad-added", self._newAudioPadCb)
80
 
        self.videocomp.gnlobject.connect("pad-added", self._newVideoPadCb)
81
 
        self.audiocomp.gnlobject.connect("pad-removed", self._removedAudioPadCb)
82
 
        self.videocomp.gnlobject.connect("pad-removed", self._removedVideoPadCb)
83
 
 
84
 
    def _newAudioPadCb(self, unused_audiocomp, pad):
85
 
        self.timeline.add_pad(gst.GhostPad("asrc", pad))
86
 
 
87
 
    def _newVideoPadCb(self, unused_videocomp, pad):
88
 
        self.timeline.add_pad(gst.GhostPad("vsrc", pad))
89
 
 
90
 
    def _removedAudioPadCb(self, unused_audiocomp, unused_pad):
91
 
        self.timeline.remove_pad(self.timeline.get_pad("asrc"))
92
 
 
93
 
    def _removedVideoPadCb(self, unused_audiocomp, unused_pad):
94
 
        self.timeline.remove_pad(self.timeline.get_pad("vsrc"))
95
 
 
96
 
    def _settingsChangedCb(self, unused_settings):
97
 
        # reset the timeline !
98
 
        result, pstate, pending = self.timeline.get_state(0)
99
 
        self.timeline.set_state(gst.STATE_READY)
100
 
        self.timeline.set_state(pstate)
101
 
 
102
 
 
103
 
class TimelineObject(gobject.GObject):
104
 
    """
105
 
    Base class for all timeline objects
106
 
 
107
 
    * Properties
108
 
      _ Start/Duration Time
109
 
      _ Media Type
110
 
      _ Gnonlin Object
111
 
      _ Linked Object
112
 
        _ Can be None
113
 
        _ Must have same duration
114
 
      _ Brother object
115
 
        _ This is the same object but with the other media_type
116
 
 
117
 
    * signals
118
 
      _ 'start-duration-changed' : start position, duration position
119
 
      _ 'linked-changed' : new linked object
120
 
    """
121
 
 
122
 
    __gsignals__ = {
123
 
        "start-duration-changed" : ( gobject.SIGNAL_RUN_LAST,
124
 
                                 gobject.TYPE_NONE,
125
 
                                 (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, )),
126
 
        "linked-changed" : ( gobject.SIGNAL_RUN_LAST,
127
 
                             gobject.TYPE_NONE,
128
 
                             (gobject.TYPE_PYOBJECT, ))
129
 
        }
130
 
    
131
 
##     start = -1  # start time
132
 
##     duration = -1   # duration time
133
 
##     linked = None       # linked object
134
 
##     brother = None      # brother object, the other-media equivalent of this object
135
 
##     factory = None      # the Factory with more details about this object
136
 
##     gnlobject = None    # The corresponding GnlObject
137
 
##     media_type = MEDIA_TYPE_NONE        # The Media Type of this object
138
 
 
139
 
    def __init__(self, factory=None, start=-1, duration=-1,
140
 
                 media_type=MEDIA_TYPE_NONE, name=""):
141
 
        gobject.GObject.__init__(self)
142
 
        gst.log("new TimelineObject :%s" % name)
143
 
        self.start = -1
144
 
        self.duration = -1
145
 
        self.linked = None
146
 
        self.brother = None
147
 
        self.name = name
148
 
        # Set factory and media_type and then create the gnlobject
149
 
        self.factory = factory
150
 
        self.media_type = media_type
151
 
        self.gnlobject = self._makeGnlObject()
152
 
        self.gnlobject.connect("notify::start", self._startDurationChangedCb)
153
 
        self.gnlobject.connect("notify::duration", self._startDurationChangedCb)
154
 
        self._setStartDurationTime(start, duration)
155
 
 
156
 
    def _makeGnlObject(self):
157
 
        """ create and return the gnl_object """
158
 
        raise NotImplementedError
159
 
 
160
 
    def _unlinkObject(self):
161
 
        # really unlink the objects
162
 
        if self.linked:
163
 
            self.linked = None
164
 
            self.emit("linked-changed", None)
165
 
 
166
 
    def _linkObject(self, object):
167
 
        # really do the link
168
 
        self.linked = object
169
 
        self.emit("linked-changed", self.linked)
170
 
 
171
 
    def linkObject(self, object):
172
 
        """
173
 
        link another object to this one.
174
 
        If there already is a linked object ,it will unlink it
175
 
        """
176
 
        if self.linked and not self.linked == object:
177
 
            self.unlinkObject()
178
 
        self._linkObject(object)
179
 
        pass
180
 
 
181
 
    def unlinkObject(self):
182
 
        """
183
 
        unlink from the current linked object
184
 
        """
185
 
        self.linked._unlinkObject()
186
 
        self._unlinkObject()
187
 
 
188
 
    def relinkBrother(self):
189
 
        """
190
 
        links the object back to it's brother
191
 
        """
192
 
        # if already linked, unlink from previous
193
 
        if self.linked:
194
 
            self.unlinkObject()
195
 
 
196
 
        # link to brother
197
 
        if self.brother:
198
 
            self.linkObject(self.brother)
199
 
 
200
 
    def getBrother(self, autolink=True):
201
 
        """
202
 
        returns the brother element if it's possible,
203
 
        if autolink, then automatically link it to this element
204
 
        """
205
 
        if not self.brother:
206
 
            self.brother = self._makeBrother()
207
 
            if not self.brother:
208
 
                return None
209
 
        if autolink and not self.linked == self.brother:
210
 
            self.relinkBrother()
211
 
        return self.brother
212
 
 
213
 
    def _makeBrother(self):
214
 
        """
215
 
        Make the exact same object for the other media_type
216
 
        implemented in subclasses
217
 
        """
218
 
        raise NotImplementedError
219
 
    
220
 
    def _setStartDurationTime(self, start=-1, duration=-1):
221
 
        # really modify the start/duration time
222
 
        self.gnlobject.info("start:%s , duration:%s" %( gst.TIME_ARGS(start),
223
 
                                                        gst.TIME_ARGS(duration)))
224
 
        if not duration == -1 and not self.duration == duration:
225
 
            self.duration = duration
226
 
            self.gnlobject.set_property("duration", long(duration))
227
 
        if not start == -1 and not self.start == start:
228
 
            self.start = start
229
 
            self.gnlobject.set_property("start", long(start))
230
 
            
231
 
    def setStartDurationTime(self, start=-1, duration=-1):
232
 
        """ sets the start and/or duration time """
233
 
        self._setStartDurationTime(start, duration)
234
 
        if self.linked:
235
 
            self.linked._setStartDurationTime(start, duration)
236
 
 
237
 
    def _startDurationChangedCb(self, gnlobject, property):
238
 
        """ start/duration time has changed """
239
 
        self.gnlobject.debug("property:%s" % property.name)
240
 
        start = -1
241
 
        duration = -1
242
 
        if property.name == "start":
243
 
            start = gnlobject.get_property("start")
244
 
            if start == self.start:
245
 
                start = -1
246
 
            else:
247
 
                self.start = long(start)
248
 
        elif property.name == "duration":
249
 
            duration = gnlobject.get_property("duration")
250
 
            if duration == self.duration:
251
 
                duration = -1
252
 
            else:
253
 
                self.gnlobject.debug("duration changed:%s" % gst.TIME_ARGS(duration))
254
 
                self.duration = long(duration)
255
 
        #if not start == -1 or not duration == -1:
256
 
        self.emit("start-duration-changed", self.start, self.duration)
257
 
            
258
 
 
259
 
        
260
 
class TimelineSource(TimelineObject):
261
 
    """
262
 
    Base class for all sources (O input)
263
 
    """
264
 
 
265
 
    def __init__(self, **kw):
266
 
        TimelineObject.__init__(self, **kw)
267
 
        
268
 
 
269
 
class TimelineFileSource(TimelineSource):
270
 
    """
271
 
    Seekable sources (mostly files)
272
 
    """
273
 
    __gsignals__ = {
274
 
        "media-start-duration-changed" : ( gobject.SIGNAL_RUN_LAST,
275
 
                                       gobject.TYPE_NONE,
276
 
                                       (gobject.TYPE_UINT64, gobject.TYPE_UINT64))
277
 
        }
278
 
 
279
 
    media_start = -1
280
 
    media_duration = -1
281
 
    
282
 
    def __init__(self, media_start=-1, media_duration=-1, **kw):
283
 
        TimelineSource.__init__(self, **kw)
284
 
        self.gnlobject.connect("notify::media-start", self._mediaStartDurationChangedCb)
285
 
        self.gnlobject.connect("notify::media-duration", self._mediaStartDurationChangedCb)
286
 
        if media_start == -1:
287
 
            media_start = 0
288
 
        if media_duration == -1:
289
 
            media_duration = self.factory.length
290
 
        self.setMediaStartDurationTime(media_start, media_duration)
291
 
        
292
 
    def _makeGnlObject(self):
293
 
        if self.media_type == MEDIA_TYPE_AUDIO:
294
 
            caps = gst.caps_from_string("audio/x-raw-int;audio/x-raw-float")
295
 
        elif self.media_type == MEDIA_TYPE_VIDEO:
296
 
            caps = gst.caps_from_string("video/x-raw-yuv;video/x-raw-rgb")
297
 
        else:
298
 
            raise NameError, "media type is NONE !"
299
 
        self.factory.lastbinid = self.factory.lastbinid + 1
300
 
        gnlobject = gst.element_factory_make("gnlfilesource", "source-" + self.name + str(self.factory.lastbinid))
301
 
        gnlobject.set_property("location", self.factory.name)
302
 
        gnlobject.set_property("caps", caps)
303
 
        gnlobject.set_property("start", long(0))
304
 
        gnlobject.set_property("duration", long(self.factory.length))
305
 
        return gnlobject
306
 
        
307
 
    def _makeBrother(self):
308
 
        """ make the brother element """
309
 
        self.gnlobject.info("making filesource brother")
310
 
        # find out if the factory provides the other element type
311
 
        if self.media_type == MEDIA_TYPE_NONE:
312
 
            return None
313
 
        if self.media_type == MEDIA_TYPE_VIDEO:
314
 
            if not self.factory.is_audio:
315
 
                return None
316
 
            brother = TimelineFileSource(media_start=self.media_start, media_duration=self.media_duration,
317
 
                                         factory=self.factory, start=self.start, duration=self.duration,
318
 
                                         media_type=MEDIA_TYPE_AUDIO, name=self.name)
319
 
        elif self.media_type == MEDIA_TYPE_AUDIO:
320
 
            if not self.factory.is_video:
321
 
                return None
322
 
            brother = TimelineFileSource(media_start=self.media_start, media_duration=self.media_duration,
323
 
                                         factory=self.factory, start=self.start, duration=self.duration,
324
 
                                         media_type=MEDIA_TYPE_VIDEO, name=self.name)
325
 
        else:
326
 
            brother = None
327
 
        return brother
328
 
 
329
 
    def _setMediaStartDurationTime(self, start=-1, duration=-1):
330
 
        gst.info("TimelineFileSource start:%d , duration:%d" % (start, duration))
331
 
        if not duration == -1 and not self.media_duration == duration:
332
 
            self.media_duration = duration
333
 
            self.gnlobject.set_property("media-duration", long(duration))
334
 
        if not start == -1 and not self.media_start == start:
335
 
            self.media_start = start
336
 
            self.gnlobject.set_property("media-start", long(start))
337
 
 
338
 
    def setMediaStartDurationTime(self, start=-1, duration=-1):
339
 
        """ sets the media start/duration time """
340
 
        self._setMediaStartDurationTime(start, duration)
341
 
        if self.linked and isinstance(self.linked, TimelineFileSource):
342
 
            self.linked._setMediaStartDurationTime(start, duration)
343
 
 
344
 
    def _mediaStartDurationChangedCb(self, gnlobject, property):
345
 
        mstart = None
346
 
        mduration = None
347
 
        if property.name == "media-start":
348
 
            mstart = gnlobject.get_property("media-start")
349
 
            if mstart == self.media_start:
350
 
                mstart = None
351
 
            else:
352
 
                self.media_start = mstart
353
 
        elif property.name == "media-duration":
354
 
            mduration = gnlobject.get_property("media-duration")
355
 
            if mduration == self.media_duration:
356
 
                mduration = None
357
 
            else:
358
 
                self.media_duration = mduration
359
 
        if mstart or mduration:
360
 
            self.emit("media-start-duration-changed",
361
 
                      self.media_start, self.media_duration)
362
 
 
363
 
 
364
 
class TimelineLiveSource(TimelineSource):
365
 
    """
366
 
    Non-seekable sources (like cameras)
367
 
    """
368
 
 
369
 
    def __init__(self, **kw):
370
 
        TimelineSource.__init__(self, **kw)
371
 
 
372
 
 
373
 
class TimelineComposition(TimelineSource):
374
 
    """
375
 
    Combines sources and effects
376
 
    _ Sets the priority of the GnlObject(s) contained within
377
 
    _ Effects have always got priorities higher than the sources
378
 
    _ Can contain global effects that have the highest priority
379
 
      _ Those global effect spread the whole duration of the composition
380
 
    _ Simple effects can overlap each other
381
 
    _ Complex Effect(s) have a lower priority than Simple Effect(s)
382
 
      _ For sanity reasons, Complex Effect(s) can't overlap each other
383
 
    _ Transitions have the lowest effect priority
384
 
    _ Source(s) contained in it follow each other if possible
385
 
    _ Source can overlap each other
386
 
      _ Knows the "visibility" of the sources contained within
387
 
 
388
 
    _ Provides a "condensed list" of the objects contained within
389
 
      _ Allows to quickly show a top-level view of the composition
390
 
    
391
 
    * Sandwich view example (top: high priority):
392
 
             [ Global Simple Effect(s) (RGB, YUV, Speed,...)    ]
393
 
             [ Simple Effect(s), can be several layers          ]
394
 
             [ Complex Effect(s), non-overlapping               ]
395
 
             [ Transition(s), non-overlapping                   ]
396
 
             [ Layers of sources                                ]
397
 
 
398
 
    * Properties:
399
 
      _ Global Simple Effect(s) (Optionnal)
400
 
      _ Simple Effect(s)
401
 
      _ Complex Effect(s)
402
 
      _ Transition(s)
403
 
      _ Condensed list
404
 
 
405
 
    * Signals:
406
 
      _ 'condensed-list-changed' : condensed list
407
 
      _ 'global-effect-added' : a global-effect was added to the composition
408
 
      _ 'global-effect-removed' : a global-effect was removed from the composition
409
 
      _ 'simple-effect-added' : a simple-effect was added to the composition
410
 
      _ 'simple-effect-removed' : a simple-effect was removed from the composition
411
 
      _ 'complex-effect-added' : a complex-effect was added to the composition
412
 
      _ 'complex-effect-removed' : a complex-effect was removed from the composition
413
 
      _ 'transition-added' : a transition was added to the composition
414
 
      _ 'transition-removed' : a transitions was removed from the composition
415
 
      _ 'source-added' : a TimelineSource was added to the composition
416
 
      _ 'source-removed' : a TimelineSource was removed from the composition
417
 
    """
418
 
 
419
 
    __gsignals__ = {
420
 
        'condensed-list-changed' : ( gobject.SIGNAL_RUN_LAST,
421
 
                                     gobject.TYPE_NONE,
422
 
                                     (gobject.TYPE_PYOBJECT, )),
423
 
        'global-effect-added' : ( gobject.SIGNAL_RUN_LAST,
424
 
                                  gobject.TYPE_NONE,
425
 
                                  (gobject.TYPE_PYOBJECT, )),
426
 
        'global-effect-removed' : ( gobject.SIGNAL_RUN_LAST,
427
 
                                    gobject.TYPE_NONE,
428
 
                                    (gobject.TYPE_PYOBJECT, )),
429
 
        'simple-effect-added' : ( gobject.SIGNAL_RUN_LAST,
430
 
                                  gobject.TYPE_NONE,
431
 
                                  (gobject.TYPE_PYOBJECT, )),
432
 
        'simple-effect-removed' : ( gobject.SIGNAL_RUN_LAST,
433
 
                                    gobject.TYPE_NONE,
434
 
                                    (gobject.TYPE_PYOBJECT, )),
435
 
        'complex-effect-added' : ( gobject.SIGNAL_RUN_LAST,
436
 
                                   gobject.TYPE_NONE,
437
 
                                   (gobject.TYPE_PYOBJECT, )),
438
 
        'complex-effect-removed' : ( gobject.SIGNAL_RUN_LAST,
439
 
                                     gobject.TYPE_NONE,
440
 
                                     (gobject.TYPE_PYOBJECT, )),
441
 
        'transitions-added' : ( gobject.SIGNAL_RUN_LAST,
442
 
                                gobject.TYPE_NONE,
443
 
                                (gobject.TYPE_PYOBJECT, )),
444
 
        'transition-removed' : ( gobject.SIGNAL_RUN_LAST,
445
 
                                 gobject.TYPE_NONE,
446
 
                                 (gobject.TYPE_PYOBJECT, )),
447
 
        'source-added' : ( gobject.SIGNAL_RUN_LAST,
448
 
                           gobject.TYPE_NONE,
449
 
                           (gobject.TYPE_PYOBJECT, )),
450
 
        'source-removed' : ( gobject.SIGNAL_RUN_LAST,
451
 
                             gobject.TYPE_NONE,
452
 
                             (gobject.TYPE_PYOBJECT, )),
453
 
        }
454
 
 
455
 
 
456
 
    def __init__(self, **kw):
457
 
        self.global_effects = [] # list of effects starting from highest priority
458
 
        self.simple_effects = [[]] # list of layers of simple effects (order: priority, then time)
459
 
        self.complex_effects = [] # complex effect sorted by time
460
 
        self.transitions = [] # transitions sorted by time
461
 
        # list of layers of simple effects (order: priority, then time)
462
 
        # each layer contains (min priority, max priority, list objects)
463
 
        #sources = [(2048, 2060, [])] 
464
 
        self.condensed = [] # list of sources/transitions seen from a top-level view
465
 
        self.sources = [(2048, 2060, [])]
466
 
        TimelineSource.__init__(self, **kw)
467
 
 
468
 
    def _makeGnlObject(self):
469
 
        return gst.element_factory_make("gnlcomposition", "composition-" + self.name)
470
 
 
471
 
    # global effects
472
 
    
473
 
    def addGlobalEffect(self, global_effect, order, auto_linked=True):
474
 
        """
475
 
        add a global effect
476
 
        order :
477
 
           n : put at the given position (0: first)
478
 
           -1 : put at the end (lowest priority)
479
 
        auto_linked : if True will add the brother (if any) of the given effect
480
 
                to the linked composition with the same order
481
 
        """
482
 
        raise NotImplementedError
483
 
 
484
 
    def removeGlobalEffect(self, global_effect, remove_linked=True):
485
 
        """
486
 
        remove a global effect
487
 
        If remove_linked is True and the effect has a linked effect, will remove
488
 
        it from the linked composition
489
 
        """
490
 
        raise NotImplementedError
491
 
 
492
 
    # simple effects
493
 
    
494
 
    def addSimpleEffect(self, simple_effect, order, auto_linked=True):
495
 
        """
496
 
        add a simple effect
497
 
 
498
 
        order works if there's overlapping:
499
 
           n : put at the given position (0: first)
500
 
           -1 : put underneath all other simple effects
501
 
        auto_linked : if True will add the brother (if any) of the given effect
502
 
                to the linked composition with the same order
503
 
        """
504
 
        raise NotImplementedError
505
 
 
506
 
    def removeSimpleEffect(self, simple_effect, remove_linked=True):
507
 
        """
508
 
        removes a simple effect
509
 
        If remove_linked is True and the effect has a linked effect, will remove
510
 
        it from the linked composition
511
 
        """
512
 
        raise NotImplementedError
513
 
 
514
 
    # complex effect
515
 
 
516
 
    def addComplexEffect(self, complex_effect, auto_linked=True):
517
 
        """
518
 
        adds a complex effect
519
 
        auto_linked : if True will add the brother (if any) of the given effect
520
 
                to the linked composition with the same order
521
 
        """
522
 
        # if it overlaps with existing complex effect, raise exception
523
 
        raise NotImplementedError
524
 
 
525
 
    def removeComplexEffect(self, complex_effect, remove_linked=True):
526
 
        """
527
 
        removes a complex effect
528
 
        If remove_linked is True and the effect has a linked effect, will remove
529
 
        it from the linked composition
530
 
        """
531
 
        raise NotImplementedError
532
 
 
533
 
    def _makeCondensedList(self):
534
 
        """ makes a condensed list """
535
 
        def condensed_sum(list1, list2):
536
 
            """ returns a condensed list of the two given lists """
537
 
            self.gnlobject.info( "condensed_sum")
538
 
            self.gnlobject.info( "comparing %s with %s" % (list1, list2))
539
 
            if not list1:
540
 
                return list2[:]
541
 
            if not list2:
542
 
                return list1[:]
543
 
            
544
 
            res = list1[:]
545
 
 
546
 
            # find the objects in list2 that go under list1 and insert them at
547
 
            # the good position in res
548
 
            for obj in list2:
549
 
                # go through res to see if it can go somewhere
550
 
                for pos in range(len(res)):
551
 
                    if obj.start <= res[pos].start:
552
 
                        res.insert(pos, obj)
553
 
                        break
554
 
                if pos == len(res) and obj.start > res[-1].start:
555
 
                    res.append(obj)
556
 
            self.gnlobject.info("returning %s" % res)
557
 
            return res
558
 
                
559
 
            
560
 
        lists = [x[2] for x in self.sources]
561
 
        lists.insert(0, self.transitions)
562
 
        return reduce(condensed_sum, lists)
563
 
 
564
 
    def _updateCondensedList(self):
565
 
        """ updates the condensed list """
566
 
        self.gnlobject.info("_update_condensed_list")
567
 
        # build a condensed list
568
 
        clist = self._makeCondensedList()
569
 
        if self.condensed:
570
 
            # compare it to the self.condensed
571
 
            list_changed = False
572
 
##             print "comparing:"
573
 
##             for i in self.condensed:
574
 
##                 print i.gnlobject, i.start, i.duration
575
 
##             print "with"
576
 
##             for i in clist:
577
 
##                 print i.gnlobject, i.start, i.duration
578
 
            if not len(clist) == len(self.condensed):
579
 
                list_changed = True
580
 
            else:
581
 
                for a, b in zip(clist, self.condensed):
582
 
                    if not a == b:
583
 
                        list_changed = True
584
 
                        break
585
 
        else:
586
 
            list_changed = True
587
 
        # if it's different or new, set it to self.condensed and emit the signal
588
 
        if list_changed:
589
 
            self.condensed = clist
590
 
            self.emit("condensed-list-changed", self.condensed)
591
 
 
592
 
    # Transitions
593
 
 
594
 
    def addTransition(self, transition, source1, source2, auto_linked=True):
595
 
        """
596
 
        adds a transition between source1 and source2
597
 
        auto_linked : if True will add the brother (if any) of the given transition
598
 
                to the linked composition with the same parameters
599
 
        """
600
 
        # if it overlaps with existing transition, raise exception
601
 
        raise NotImplementedError
602
 
 
603
 
    def moveTransition(self, transition, source1, source2):
604
 
        """ move a transition between source1 and source2 """
605
 
        # if it overlays with existing transition, raise exception
606
 
        raise NotImplementedError
607
 
 
608
 
    def removeTransition(self, transition, reorder_sources=True, remove_linked=True):
609
 
        """
610
 
        removes a transition,
611
 
        If reorder sources is True it puts the sources
612
 
        between which the transition was back one after the other
613
 
        If remove_linked is True and the transition has a linked effect, will remove
614
 
        it from the linked composition
615
 
        """
616
 
        raise NotImplementedError
617
 
 
618
 
    # Sources
619
 
 
620
 
    def _getSourcePosition(self, source):
621
 
        position = 0
622
 
        foundit = False
623
 
        for slist in self.sources:
624
 
            if source in slist[2]:
625
 
                foundit = True
626
 
                break
627
 
            position = position + 1
628
 
        if foundit:
629
 
            return position + 1
630
 
        return 0
631
 
 
632
 
    def _haveGotThisSource(self, source):
633
 
        for slist in self.sources:
634
 
            if source in slist[2]:
635
 
                return True
636
 
        return False
637
 
 
638
 
    def addSource(self, source, position, auto_linked=True):
639
 
        """
640
 
        add a source (with correct start/duration time already set)
641
 
        position : the vertical position
642
 
          _ 0 : insert above all other layers
643
 
          _ n : insert at the given position (1: top row)
644
 
          _ -1 : insert at the bottom, under all sources
645
 
        auto_linked : if True will add the brother (if any) of the given source
646
 
                to the linked composition with the same parameters
647
 
        """
648
 
        self.gnlobject.info("source %s , position:%d, self.sources:%s" %(source, position, self.sources))
649
 
        
650
 
        def my_add_sorted(sources, object):
651
 
            slist = sources[2]
652
 
            i = 0
653
 
            for item in slist:
654
 
                if item.start > object.start:
655
 
                    break
656
 
                i = i + 1
657
 
            object.gnlobject.set_property("priority", sources[0])
658
 
            slist.insert(i, object)
659
 
            
660
 
        # TODO : add functionnality to add above/under
661
 
        # For the time being it's hardcoded to a single layer
662
 
        position = 1
663
 
 
664
 
        # add it to the correct self.sources[position]
665
 
        my_add_sorted(self.sources[position-1], source)
666
 
        
667
 
        # add it to self.gnlobject
668
 
        self.gnlobject.info("adding %s to our composition" % source.gnlobject)
669
 
        self.gnlobject.add(source.gnlobject)
670
 
 
671
 
        # update the condensed list
672
 
        self._updateCondensedList()
673
 
 
674
 
        # if auto_linked and self.linked, add brother to self.linked with same parameters
675
 
        if auto_linked and self.linked:
676
 
            if source.getBrother():
677
 
                self.linked.addSource(source.brother, position, auto_linked=False)
678
 
        self.gnlobject.info("added source %s" % source.gnlobject)
679
 
        gst.info("%s" % str(self.sources))
680
 
        self.emit('source-added', source)
681
 
 
682
 
    def insertSourceAfter(self, source, existingsource, push_following=True, auto_linked=True):
683
 
        """
684
 
        inserts a source after the existingsource, pushing the following ones
685
 
        if existingsource is None, it puts the source at the beginning
686
 
        """
687
 
        if existingsource:
688
 
            self.gnlobject.info("insert_source after %s" % existingsource.gnlobject)
689
 
        else:
690
 
            self.gnlobject.info("insert_source at the beginning")
691
 
            
692
 
        # find the time where it's going to be added
693
 
        if not existingsource or not self._haveGotThisSource(existingsource):
694
 
            start = 0
695
 
            position = 1
696
 
            existorder = 0
697
 
        else:
698
 
            start = existingsource.start + existingsource.duration
699
 
            position = self._getSourcePosition(existingsource)
700
 
            existorder = self.sources[position - 1][2].index(existingsource) + 1
701
 
 
702
 
        gst.info("start=%s, position=%d, existorder=%d, sourcelength=%s" % (gst.TIME_ARGS(start),
703
 
                                                                            position,
704
 
                                                                            existorder,
705
 
                                                                            gst.TIME_ARGS(source.factory.length)))
706
 
##         for i in self.sources[position -1][2]:
707
 
##             print i.gnlobject, i.start, i.duration
708
 
        # set the correct start/duration time
709
 
        duration = source.factory.length
710
 
        source.setStartDurationTime(start, duration)
711
 
        
712
 
        # pushing following
713
 
        if push_following and not position in [-1, 0]:
714
 
            #print self.gnlobject, "pushing following", existorder, len(self.sources[position - 1][2])
715
 
            for i in range(existorder, len(self.sources[position - 1][2])):
716
 
                mvsrc = self.sources[position - 1][2][i]
717
 
                self.gnlobject.info("pushing following")
718
 
                #print "run", i, "start", mvsrc.start, "duration", mvsrc.duration
719
 
                # increment self.sources[position - 1][i] by source.factory.length
720
 
                mvsrc.setStartDurationTime(mvsrc.start + source.factory.length)
721
 
        
722
 
        self.addSource(source, position, auto_linked=auto_linked)
723
 
 
724
 
    def appendSource(self, source, position=1, auto_linked=True):
725
 
        """
726
 
        puts a source after all the others
727
 
        """
728
 
        self.gnlobject.info("source:%s" % source.gnlobject)
729
 
        # find the source with the highest duration time on the first layer
730
 
        if self.sources[position - 1]:
731
 
            existingsource = self.sources[position - 1][2][-1]
732
 
        else:
733
 
            existingsource = None
734
 
 
735
 
        self.insertSourceAfter(source, existingsource, push_following=False,
736
 
                               auto_linked=auto_linked)
737
 
 
738
 
    def prependSource(self, source, push_following=True, auto_linked=True):
739
 
        """
740
 
        adds a source to the beginning of the sources
741
 
        """
742
 
        self.gnlobject.info("source:%s" % source.gnlobject)
743
 
        self.insertSourceAfter(source, None, push_following, auto_linked)
744
 
 
745
 
    def moveSource(self, source, newpos):
746
 
        """
747
 
        moves the source to the new position
748
 
        """
749
 
        raise NotImplementedError
750
 
 
751
 
    def removeSource(self, source, remove_linked=True, collapse_neighbours=False):
752
 
        """
753
 
        removes a source
754
 
        If remove_linked is True and the source has a linked source, will remove
755
 
        it from the linked composition
756
 
        """
757
 
        raise NotImplementedError
758
 
 
759
 
 
760
 
class TimelineEffect(TimelineObject):
761
 
    """
762
 
    Base class for effects (1->n input(s))
763
 
    """
764
 
 
765
 
    def __init__(self, nbinputs=1, **kw):
766
 
        self.nbinputs = nbinputs
767
 
        TimelineObject.__init__(self, **kw)
768
 
 
769
 
    def _makeGnlObject(self):
770
 
        gnlobject = gst.element_factory_make("gnloperation", "operation-" + self.name)
771
 
        self._setUpGnlOperation(gnlobject)
772
 
        return gnlobject
773
 
 
774
 
    def _setUpGnlOperation(self, gnlobject):
775
 
        """ fill up the gnloperation for the first go """
776
 
        raise NotImplementedError
777
 
 
778
 
class TimelineSimpleEffect(TimelineEffect):
779
 
    """
780
 
    Simple effects (1 input)
781
 
    """
782
 
 
783
 
    def __init__(self, factory, **kw):
784
 
        self.factory = factory
785
 
        TimelineEffect.__init__(self, **kw)
786
 
 
787
 
 
788
 
class TimelineTransition(TimelineEffect):
789
 
    """
790
 
    Transition Effect
791
 
    """
792
 
    source1 = None
793
 
    source2 = None
794
 
 
795
 
    def __init__(self, factory, source1=None, source2=None, **kw):
796
 
        self.factory = factory
797
 
        TimelineEffect.__init__(self, nbinputs=2, **kw)
798
 
        self.setSources(source1, source2)
799
 
 
800
 
    def setSources(self, source1, source2):
801
 
        """ changes the sources in between which the transition lies """
802
 
        self.source1 = source1
803
 
        self.source2 = source2
804
 
 
805
 
 
806
 
class TimelineComplexEffect(TimelineEffect):
807
 
    """
808
 
    Complex Effect
809
 
    """
810
 
 
811
 
    def __init__(self, factory, **kw):
812
 
        self.factory = factory
813
 
        # Find out the number of inputs
814
 
        nbinputs = 2
815
 
        TimelineEffect.__init__(self, nbinputs=nbinputs, **kw)