~ubuntu-branches/ubuntu/hardy/pitivi/hardy

« back to all changes in this revision

Viewing changes to pitivi/timeline/composition.py

  • Committer: Bazaar Package Importer
  • Author(s): Loic Minier
  • Date: 2007-01-31 15:32:37 UTC
  • mfrom: (1.1.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20070131153237-t958gfphlgt494cb
Tags: 0.10.2-1
* New upstream release, "A New Toy".
  - Update contributors list in copyright.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# PiTiVi , Non-linear video editor
 
2
#
 
3
#       pitivi/timeline/composition.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 Composition object
 
24
"""
 
25
 
 
26
import gobject
 
27
import gst
 
28
 
 
29
from pitivi.settings import ExportSettings
 
30
from source import TimelineSource
 
31
from objects import BrotherObjects, MEDIA_TYPE_AUDIO
 
32
 
 
33
class Layer(BrotherObjects):
 
34
    """
 
35
    Base class for composition layers (effects, sources, ...)
 
36
    """
 
37
 
 
38
    def __init__(self):
 
39
        gobject.GObject.__init__(self)
 
40
 
 
41
 
 
42
class EffectsLayer(Layer):
 
43
    """
 
44
    Layers of the composition that have only one priority
 
45
    """
 
46
 
 
47
    def __init__(self, priority):
 
48
        Layer.__initi__(self)
 
49
        self._priority = priority
 
50
        self._effects = []
 
51
 
 
52
    def __len__(self):
 
53
        return len(self._effects)
 
54
 
 
55
    def __getitem__(self, x):
 
56
        return self._effects.__getitem__(x)
 
57
 
 
58
class SourcesLayer(Layer):
 
59
    """
 
60
    Layers of the composition that have minimum and maximum priority
 
61
    Sources are sorted by start time and then by priority
 
62
    """
 
63
 
 
64
    def __init__(self, minprio, maxprio):
 
65
        Layer.__initi__(self)
 
66
        self._minprio = minprio
 
67
        self._maxprio = maxprio
 
68
        self._sources = []
 
69
 
 
70
    def __len__(self):
 
71
        return len(self._sources)
 
72
 
 
73
    def __contains__(self, source):
 
74
        return self._sources.__contains__(source)
 
75
 
 
76
    def index(self, source):
 
77
        return self._sources.index(source)
 
78
 
 
79
 
 
80
class TimelineComposition(TimelineSource):
 
81
    """
 
82
    Combines sources and effects
 
83
    _ Sets the priority of the GnlObject(s) contained within
 
84
    _ Effects have always got priorities higher than the sources
 
85
    _ Can contain global effects that have the highest priority
 
86
      _ Those global effect spread the whole duration of the composition
 
87
    _ Simple effects (applies on one source), can overlap each other
 
88
    _ Complex Effect(s) have a lower priority than Simple Effect(s)
 
89
      _ For sanity reasons, Complex Effect(s) can't overlap each other
 
90
    _ Transitions have the lowest effect priority
 
91
    _ Source(s) contained in it follow each other if possible
 
92
    _ Source can overlap each other
 
93
      _ Knows the "visibility" of the sources contained within
 
94
 
 
95
    _ Provides a "condensed list" of the objects contained within
 
96
      _ Allows to quickly show a top-level view of the composition
 
97
    
 
98
    * Sandwich view example (top: high priority):
 
99
             [ Global Simple Effect(s) (RGB, YUV, Speed,...)    ]
 
100
             [ Simple Effect(s), can be several layers          ]
 
101
             [ Complex Effect(s), non-overlapping               ]
 
102
             [ Transition(s), non-overlapping                   ]
 
103
             [ Layers of sources                                ]
 
104
 
 
105
    * Properties:
 
106
      _ Global Simple Effect(s) (Optionnal)
 
107
      _ Simple Effect(s)
 
108
      _ Complex Effect(s)
 
109
      _ Transition(s)
 
110
      _ Condensed list
 
111
 
 
112
    * Signals:
 
113
      _ 'condensed-list-changed' : condensed list
 
114
      _ 'global-effect-added' : a global-effect was added to the composition
 
115
      _ 'global-effect-removed' : a global-effect was removed from the composition
 
116
      _ 'simple-effect-added' : a simple-effect was added to the composition
 
117
      _ 'simple-effect-removed' : a simple-effect was removed from the composition
 
118
      _ 'complex-effect-added' : a complex-effect was added to the composition
 
119
      _ 'complex-effect-removed' : a complex-effect was removed from the composition
 
120
      _ 'transition-added' : a transition was added to the composition
 
121
      _ 'transition-removed' : a transitions was removed from the composition
 
122
      _ 'source-added' : a TimelineSource was added to the composition
 
123
      _ 'source-removed' : a TimelineSource was removed from the composition
 
124
    """
 
125
 
 
126
    __gsignals__ = {
 
127
        'condensed-list-changed' : ( gobject.SIGNAL_RUN_LAST,
 
128
                                     gobject.TYPE_NONE,
 
129
                                     (gobject.TYPE_PYOBJECT, )),
 
130
        'global-effect-added' : ( gobject.SIGNAL_RUN_LAST,
 
131
                                  gobject.TYPE_NONE,
 
132
                                  (gobject.TYPE_PYOBJECT, )),
 
133
        'global-effect-removed' : ( gobject.SIGNAL_RUN_LAST,
 
134
                                    gobject.TYPE_NONE,
 
135
                                    (gobject.TYPE_PYOBJECT, )),
 
136
        'simple-effect-added' : ( gobject.SIGNAL_RUN_LAST,
 
137
                                  gobject.TYPE_NONE,
 
138
                                  (gobject.TYPE_PYOBJECT, )),
 
139
        'simple-effect-removed' : ( gobject.SIGNAL_RUN_LAST,
 
140
                                    gobject.TYPE_NONE,
 
141
                                    (gobject.TYPE_PYOBJECT, )),
 
142
        'complex-effect-added' : ( gobject.SIGNAL_RUN_LAST,
 
143
                                   gobject.TYPE_NONE,
 
144
                                   (gobject.TYPE_PYOBJECT, )),
 
145
        'complex-effect-removed' : ( gobject.SIGNAL_RUN_LAST,
 
146
                                     gobject.TYPE_NONE,
 
147
                                     (gobject.TYPE_PYOBJECT, )),
 
148
        'transitions-added' : ( gobject.SIGNAL_RUN_LAST,
 
149
                                gobject.TYPE_NONE,
 
150
                                (gobject.TYPE_PYOBJECT, )),
 
151
        'transition-removed' : ( gobject.SIGNAL_RUN_LAST,
 
152
                                 gobject.TYPE_NONE,
 
153
                                 (gobject.TYPE_PYOBJECT, )),
 
154
        'source-added' : ( gobject.SIGNAL_RUN_LAST,
 
155
                           gobject.TYPE_NONE,
 
156
                           (gobject.TYPE_PYOBJECT, )),
 
157
        'source-removed' : ( gobject.SIGNAL_RUN_LAST,
 
158
                             gobject.TYPE_NONE,
 
159
                             (gobject.TYPE_PYOBJECT, )),
 
160
        }
 
161
 
 
162
    # mid-level representation/storage of sources/effecst lists
 
163
    #
 
164
    # Global effects:
 
165
    #   Apply on the whole duration of the composition.
 
166
    #   Sorted by priority (first: most important)
 
167
    #
 
168
    # Simple effects:
 
169
    #   2 dimensional list
 
170
    #   Priority, then time
 
171
    #
 
172
    # Complex effect:
 
173
    # Transitions:
 
174
    #   Simple list sorted by time
 
175
    #
 
176
    # Source List:
 
177
    #   List of layers
 
178
    #   Layers:
 
179
    #      Handles priority attribution to contained sources
 
180
    #      3-tuple:
 
181
    #      _ minimum priority
 
182
    #      _ maximum priority
 
183
    #      _ list of sources sorted by time
 
184
 
 
185
    def __init__(self, **kw):
 
186
        self.global_effects = [] # list of effects starting from highest priority
 
187
        self.simple_effects = [[]] # list of layers of simple effects (order: priority, then time)
 
188
        self.complex_effects = [] # complex effect sorted by time
 
189
        self.transitions = [] # transitions sorted by time
 
190
        # list of layers of simple effects (order: priority, then time)
 
191
        self.condensed = [] # list of sources/transitions seen from a top-level view
 
192
        # each layer contains (min priority, max priority, list objects)
 
193
        #sources = [(2048, 2060, [])] 
 
194
        self.sources = [(2048, 2060, [])]
 
195
        self.defaultSource = None
 
196
        TimelineSource.__init__(self, **kw)
 
197
 
 
198
    def __len__(self):
 
199
        """ return the number of sources in this composition """
 
200
        l = 0
 
201
        for min, max, sources in self.sources:
 
202
            l += len(sources)
 
203
        return l
 
204
 
 
205
    def __nonzero__(self):
 
206
        """ Always returns True, else bool(object) will return False if len(object) == 0 """
 
207
        return True
 
208
 
 
209
    def _makeGnlObject(self):
 
210
        return gst.element_factory_make("gnlcomposition", "composition-" + self.name)
 
211
 
 
212
    # global effects
 
213
    
 
214
    def addGlobalEffect(self, global_effect, order, auto_linked=True):
 
215
        """
 
216
        add a global effect
 
217
        order :
 
218
           n : put at the given position (0: first)
 
219
           -1 : put at the end (lowest priority)
 
220
        auto_linked : if True will add the brother (if any) of the given effect
 
221
                to the linked composition with the same order
 
222
        """
 
223
        raise NotImplementedError
 
224
 
 
225
    def removeGlobalEffect(self, global_effect, remove_linked=True):
 
226
        """
 
227
        remove a global effect
 
228
        If remove_linked is True and the effect has a linked effect, will remove
 
229
        it from the linked composition
 
230
        """
 
231
        raise NotImplementedError
 
232
 
 
233
    # simple effects
 
234
    
 
235
    def addSimpleEffect(self, simple_effect, order, auto_linked=True):
 
236
        """
 
237
        add a simple effect
 
238
 
 
239
        order works if there's overlapping:
 
240
           n : put at the given position (0: first)
 
241
           -1 : put underneath all other simple effects
 
242
        auto_linked : if True will add the brother (if any) of the given effect
 
243
                to the linked composition with the same order
 
244
        """
 
245
        raise NotImplementedError
 
246
 
 
247
    def removeSimpleEffect(self, simple_effect, remove_linked=True):
 
248
        """
 
249
        removes a simple effect
 
250
        If remove_linked is True and the effect has a linked effect, will remove
 
251
        it from the linked composition
 
252
        """
 
253
        raise NotImplementedError
 
254
 
 
255
    # complex effect
 
256
 
 
257
    def addComplexEffect(self, complex_effect, auto_linked=True):
 
258
        """
 
259
        adds a complex effect
 
260
        auto_linked : if True will add the brother (if any) of the given effect
 
261
                to the linked composition with the same order
 
262
        """
 
263
        # if it overlaps with existing complex effect, raise exception
 
264
        raise NotImplementedError
 
265
 
 
266
    def removeComplexEffect(self, complex_effect, remove_linked=True):
 
267
        """
 
268
        removes a complex effect
 
269
        If remove_linked is True and the effect has a linked effect, will remove
 
270
        it from the linked composition
 
271
        """
 
272
        raise NotImplementedError
 
273
 
 
274
    def _makeCondensedList(self):
 
275
        """ makes a condensed list """
 
276
        def condensed_sum(list1, list2):
 
277
            """ returns a condensed list of the two given lists """
 
278
            self.gnlobject.info( "condensed_sum")
 
279
            self.gnlobject.info( "comparing %s with %s" % (list1, list2))
 
280
            if not len(list1):
 
281
                return list2[:]
 
282
            if not len(list2):
 
283
                return list1[:]
 
284
            
 
285
            res = list1[:]
 
286
 
 
287
            # find the objects in list2 that go under list1 and insert them at
 
288
            # the good position in res
 
289
            for obj in list2:
 
290
                # go through res to see if it can go somewhere
 
291
                for pos in range(len(res)):
 
292
                    if obj.start <= res[pos].start:
 
293
                        res.insert(pos, obj)
 
294
                        break
 
295
                if pos == len(res) and obj.start > res[-1].start:
 
296
                    res.append(obj)
 
297
            self.gnlobject.info("returning %s" % res)
 
298
            return res
 
299
                
 
300
            
 
301
        lists = [x[2] for x in self.sources]
 
302
        lists.insert(0, self.transitions)
 
303
        return reduce(condensed_sum, lists)
 
304
 
 
305
    def _updateCondensedList(self):
 
306
        """ updates the condensed list """
 
307
        self.gnlobject.info("_update_condensed_list")
 
308
        # build a condensed list
 
309
        clist = self._makeCondensedList()
 
310
        self.gnlobject.info("clist:%r" % clist)
 
311
        if self.condensed:
 
312
            # compare it to the self.condensed
 
313
            list_changed = False
 
314
##             print "comparing:"
 
315
##             for i in self.condensed:
 
316
##                 print i.gnlobject, i.start, i.duration
 
317
##             print "with"
 
318
##             for i in clist:
 
319
##                 print i.gnlobject, i.start, i.duration
 
320
            if not len(clist) == len(self.condensed):
 
321
                list_changed = True
 
322
            else:
 
323
                for a, b in zip(clist, self.condensed):
 
324
                    if not a == b:
 
325
                        list_changed = True
 
326
                        break
 
327
        else:
 
328
            list_changed = True
 
329
        self.gnlobject.log("list_change : %s" % list_changed)
 
330
        # if it's different or new, set it to self.condensed and emit the signal
 
331
        if list_changed:
 
332
            self.condensed = clist
 
333
            self.emit("condensed-list-changed", self.condensed)
 
334
 
 
335
    # Transitions
 
336
 
 
337
    def addTransition(self, transition, source1, source2, auto_linked=True):
 
338
        """
 
339
        adds a transition between source1 and source2
 
340
        auto_linked : if True will add the brother (if any) of the given transition
 
341
                to the linked composition with the same parameters
 
342
        """
 
343
        # if it overlaps with existing transition, raise exception
 
344
        raise NotImplementedError
 
345
 
 
346
    def moveTransition(self, transition, source1, source2):
 
347
        """ move a transition between source1 and source2 """
 
348
        # if it overlays with existing transition, raise exception
 
349
        raise NotImplementedError
 
350
 
 
351
    def removeTransition(self, transition, reorder_sources=True, remove_linked=True):
 
352
        """
 
353
        removes a transition,
 
354
        If reorder sources is True it puts the sources
 
355
        between which the transition was back one after the other
 
356
        If remove_linked is True and the transition has a linked effect, will remove
 
357
        it from the linked composition
 
358
        """
 
359
        raise NotImplementedError
 
360
 
 
361
    # Sources
 
362
 
 
363
    def _getSourcePosition(self, source):
 
364
        position = 0
 
365
        foundit = False
 
366
        for slist in self.sources:
 
367
            if source in slist[2]:
 
368
                foundit = True
 
369
                break
 
370
            position = position + 1
 
371
        if foundit:
 
372
            return position + 1
 
373
        return 0
 
374
 
 
375
    def _haveGotThisSource(self, source):
 
376
        for slist in self.sources:
 
377
            if source in slist[2]:
 
378
                return True
 
379
        return False
 
380
 
 
381
 
 
382
    def _addSource(self, source, position):
 
383
        """ private version of addSource """
 
384
        def my_add_sorted(sources, object):
 
385
            slist = sources[2]
 
386
            i = 0
 
387
            for item in slist:
 
388
                if item.start > object.start:
 
389
                    break
 
390
                i = i + 1
 
391
            object.gnlobject.set_property("priority", sources[0])
 
392
            slist.insert(i, object)
 
393
            
 
394
        # TODO : add functionnality to add above/under
 
395
        # For the time being it's hardcoded to a single layer
 
396
        position = 1
 
397
 
 
398
        # add it to the correct self.sources[position]
 
399
        my_add_sorted(self.sources[position-1], source)
 
400
        
 
401
        # add it to self.gnlobject
 
402
        self.gnlobject.info("adding %s to our composition" % source.gnlobject)
 
403
        self.gnlobject.add(source.gnlobject)
 
404
 
 
405
        self.gnlobject.info("added source %s" % source.gnlobject)
 
406
        gst.info("%s" % str(self.sources))
 
407
        self.emit('source-added', source)
 
408
 
 
409
        # update the condensed list
 
410
        self._updateCondensedList()
 
411
 
 
412
    def addSource(self, source, position, auto_linked=True):
 
413
        """
 
414
        add a source (with correct start/duration time already set)
 
415
        position : the vertical position
 
416
          _ 0 : insert above all other layers
 
417
          _ n : insert at the given position (1: top row)
 
418
          _ -1 : insert at the bottom, under all sources
 
419
        auto_linked : if True will add the brother (if any) of the given source
 
420
                to the linked composition with the same parameters
 
421
        """
 
422
        self.gnlobject.info("source %s , position:%d, self.sources:%s" %(source, position, self.sources))
 
423
        
 
424
        self._addSource(source, position)
 
425
 
 
426
        # if auto_linked and self.linked, add brother to self.linked with same parameters
 
427
        if auto_linked and self.linked:
 
428
            if source.getBrother():
 
429
                self.linked._addSource(source.brother, position)
 
430
 
 
431
    def insertSourceAfter(self, source, existingsource, push_following=True, auto_linked=True):
 
432
        """
 
433
        inserts a source after the existingsource, pushing the following ones
 
434
        if existingsource is None, it puts the source at the beginning
 
435
        """
 
436
        if existingsource:
 
437
            self.gnlobject.info("insert_source after %s" % existingsource.gnlobject)
 
438
        else:
 
439
            self.gnlobject.info("insert_source at the beginning")
 
440
            
 
441
        # find the time where it's going to be added
 
442
        if not existingsource or not self._haveGotThisSource(existingsource):
 
443
            start = 0
 
444
            position = 1
 
445
            existorder = 0
 
446
        else:
 
447
            start = existingsource.start + existingsource.duration
 
448
            position = self._getSourcePosition(existingsource)
 
449
            existorder = self.sources[position - 1][2].index(existingsource) + 1
 
450
 
 
451
        gst.info("start=%s, position=%d, existorder=%d, sourcelength=%s" % (gst.TIME_ARGS(start),
 
452
                                                                            position,
 
453
                                                                            existorder,
 
454
                                                                            gst.TIME_ARGS(source.factory.length)))
 
455
##         for i in self.sources[position -1][2]:
 
456
##             print i.gnlobject, i.start, i.duration
 
457
        # set the correct start/duration time
 
458
        duration = source.factory.length
 
459
        source.setStartDurationTime(start, duration)
 
460
        
 
461
        # pushing following
 
462
        if push_following and not position in [-1, 0]:
 
463
            #print self.gnlobject, "pushing following", existorder, len(self.sources[position - 1][2])
 
464
            for i in range(existorder, len(self.sources[position - 1][2])):
 
465
                mvsrc = self.sources[position - 1][2][i]
 
466
                self.gnlobject.info("pushing following")
 
467
                #print "run", i, "start", mvsrc.start, "duration", mvsrc.duration
 
468
                # increment self.sources[position - 1][i] by source.factory.length
 
469
                mvsrc.setStartDurationTime(mvsrc.start + source.factory.length)
 
470
        
 
471
        self.addSource(source, position, auto_linked=auto_linked)
 
472
 
 
473
    def appendSource(self, source, position=1, auto_linked=True):
 
474
        """
 
475
        puts a source after all the others
 
476
        """
 
477
        self.gnlobject.info("source:%s" % source.gnlobject)
 
478
        # find the source with the highest duration time on the first layer
 
479
        if self.sources[position - 1]:
 
480
            existingsource = self.sources[position - 1][2][-1]
 
481
        else:
 
482
            existingsource = None
 
483
 
 
484
        self.insertSourceAfter(source, existingsource, push_following=False,
 
485
                               auto_linked=auto_linked)
 
486
 
 
487
    def prependSource(self, source, push_following=True, auto_linked=True):
 
488
        """
 
489
        adds a source to the beginning of the sources
 
490
        """
 
491
        self.gnlobject.info("source:%s" % source.gnlobject)
 
492
        self.insertSourceAfter(source, None, push_following, auto_linked)
 
493
 
 
494
    def moveSource(self, source, newpos, move_linked=True, push_neighbours=True, collapse_neighbours=True):
 
495
        """
 
496
        Moves the source to the new position. The position is the existing source before which to move
 
497
        the source.
 
498
        
 
499
        If move_linked is True and the source has a linked source, the linked source will
 
500
        be moved to the same position.
 
501
        If collapse_neighbours is True, all sources located AFTER the OLD position of the
 
502
        source will be shifted in the past by the duration of the removed source.
 
503
        If push_neighbours is True, then sources located AFTER the NEW position will be shifted
 
504
        forward in time, in order to have enough free space to insert the source.
 
505
        """
 
506
        self.gnlobject.info("source:%s , newpos:%d, move_linked:%s, push_neighbours:%s, collapse_neighbours:%s" % (source,
 
507
                                                                                                                   newpos,
 
508
                                                                                                                   move_linked,
 
509
                                                                                                                   push_neighbours,
 
510
                                                                                                                   collapse_neighbours))
 
511
        sources = self.sources[0][2]
 
512
        oldpos = sources.index(source)
 
513
        if newpos == -1:
 
514
            newpos = len(sources)
 
515
 
 
516
        self.gnlobject.info("source was at position %d in his layer" % oldpos)
 
517
 
 
518
        # if we're not moving, return
 
519
        if (oldpos == newpos):
 
520
            self.gnlobject.warning("source is already at the correct position, not moving")
 
521
            return
 
522
 
 
523
        # 0. Temporarily remove moving source from composition
 
524
        self.gnlobject.log("Setting source priority at maximum [%d]" % self.sources[0][1])
 
525
        source.gnlobject.set_property("priority", self.sources[0][1])
 
526
 
 
527
        # 1. if collapse_neighbours, shift all downstream sources by duration
 
528
        if collapse_neighbours and oldpos != len(sources) - 1:
 
529
            self.gnlobject.log("collapsing all following neighbours after the old position [%d]" % oldpos)
 
530
            for i in range(oldpos + 1, len(sources)):
 
531
                obj = sources[i]
 
532
                self.gnlobject.log("moving source %d %s" % (i, obj))
 
533
                obj.setStartDurationTime(start = (obj.start - source.duration))
 
534
 
 
535
        # 2. if push_neighbours, make sure there's enough room at the new position
 
536
        if push_neighbours and newpos != len(sources):
 
537
            pushmin = source.duration
 
538
            if newpos != 0:
 
539
                pushmin += sources[newpos - 1].start + sources[newpos - 1].duration
 
540
            self.gnlobject.log("We need to make sure sources after newpos are at or after %s" % gst.TIME_ARGS(pushmin))
 
541
            if sources[newpos].start < pushmin:
 
542
                # don't push sources after old position
 
543
                if oldpos > newpos:
 
544
                    stoppos = oldpos
 
545
                else:
 
546
                    stoppos = len(sources)
 
547
                self.gnlobject.log("pushing neighbours between new position [%d] and stop [%d]" % (newpos, stoppos))
 
548
                for i in range(newpos, stoppos):
 
549
                    obj = sources[i]
 
550
                    obj.setStartDurationTime(start = pushmin)
 
551
                    pushmin += obj.duration
 
552
 
 
553
        # 3. move the source
 
554
        newtimepos = 0
 
555
        if newpos:
 
556
            newtimepos += sources[newpos - 1].start + sources[newpos - 1].duration
 
557
        self.gnlobject.log("Setting source start position to %s" % gst.TIME_ARGS(newtimepos))
 
558
        source.setStartDurationTime(start = newtimepos)
 
559
 
 
560
        self.gnlobject.log("Removing source from position [%d] and putting it to position [%d]" % (oldpos, newpos - 1))
 
561
        del sources[oldpos]
 
562
        sources.insert(newpos - 1, source)
 
563
        source.gnlobject.set_property("priority", self.sources[0][0])
 
564
 
 
565
        # 4. same thing for brother
 
566
        # FIXME : TODO
 
567
 
 
568
        # 5. update condensed list
 
569
        self.gnlobject.log("Done moving %s , updating condensed list" % source)
 
570
        self._updateCondensedList()
 
571
 
 
572
    def removeSource(self, source, remove_linked=True, collapse_neighbours=False):
 
573
        """
 
574
        Removes a source.
 
575
        
 
576
        If remove_linked is True and the source has a linked source, will remove
 
577
        it from the linked composition.
 
578
        If collapse_neighbours is True, then all object after the removed source
 
579
        will be shifted in the past by the duration of the removed source.
 
580
        """
 
581
        self.gnlobject.info("source:%s, remove_linked:%s, collapse_neighbours:%s" % (source, remove_linked, collapse_neighbours))
 
582
        sources = self.sources[0]
 
583
 
 
584
        pos = sources[2].index(source)
 
585
        self.gnlobject.info("source was at position %d in his layer" % pos)
 
586
 
 
587
        # actually remove it
 
588
        self.gnlobject.info("Really removing %s from our composition" % source.gnlobject)
 
589
        self.gnlobject.remove(source.gnlobject)
 
590
        del sources[2][pos]
 
591
 
 
592
        # collapse neighbours
 
593
        if collapse_neighbours:
 
594
            self.gnlobject.info("Collapsing neighbours")
 
595
            for i in range(pos, len(sources[2])):
 
596
                obj = sources[2][i]
 
597
                obj.setStartDurationTime(start = (obj.start - source.duration))
 
598
 
 
599
        # if we have a brother
 
600
        if remove_linked and self.linked and self.linked.gnlobject:
 
601
            self.linked.gnlobject.remove(source.linked.gnlobject)
 
602
            self.linked.emit('source-removed', source.linked)
 
603
            self.linked._updateCondensedList()
 
604
 
 
605
        self.emit('source-removed', source)
 
606
        # update the condensed list
 
607
        self._updateCondensedList()
 
608
 
 
609
 
 
610
    def setDefaultSource(self, source):
 
611
        """
 
612
        Adds a default source to the composition.
 
613
        Default sources will be used for gaps within the composition.
 
614
        """
 
615
        if self.defaultSource:
 
616
            self.gnlobject.remove(self.defaultSource)
 
617
        source.props.priority = 2 ** 32 - 1
 
618
        self.gnlobject.add(source)
 
619
        self.defaultSource = source
 
620
 
 
621
    def getDefaultSource(self):
 
622
        """
 
623
        Returns the default source.
 
624
        """
 
625
        return self.defaultSource
 
626
 
 
627
 
 
628
    # AutoSettings methods
 
629
 
 
630
    def _autoVideoSettings(self):
 
631
        # return a ExportSettings in which all videos of the composition
 
632
        # will be able to be exported without loss
 
633
        biggest = None
 
634
        # FIXME : we suppose we only have only source layer !!!
 
635
        # FIXME : we in fact return the first file's settings
 
636
        for source in self.sources[0][2]:
 
637
            if not biggest:
 
638
                biggest = source.getExportSettings()
 
639
            else:
 
640
                sets = source.getExportSettings()
 
641
                for prop in ['videowidth', 'videoheight',
 
642
                             'videopar', 'videorate']:
 
643
                    if sets.__getattribute__(prop) != biggest.__getattribute__(prop):
 
644
                        return biggest
 
645
        return biggest
 
646
 
 
647
    def _autoAudioSettings(self):
 
648
        # return an ExportSettings in which all audio source of the composition
 
649
        # will be able to be exported without (too much) loss
 
650
        biggest = None
 
651
        # FIXME : we suppose we only have only source layer !!!
 
652
        # FIXME : we in fact return the first file's settings
 
653
        for source in self.sources[0][2]:
 
654
            if not biggest:
 
655
                biggest = source.getExportSettings()
 
656
            else:
 
657
                sets = source.getExportSettings()
 
658
                for prop in ['audiorate', 'audiochannels', 'audiodepth']:
 
659
                    if sets.__getattribute__(prop) != biggest.__getattribute__(prop):
 
660
                        return biggest
 
661
        return biggest
 
662
 
 
663
 
 
664
    def _getAutoSettings(self):
 
665
        gst.log("len(self) : %d" % len(self))
 
666
        if not len(self):
 
667
            return None
 
668
        if len(self) == 1:
 
669
            # return the settings of our only source
 
670
            return self.sources[0][2][0].getExportSettings()
 
671
        else:
 
672
            if self.media_type == MEDIA_TYPE_AUDIO:
 
673
                return self._autoAudioSettings()
 
674
            else:
 
675
                return self._autoVideoSettings()