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

« back to all changes in this revision

Viewing changes to pitivi/timeline/track.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/timeline/timeline.py
4
 
#
5
 
# Copyright (c) 2009, Alessandro Decina <alessandro.decina@collabora.co.uk>
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
 
import gst
23
 
import gobject
24
 
 
25
 
from pitivi.signalinterface import Signallable
26
 
from pitivi.utils import get_controllable_properties, getPreviousObject, \
27
 
        getNextObject, start_insort_right, between
28
 
from pitivi.log.loggable import Loggable
29
 
from pitivi.stream import VideoStream, AudioStream
30
 
from pitivi.factories.test import VideoTestSourceFactory, \
31
 
        AudioTestSourceFactory
32
 
from pitivi.elements.mixer import SmartAdderBin, SmartVideomixerBin
33
 
from pitivi.timeline.gap import Gap
34
 
 
35
 
 
36
 
class TrackError(Exception):
37
 
    pass
38
 
 
39
 
 
40
 
class Keyframe(Signallable):
41
 
 
42
 
    """Represents a single point on an interpolation curve"""
43
 
 
44
 
    __signals__ = {
45
 
        "value-changed": ['value'],
46
 
        "time-changed": ['time'],
47
 
        "mode-changed": ['mode'],
48
 
    }
49
 
 
50
 
    def __init__(self, parent):
51
 
        self.parent = parent
52
 
 
53
 
## Properties
54
 
 
55
 
    _mode = gst.INTERPOLATE_LINEAR
56
 
 
57
 
    def setMode(self, mode):
58
 
        if self.parent:
59
 
            self.parent.setKeyframeMode(self, mode)
60
 
        else:
61
 
            self.setObjectMode(mode)
62
 
 
63
 
    def setObjectMode(self, mode):
64
 
        self._mode = mode
65
 
        self.emit("mode-changed", mode)
66
 
 
67
 
    def getMode(self):
68
 
        return self._mode
69
 
 
70
 
    mode = property(getMode, setMode)
71
 
 
72
 
    _time = 0
73
 
 
74
 
    def setTime(self, time):
75
 
        if self.parent:
76
 
            self.parent.setKeyframeTime(self, time)
77
 
        else:
78
 
            self.setObjectTime(time)
79
 
 
80
 
    def setObjectTime(self, time):
81
 
        self._time = time
82
 
        self.emit("time-changed", time)
83
 
 
84
 
    def getTime(self):
85
 
        return self._time
86
 
 
87
 
    time = property(getTime, setTime)
88
 
 
89
 
    _value = None
90
 
 
91
 
    def setValue(self, value):
92
 
        if self.parent:
93
 
            self.parent.setKeyframeValue(self, value)
94
 
        else:
95
 
            self.setObjectValue(value)
96
 
 
97
 
    def setObjectValue(self, value):
98
 
        self._value = value
99
 
        self.emit("value-changed", value)
100
 
 
101
 
    def getValue(self):
102
 
        return self._value
103
 
 
104
 
    value = property(getValue, setValue)
105
 
 
106
 
    def __cmp__(self, other):
107
 
        if other:
108
 
            return cmp(self.time, other.time)
109
 
        return self
110
 
 
111
 
 
112
 
class FixedKeyframe(Keyframe):
113
 
 
114
 
    def setTime(self, time):
115
 
        pass
116
 
 
117
 
    time = property(Keyframe.getTime, setTime)
118
 
 
119
 
 
120
 
class Interpolator(Signallable, Loggable):
121
 
 
122
 
    """The bridge between the gstreamer dynamic property API and pitivi track
123
 
    objects.
124
 
 
125
 
    * creates gnl.Controller() objects
126
 
    * binds controller to track object's gnlobject
127
 
    * allows client code to manipulate the interpolation curve by adding,
128
 
      removing, and mutating discrete keyframe objects
129
 
 
130
 
    There are two special control points: the start and end points, which are
131
 
    "fixed" to the start and end of the clip in the timeline.
132
 
 
133
 
    Timestamps given are assumed to be relative to the start of the clip. This
134
 
    seems to be the normal behavior when the element being controlled is
135
 
    internal to the gnlsource or gnloperation.
136
 
    """
137
 
 
138
 
    __signals__ = {
139
 
        'keyframe-added': ['keyframe'],
140
 
        'keyframe-removed': ['keyframe', 'old_value'],
141
 
        'keyframe-moved': ['keyframe', 'old_value'],
142
 
    }
143
 
 
144
 
    def __init__(self, trackobject, element, prop, minimum=None, maximum=None,
145
 
        format=None):
146
 
        Loggable.__init__(self)
147
 
        self.debug("track:%r, element:%r, property:%r", trackobject, element, prop)
148
 
        self._keyframes = []
149
 
        self.trackobject = trackobject
150
 
 
151
 
        if minimum is None:
152
 
            minimum = prop.minimum
153
 
        if maximum is None:
154
 
            maximum = prop.maximum
155
 
        assert not ((minimum is None) or (maximum is None))
156
 
        self.lower = minimum
157
 
        self.upper = maximum
158
 
        self.range = maximum - minimum
159
 
 
160
 
        # FIXME: don't necessarily want to create separate controllers for
161
 
        # each Interpolator. We should instead create a ControlSource for each
162
 
        # element. We can't do this until the new controller interface is
163
 
        # exposed in gst-python.
164
 
        self.attachToElementProperty(prop, element)
165
 
        self._default = self._element.get_property(prop.name)
166
 
        self.start = FixedKeyframe(self)
167
 
        self.end = FixedKeyframe(self)
168
 
        self.start.value = self._default
169
 
        self.start.setObjectTime(trackobject.in_point)
170
 
        self._keyframeTimeValueChanged(self.start, 0, self.start.value)
171
 
        self.end.value = self._default
172
 
        if trackobject.in_point == trackobject.out_point:
173
 
            self.end.setObjectTime(trackobject.in_point + 1)
174
 
        else:
175
 
            self.end.setObjectTime(trackobject.out_point)
176
 
        self._keyframeTimeValueChanged(self.end, self.end.time, self.end.value)
177
 
        self.format = format if format else str
178
 
 
179
 
    def attachToElementProperty(self, prop, element):
180
 
        self._element = element
181
 
        self._property = prop
182
 
        self.debug("Creating a GstController for element %r and property %s",
183
 
            self._element, prop.name)
184
 
        self._controller = gst.Controller(self._element, prop.name)
185
 
        self._controller.set_interpolation_mode(prop.name, gst.INTERPOLATE_LINEAR)
186
 
 
187
 
    def newKeyframe(self, time_or_keyframe, value=None, mode=None):
188
 
        """add a new keyframe at the specified time, optionally with specified
189
 
        value and specified mode. If not specified, these will be computed so
190
 
        that the new keyframe likes on the existing curve at that timestampi
191
 
 
192
 
        returns: the keyframe object"""
193
 
        if time_or_keyframe is self.start or time_or_keyframe is self.end:
194
 
            self.debug("We don't want to add the starting and end keyframes to"
195
 
                       " our list of controlled keyframes")
196
 
            return
197
 
 
198
 
        if isinstance(time_or_keyframe, Keyframe):
199
 
            keyframe = time_or_keyframe
200
 
        else:
201
 
            if value is None:
202
 
                value = self._controller.get(self._property.name,
203
 
                    time_or_keyframe)
204
 
            if mode is None:
205
 
                # FIXME: Controller.get_interpolation_mode is not wrapped in
206
 
                # gst-python, so for now we assume the default is linear.
207
 
                # Use the following code to get the current mode when this method becomes
208
 
                # available.
209
 
                # mode = self._controller.get_interpolation_mode()
210
 
                mode = gst.INTERPOLATE_LINEAR
211
 
 
212
 
            keyframe = Keyframe(self)
213
 
            keyframe._time = time_or_keyframe
214
 
            keyframe._value = value
215
 
            keyframe._mode = mode
216
 
 
217
 
        self.debug("time:%s, value:%r, mode:%r",
218
 
                   gst.TIME_ARGS(keyframe.time), keyframe.value, keyframe.mode)
219
 
 
220
 
        self._keyframes.append(keyframe)
221
 
        self._keyframes.sort()
222
 
 
223
 
        self._controller.set(self._property.name, keyframe.time, keyframe.value)
224
 
 
225
 
        self.emit("keyframe-added", keyframe)
226
 
 
227
 
        return keyframe
228
 
 
229
 
    def removeKeyframe(self, keyframe):
230
 
        if keyframe not in self._keyframes:
231
 
            self.debug("This keyframe doesn't belong to me")
232
 
            return
233
 
 
234
 
        old_value = self._controller.get(self._property.name, keyframe.time)
235
 
        self._controller.unset(self._property.name, keyframe.time)
236
 
        if keyframe is not self.start and keyframe is not self.end:
237
 
            self._keyframes.remove(keyframe)
238
 
            self.emit("keyframe-removed", keyframe, old_value)
239
 
 
240
 
    def setKeyframeMode(self, kf, mode):
241
 
        # FIXME: currently InterpolationSourceControllers only support a
242
 
        # single mode. Suporting per-keyframe modes would require implementing
243
 
        # a custom interplation source controller.
244
 
        # For now, whenever one keyframe's mode changes, we'll set the mode
245
 
        # globally.
246
 
        for keyframe in self.keyframes:
247
 
            keyframe.setObjectMode(mode)
248
 
        self._controller.set_interpolation_mode(self._property.name, mode)
249
 
 
250
 
    def setKeyframeTime(self, kf, time):
251
 
        time = max(self.start.time, min(self.end.time, time))
252
 
        self._keyframeTimeValueChanged(kf, time, kf.value)
253
 
        kf.setObjectTime(time)
254
 
 
255
 
    def setKeyframeValue(self, kf, value):
256
 
        value = max(self.lower, min(self.upper, value))
257
 
        self._keyframeTimeValueChanged(kf, kf.time, value)
258
 
        kf.setObjectValue(value)
259
 
 
260
 
    def _keyframeTimeValueChanged(self, kf, ptime, value):
261
 
        self.debug("kf.time:%s, ptime:%s, value:%r",
262
 
                   gst.TIME_ARGS(kf.time),
263
 
                   gst.TIME_ARGS(ptime), value)
264
 
        old_value = self._controller.get(self._property.name, ptime)
265
 
        self._controller.set(self._property.name, ptime, value)
266
 
        if kf.time != ptime:
267
 
            self._controller.unset(self._property.name, kf.time)
268
 
        self.emit("keyframe-moved", kf, old_value)
269
 
 
270
 
    def getKeyframes(self):
271
 
        yield self.start
272
 
        for kf in self._keyframes:
273
 
            yield kf
274
 
        yield self.end
275
 
 
276
 
    def getInteriorKeyframes(self):
277
 
        """Same as above but does not include start, or end points"""
278
 
        for kf in self._keyframes:
279
 
            yield kf
280
 
 
281
 
    def getVisibleKeyframes(self):
282
 
        """Return start, end and any keyframes included in between"""
283
 
        yield self.start
284
 
        start_time = self.start.time
285
 
        end_time = self.end.time
286
 
        for kf in sorted(self._keyframes):
287
 
            if between(start_time, kf.time, end_time):
288
 
                yield kf
289
 
        yield self.end
290
 
 
291
 
    def updateMediaStart(self, start):
292
 
        self._keyframeTimeValueChanged(self.start, start, self.start.value)
293
 
        self.start.setObjectTime(start)
294
 
 
295
 
    def updateMediaStop(self, stop):
296
 
        self._keyframeTimeValueChanged(self.end, stop, self.end.value)
297
 
        self.end.setObjectTime(stop)
298
 
 
299
 
    def valueAt(self, time):
300
 
        return self._controller.get(self._property.name, time)
301
 
 
302
 
    def formatValue(self, value):
303
 
        return self.format(value)
304
 
 
305
 
    keyframes = property(getKeyframes)
306
 
 
307
 
 
308
 
class TrackObject(Signallable, Loggable):
309
 
 
310
 
    __signals__ = {
311
 
        'start-changed': ['start'],
312
 
        'duration-changed': ['duration'],
313
 
        'in-point-changed': ['in-point'],
314
 
        'out-point-changed': ['out-point'],
315
 
        'media-duration-changed': ['media-duration'],
316
 
        'priority-changed': ['priority'],
317
 
        'selected-changed': ['state'],
318
 
        'stagger-changed': ['stagger'],
319
 
        'active-changed': ['active'],
320
 
    }
321
 
 
322
 
    def __init__(self, factory, stream, start=0,
323
 
            duration=0, in_point=0,
324
 
            media_duration=0, priority=0):
325
 
        Loggable.__init__(self)
326
 
        self.debug("factory:%r", factory)
327
 
        self.factory = factory
328
 
        self.stream = stream
329
 
        self.stream_type = type(stream)
330
 
        self.track = None
331
 
        self.timeline_object = None
332
 
        self.interpolators = {}
333
 
        self._rebuild_interpolators = False
334
 
        self._public_priority = priority
335
 
        self._position = 0
336
 
        self._stagger = 0
337
 
        self.gnl_object = obj = self._makeGnlObject()
338
 
        self.keyframes = []
339
 
 
340
 
        if start != 0:
341
 
            obj.props.start = start
342
 
 
343
 
        if duration == 0:
344
 
            if factory.duration != gst.CLOCK_TIME_NONE:
345
 
                duration = factory.duration
346
 
            elif factory.default_duration != gst.CLOCK_TIME_NONE:
347
 
                duration = factory.default_duration
348
 
 
349
 
        obj.props.duration = duration
350
 
 
351
 
        obj.props.media_start = in_point
352
 
        if media_duration != 0:
353
 
            obj.props.media_duration = media_duration
354
 
        else:
355
 
            obj.props.media_duration = duration
356
 
 
357
 
        obj.props.priority = priority
358
 
 
359
 
        self._connectToSignals(obj)
360
 
        self._updatePriority(self._public_priority)
361
 
 
362
 
    def getInterpolator(self, property_name):
363
 
        self._maybeBuildInterpolators()
364
 
 
365
 
        try:
366
 
            return self.interpolators[property_name][1]
367
 
        except KeyError:
368
 
            raise TrackError("no interpolator for '%s'" % property_name)
369
 
 
370
 
    def getInterpolators(self):
371
 
        self._maybeBuildInterpolators()
372
 
        return self.interpolators
373
 
 
374
 
    def _maybeBuildInterpolators(self):
375
 
        if not self._rebuild_interpolators:
376
 
            return
377
 
 
378
 
        if not list(self.gnl_object.elements()):
379
 
            raise TrackError("makeBin hasn't been called yet")
380
 
 
381
 
        self._rebuild_interpolators = False
382
 
 
383
 
        factory_properties = self.factory.getInterpolatedProperties(self.stream)
384
 
 
385
 
        old_interpolators = self.interpolators
386
 
        self.interpolators = {}
387
 
        for gst_object, gst_object_property in \
388
 
                get_controllable_properties(self.gnl_object):
389
 
            prop_name = gst_object_property.name
390
 
            if prop_name not in factory_properties:
391
 
                continue
392
 
 
393
 
            try:
394
 
                interpolator = old_interpolators[prop_name][1]
395
 
            except KeyError:
396
 
                if factory_properties[prop_name]:
397
 
                    lower, upper, formatstr = factory_properties[prop_name]
398
 
                else:
399
 
                    lower, upper, formatstr = None, None, None
400
 
                interpolator = Interpolator(self, gst_object,
401
 
                    gst_object_property, lower, upper, formatstr)
402
 
            else:
403
 
                # remove and add again the keyframes so they are set on the
404
 
                # current controller
405
 
                keyframes = list(interpolator.keyframes)
406
 
                for keyframe in keyframes:
407
 
                    interpolator.removeKeyframe(keyframe)
408
 
 
409
 
                interpolator.attachToElementProperty(gst_object_property,
410
 
                        gst_object)
411
 
                interpolator.updateMediaStop(self.out_point)
412
 
 
413
 
                for keyframe in keyframes:
414
 
                    interpolator.newKeyframe(keyframe)
415
 
 
416
 
            self.interpolators[gst_object_property.name] = \
417
 
                    (gst_object_property, interpolator)
418
 
 
419
 
    def release(self):
420
 
        self._disconnectFromSignals()
421
 
        self.releaseBin()
422
 
        self.gnl_object = None
423
 
        self.factory = None
424
 
 
425
 
    def copy(self):
426
 
        cls = self.__class__
427
 
        other = cls(self.factory, self.stream, start=self.start,
428
 
            duration=self.duration, in_point=self.in_point,
429
 
            media_duration=self.media_duration, priority=self.priority)
430
 
 
431
 
        if self.track is not None:
432
 
            self.track.addTrackObject(other)
433
 
            other.gnl_object.set_property("active",
434
 
                                          self.gnl_object.get_property("active"))
435
 
 
436
 
        interpolators = self.getInterpolators()
437
 
        for property, interpolator in interpolators.itervalues():
438
 
            other_interpolator = other.getInterpolator(property.name)
439
 
            other_interpolator.start.value = interpolator.start.value
440
 
            other_interpolator.start.mode = interpolator.start.mode
441
 
            other_interpolator.end.value = interpolator.end.value
442
 
            other_interpolator.end.mode = interpolator.end.mode
443
 
            for kf in interpolator.getInteriorKeyframes():
444
 
                other_interpolator.newKeyframe(kf.time,
445
 
                    kf.value,
446
 
                    kf.mode)
447
 
 
448
 
        return other
449
 
 
450
 
    def snapStartDurationTime(self, *args):
451
 
        return
452
 
 
453
 
    def _getStart(self):
454
 
        return self.gnl_object.props.start
455
 
 
456
 
    def setStart(self, position, snap=False):
457
 
        if self.timeline_object is not None:
458
 
            self.timeline_object.setStart(position, snap)
459
 
        else:
460
 
            if snap:
461
 
                raise TrackError()
462
 
 
463
 
            self.setObjectStart(position)
464
 
 
465
 
    def setObjectStart(self, position):
466
 
        if self.gnl_object.props.start != position:
467
 
            self.gnl_object.props.start = position
468
 
 
469
 
    start = property(_getStart, setStart)
470
 
 
471
 
    def _getActive(self):
472
 
        return self.gnl_object.props.active
473
 
 
474
 
    def setActive(self, active):
475
 
        self.gnl_object.props.active = active
476
 
 
477
 
    active = property(_getActive, setActive)
478
 
 
479
 
    def _getDuration(self):
480
 
        return self.gnl_object.props.duration
481
 
 
482
 
    def setDuration(self, position, snap=False):
483
 
        if self.timeline_object is not None:
484
 
            self.timeline_object.setDuration(position, snap)
485
 
        else:
486
 
            if snap:
487
 
                raise TrackError()
488
 
 
489
 
            self.setObjectDuration(position)
490
 
 
491
 
    def setObjectDuration(self, position):
492
 
        if self.gnl_object.props.duration != position:
493
 
            self.gnl_object.props.duration = position
494
 
 
495
 
    duration = property(_getDuration, setDuration)
496
 
 
497
 
    def _getInPoint(self):
498
 
        return self.gnl_object.props.media_start
499
 
 
500
 
    def setInPoint(self, position, snap=False):
501
 
        if self.timeline_object is not None:
502
 
 
503
 
            self.timeline_object.setInPoint(position, snap)
504
 
        else:
505
 
            self.setObjectInPoint(position)
506
 
 
507
 
    def setObjectInPoint(self, value):
508
 
        if self.gnl_object.props.media_start != value:
509
 
            self.gnl_object.props.media_start = value
510
 
 
511
 
    in_point = property(_getInPoint, setInPoint)
512
 
 
513
 
    def _getOutPoint(self):
514
 
        return self.gnl_object.props.media_stop
515
 
 
516
 
    out_point = property(_getOutPoint)
517
 
 
518
 
    def _getMediaDuration(self):
519
 
        return self.gnl_object.props.media_duration
520
 
 
521
 
    def setMediaDuration(self, position, snap=False):
522
 
        if self.timeline_object is not None:
523
 
            self.timeline_object.setMediaDuration(position, snap)
524
 
        else:
525
 
            self.setObjectMediaDuration(position)
526
 
 
527
 
    def setObjectMediaDuration(self, position):
528
 
        if self.gnl_object.props.media_duration != position:
529
 
            self.gnl_object.props.media_duration = position
530
 
 
531
 
    media_duration = property(_getMediaDuration, setMediaDuration)
532
 
 
533
 
    def _getRate(self):
534
 
        return self.gnl_object.props.rate
535
 
 
536
 
    rate = property(_getRate)
537
 
 
538
 
    def _getPriority(self):
539
 
        return self._public_priority
540
 
 
541
 
    def setPriority(self, priority):
542
 
        if self.timeline_object is not None:
543
 
            self.timeline_object.setPriority(priority)
544
 
        else:
545
 
            self.setObjectPriority(priority)
546
 
 
547
 
    def setObjectPriority(self, priority):
548
 
        if priority != self._public_priority:
549
 
            self._updatePriority(priority)
550
 
 
551
 
    def _getTruePriority(self, priority):
552
 
        """ calculate the priority the contained gnlobject should have """
553
 
        if self.stream_type is VideoStream:
554
 
            return 3 + self._stagger + (3 * priority)
555
 
        elif self.stream_type is AudioStream:
556
 
            return 3 + (2 * self._stagger) + (4 * priority)
557
 
 
558
 
    def _updatePriority(self, priority):
559
 
        true_priority = self._getTruePriority(priority)
560
 
 
561
 
        if self.gnl_object.props.priority != true_priority:
562
 
            self.gnl_object.props.priority = true_priority
563
 
 
564
 
        self.debug("New priority: %r", self.gnl_object.props.priority)
565
 
 
566
 
    priority = property(_getPriority, setPriority)
567
 
 
568
 
    def _getStagger(self):
569
 
        return self._stagger
570
 
 
571
 
    stagger = property(_getStagger)
572
 
 
573
 
    def trimStart(self, position, snap=False):
574
 
        if self.timeline_object is not None:
575
 
            self.timeline_object.trimStart(position, snap)
576
 
        else:
577
 
            self.trimObjectStart(position)
578
 
 
579
 
    def _getTrimInpointAndPosition(self, position):
580
 
        # clamp position to be inside the object
581
 
        position = max(self.start - self.in_point, position)
582
 
        position = min(position, self.start + self.duration)
583
 
 
584
 
        delta = position - self.start
585
 
        in_point = self.in_point
586
 
        in_point += delta
587
 
 
588
 
        return in_point, position
589
 
 
590
 
    def trimObjectStart(self, position):
591
 
        in_point, position = self._getTrimInpointAndPosition(position)
592
 
        new_duration = max(0, self.start + self.duration - position)
593
 
 
594
 
        self.setObjectStart(position)
595
 
        self.setObjectDuration(new_duration)
596
 
        self.setObjectInPoint(in_point)
597
 
        self.setObjectMediaDuration(new_duration)
598
 
 
599
 
    def split(self, position, snap=False):
600
 
        if self.timeline_object is not None:
601
 
            return self.timeline_object.split(position, snap)
602
 
        else:
603
 
            return self.splitObject(position)
604
 
 
605
 
    def splitObject(self, position):
606
 
        start = self.gnl_object.props.start
607
 
        duration = self.gnl_object.props.duration
608
 
        in_point = self.gnl_object.props.media_start
609
 
        if position <= start or position >= start + duration:
610
 
            raise TrackError("can't split at position %s" % gst.TIME_ARGS(position))
611
 
 
612
 
        other = self.copy()
613
 
 
614
 
        # update interpolators
615
 
        for prop, i in self.interpolators.itervalues():
616
 
            value = i.valueAt(position)
617
 
            i.end.setValue(value)
618
 
            keyframes = i.getInteriorKeyframes()
619
 
            duplicates = []
620
 
            for kf in keyframes:
621
 
                if kf.getTime() >= (position - start + in_point):
622
 
                    duplicates.append(kf)
623
 
            for kf in duplicates:
624
 
                i.removeKeyframe(kf)
625
 
 
626
 
        for prop, i in other.interpolators.itervalues():
627
 
            value = i.valueAt(position)
628
 
            i.start.setValue(value)
629
 
            keyframes = i.getInteriorKeyframes()
630
 
            duplicates = []
631
 
            for kf in keyframes:
632
 
                if kf.getTime() <= (position - start + in_point):
633
 
                    duplicates.append(kf)
634
 
            for kf in duplicates:
635
 
                i.removeKeyframe(kf)
636
 
 
637
 
        other.trimObjectStart(position)
638
 
        self.setObjectDuration(position - self.gnl_object.props.start)
639
 
        self.setObjectMediaDuration(position - self.gnl_object.props.start)
640
 
        return other
641
 
 
642
 
    # True when the track object is part of the timeline's current selection
643
 
    __selected = False
644
 
 
645
 
    def _getSelected(self):
646
 
        return self.__selected
647
 
 
648
 
    def setObjectSelected(self, state):
649
 
        """Sets the object's selected property to the specified value. This
650
 
        should only be called by the track object's parent timeline object."""
651
 
        self.__selected = state
652
 
        self.emit("selected-changed", state)
653
 
 
654
 
    selected = property(_getSelected)
655
 
 
656
 
    def makeBin(self):
657
 
        if self.stream is None:
658
 
            raise TrackError()
659
 
        if self.gnl_object is None:
660
 
            raise TrackError()
661
 
 
662
 
        bin = self.factory.makeBin(self.stream)
663
 
        self.gnl_object.add(bin)
664
 
        self._rebuild_interpolators = True
665
 
        self._maybeBuildInterpolators()
666
 
 
667
 
    def releaseBin(self):
668
 
        for bin in list(self.gnl_object.elements()):
669
 
            self.gnl_object.remove(bin)
670
 
            bin.set_state(gst.STATE_NULL)
671
 
            self.factory.releaseBin(bin)
672
 
        self._rebuild_interpolators = True
673
 
 
674
 
    def _notifyStartCb(self, obj, pspec):
675
 
        self.emit('start-changed', obj.props.start)
676
 
 
677
 
    def _notifyDurationCb(self, obj, pspec):
678
 
        self.emit('duration-changed', obj.props.duration)
679
 
 
680
 
    def _notifyMediaStartCb(self, obj, pspec):
681
 
        start = obj.props.media_start
682
 
        self.emit('in-point-changed', start)
683
 
        for p, i in self.interpolators.itervalues():
684
 
            i.updateMediaStart(start)
685
 
 
686
 
    def _notifyMediaDurationCb(self, obj, pspec):
687
 
        self.emit('media-duration-changed', obj.props.media_duration)
688
 
 
689
 
    def _notifyMediaStopCb(self, obj, pspec):
690
 
        stop = obj.props.media_stop
691
 
        self.emit('out-point-changed', stop)
692
 
        for p, i in self.interpolators.itervalues():
693
 
            i.updateMediaStop(stop)
694
 
 
695
 
    def _notifyPriorityCb(self, obj, pspec):
696
 
        if self.stream_type is VideoStream:
697
 
            true_priority = obj.props.priority
698
 
            public_priority = (true_priority - 2 - self._stagger) // 3
699
 
        elif self.stream_type is AudioStream:
700
 
            true_priority = obj.props.priority
701
 
            public_priority = (true_priority - 2 - (2 * self._stagger)) // 4
702
 
        if self._public_priority != public_priority:
703
 
            self._public_priority = public_priority
704
 
            self.emit('priority-changed', public_priority)
705
 
 
706
 
    def _notifyActiveCb(self, obj, pspec):
707
 
        self.emit('active-changed', obj.props.active)
708
 
 
709
 
    def _connectToSignals(self, gnl_object):
710
 
        gnl_object.connect('notify::start', self._notifyStartCb)
711
 
        gnl_object.connect('notify::duration', self._notifyDurationCb)
712
 
        gnl_object.connect('notify::media-start', self._notifyMediaStartCb)
713
 
        gnl_object.connect('notify::media-duration',
714
 
                self._notifyMediaDurationCb)
715
 
        gnl_object.connect('notify::media-stop',
716
 
                self._notifyMediaStopCb)
717
 
        gnl_object.connect('notify::priority',
718
 
                self._notifyPriorityCb)
719
 
        gnl_object.connect('notify::active',
720
 
                self._notifyActiveCb)
721
 
 
722
 
    def _disconnectFromSignals(self):
723
 
        if self.gnl_object:
724
 
            self.gnl_object.disconnect_by_func(self._notifyStartCb)
725
 
            self.gnl_object.disconnect_by_func(self._notifyDurationCb)
726
 
            self.gnl_object.disconnect_by_func(self._notifyMediaStartCb)
727
 
            self.gnl_object.disconnect_by_func(self._notifyMediaDurationCb)
728
 
            self.gnl_object.disconnect_by_func(self._notifyMediaStopCb)
729
 
            self.gnl_object.disconnect_by_func(self._notifyPriorityCb)
730
 
 
731
 
    def _makeGnlObject(self):
732
 
        raise NotImplementedError()
733
 
 
734
 
    def updatePosition(self, position):
735
 
        if position != self._position:
736
 
            self._position = position
737
 
            self._stagger = position & 1
738
 
            self._updatePriority(self._public_priority)
739
 
            self.emit("stagger-changed", self._stagger)
740
 
 
741
 
 
742
 
class SourceTrackObject(TrackObject):
743
 
 
744
 
    numobjs = 0
745
 
 
746
 
    def _makeGnlObject(self):
747
 
        source = gst.element_factory_make('gnlsource',
748
 
            "gnlsource: " + self.factory.__class__.__name__ +
749
 
            str(SourceTrackObject.numobjs))
750
 
        SourceTrackObject.numobjs += 1
751
 
        return source
752
 
 
753
 
 
754
 
class TrackEffect(TrackObject):
755
 
 
756
 
    numobjs = 0
757
 
 
758
 
    def _makeGnlObject(self):
759
 
        effect = gst.element_factory_make('gnloperation',
760
 
            "gnloperation: " + self.factory.__class__.__name__ +
761
 
            str(TrackEffect.numobjs))
762
 
        TrackEffect.numobjs += 1
763
 
        return effect
764
 
 
765
 
    def _getTrimInpointAndPosition(self, position):
766
 
        # Effect inpoint is meaningless, and we can resize theme
767
 
        # as needed
768
 
        return 0, position
769
 
 
770
 
    def copy(self):
771
 
        other = TrackObject.copy(self)
772
 
 
773
 
        if self.track is not None:
774
 
            element = self.getElement()
775
 
            new_element = other.getElement()
776
 
            for prop in gobject.list_properties(element):
777
 
                value = element.get_property(prop.name)
778
 
                if value != prop.default_value:
779
 
                    new_element.set_property(prop.name, value)
780
 
 
781
 
        return other
782
 
 
783
 
    def _getTruePriority(self, priority):
784
 
        """
785
 
        The priority of an effect should always be higher than the priority
786
 
        of the track it is applied to. Those priority are affected when we
787
 
        add a TrackObject to timeline
788
 
        """
789
 
        if self.stream_type is VideoStream:
790
 
            return 2 + self._stagger + (3 * priority)
791
 
        elif self.stream_type is AudioStream:
792
 
            return  2 + (2 * self._stagger) + (4 * priority)
793
 
 
794
 
    def getElement(self):
795
 
        """
796
 
        Permit to get the gst.Element inside the gnl_object that correspond
797
 
        to the track factory
798
 
        """
799
 
        effect_bin = list(self.gnl_object.elements())[0]
800
 
        return effect_bin.get_by_name("effect")
801
 
 
802
 
 
803
 
class Transition(Signallable):
804
 
 
805
 
    __signals__ = {
806
 
        "start-changed": ["start"],
807
 
        "duration-changed": ["duration"],
808
 
        "priority-changed": ["priority"],
809
 
    }
810
 
 
811
 
    def __init__(self, a, b):
812
 
        self.a = a
813
 
        self.b = b
814
 
        self.start = 0
815
 
        self.duration = 0
816
 
        self.priority = 0
817
 
        self._makeGnlObject()
818
 
        self._connectToTrackObjects(a, b)
819
 
 
820
 
    def _makeGnlObject(self):
821
 
        pass
822
 
 
823
 
    def _connectToTrackObjects(self, a, b):
824
 
        a.connect("start-changed", self._updateStartDuration)
825
 
        a.connect("duration-changed", self._updateStartDuration)
826
 
        a.connect("priority-changed", self._updatePriority)
827
 
        b.connect("start-changed", self._updateStartDuration)
828
 
        b.connect("priority-changed", self._updatePriority)
829
 
        a.connect("stagger-changed", self._staggerChanged)
830
 
        b.connect("stagger-changed", self._staggerChanged)
831
 
        self._updateStartDuration()
832
 
        self._updatePriority()
833
 
 
834
 
    def _updateStartDuration(self, *unused):
835
 
        start = self.b.start
836
 
        end = self.a.start + self.a.duration
837
 
        duration = max(0, end - start)
838
 
 
839
 
        if start != self.start:
840
 
            self._updateOperationStart(start)
841
 
            self.start = start
842
 
            self.emit("start-changed", start)
843
 
 
844
 
        if duration != self.duration:
845
 
            self._updateOperationDuration(duration)
846
 
            self.duration = duration
847
 
            self.emit("duration-changed", duration)
848
 
 
849
 
        self._updateController()
850
 
 
851
 
    def _updatePriority(self, *unused):
852
 
        if self.a.priority == self.b.priority:
853
 
            priority = self.a.priority
854
 
            self._updateOperationPriority(priority)
855
 
            self.priority = priority
856
 
            self.emit("priority-changed", priority)
857
 
 
858
 
    def _staggerChanged(self, *unused):
859
 
        self._updateController()
860
 
 
861
 
    def _updateController(self):
862
 
        pass
863
 
 
864
 
    def _updateOperationStart(self, start):
865
 
        pass
866
 
 
867
 
    def _updateOperationDuration(self, duration):
868
 
        pass
869
 
 
870
 
    def _updateOperationPriority(self, priority):
871
 
        pass
872
 
 
873
 
    def addThyselfToComposition(self, composition):
874
 
        pass
875
 
 
876
 
    def removeThyselfFromComposition(self, composition):
877
 
        pass
878
 
 
879
 
 
880
 
class VideoTransition(Transition):
881
 
 
882
 
    caps = gst.Caps("video/x-raw-yuv,format=(fourcc)AYUV")
883
 
 
884
 
    def _makeGnlObject(self):
885
 
        trans = gst.element_factory_make("alpha")
886
 
        self.controller = gst.Controller(trans, "alpha")
887
 
        self.controller.set_interpolation_mode("alpha", gst.INTERPOLATE_LINEAR)
888
 
 
889
 
        self.operation = gst.element_factory_make("gnloperation")
890
 
        self.operation.add(trans)
891
 
        self.operation.props.media_start = 0
892
 
        self.operation.props.caps = self.caps
893
 
 
894
 
    def addThyselfToComposition(self, composition):
895
 
        composition.add(self.operation)
896
 
 
897
 
    def removeThyselfFromComposition(self, composition):
898
 
        composition.remove(self.operation)
899
 
        self.operation.set_state(gst.STATE_NULL)
900
 
 
901
 
    def _updateOperationStart(self, start):
902
 
        self.operation.props.start = start
903
 
 
904
 
    def _updateOperationDuration(self, duration):
905
 
        self.operation.props.duration = duration
906
 
        self.operation.props.media_duration = duration
907
 
 
908
 
    def _updateOperationPriority(self, priority):
909
 
        self.operation.props.priority = 1 + 3 * priority
910
 
 
911
 
    def _updateController(self):
912
 
        if self.a.stagger > self.b.stagger:
913
 
            # source a is under source b (higher priority)
914
 
            # we fade source B in
915
 
            self.controller.unset_all("alpha")
916
 
            self.controller.set("alpha", 0, 0.0)
917
 
            self.controller.set("alpha", self.duration, 1.0)
918
 
        elif self.a.stagger < self.b.stagger:
919
 
            # source a is over source b (lower priority)
920
 
            # we fade source a out
921
 
            self.controller.unset_all("alpha")
922
 
            self.controller.set("alpha", 0, 1.0)
923
 
            self.controller.set("alpha", self.duration, 0.0)
924
 
 
925
 
 
926
 
class AudioTransition(Transition):
927
 
 
928
 
    def _makeGnlObject(self):
929
 
        trans = gst.element_factory_make("volume")
930
 
        self.a_controller = gst.Controller(trans, "volume")
931
 
        self.a_controller.set_interpolation_mode("volume", gst.INTERPOLATE_LINEAR)
932
 
 
933
 
        self.a_operation = gst.element_factory_make("gnloperation")
934
 
        self.a_operation.add(trans)
935
 
 
936
 
        trans = gst.element_factory_make("volume")
937
 
        self.b_controller = gst.Controller(trans, "volume")
938
 
        self.b_controller.set_interpolation_mode("volume", gst.INTERPOLATE_LINEAR)
939
 
 
940
 
        self.b_operation = gst.element_factory_make("gnloperation")
941
 
        self.b_operation.add(trans)
942
 
 
943
 
        self.a_operation.props.media_start = 0
944
 
 
945
 
        self.b_operation.props.media_start = 0
946
 
 
947
 
    def addThyselfToComposition(self, composition):
948
 
        composition.add(self.a_operation, self.b_operation)
949
 
 
950
 
    def removeThyselfFromComposition(self, composition):
951
 
        composition.remove(self.a_operation)
952
 
        composition.remove(self.b_operation)
953
 
        self.a_operation.set_state(gst.STATE_NULL)
954
 
        self.b_operation.set_state(gst.STATE_NULL)
955
 
 
956
 
    def _updateOperationStart(self, start):
957
 
        self.a_operation.props.start = start
958
 
        self.b_operation.props.start = start
959
 
 
960
 
    def _updateOperationDuration(self, duration):
961
 
        self.a_operation.props.duration = duration
962
 
        self.a_operation.props.media_duration = duration
963
 
        self.b_operation.props.duration = duration
964
 
        self.b_operation.props.media_duration = duration
965
 
 
966
 
    def _staggerChanged(self, *unused):
967
 
        self._updateOperationPriority(self.priority)
968
 
 
969
 
    def _updateOperationPriority(self, priority):
970
 
        self.a_operation.props.priority = 1 + 2 * self.a.stagger + 4 * priority
971
 
        self.b_operation.props.priority = 1 + 2 * self.b.stagger + 4 * priority
972
 
 
973
 
    def _updateController(self):
974
 
        self.a_controller.set("volume", 0, 1.0)
975
 
        self.a_controller.set("volume", self.duration, 0.0)
976
 
        self.b_controller.set("volume", 0, 0.0)
977
 
        self.b_controller.set("volume", self.duration, 1.0)
978
 
 
979
 
 
980
 
class Track(Signallable, Loggable):
981
 
    logCategory = "track"
982
 
 
983
 
    __signals__ = {
984
 
        'start-changed': ['start'],
985
 
        'duration-changed': ['duration'],
986
 
        'track-object-added': ['track_object'],
987
 
        'track-object-removed': ['track_object'],
988
 
        'max-priority-changed': ['track_object'],
989
 
        'transition-added': ['transition'],
990
 
        'transition-removed': ['transition'],
991
 
    }
992
 
 
993
 
    def __init__(self, stream):
994
 
        self.stream = stream
995
 
        if type(self.stream) is VideoStream:
996
 
            self.TransitionClass = VideoTransition
997
 
        elif type(self.stream) is AudioStream:
998
 
            self.TransitionClass = AudioTransition
999
 
        self.composition = gst.element_factory_make('gnlcomposition')
1000
 
        self.composition.connect('notify::start', self._compositionStartChangedCb)
1001
 
        self.composition.connect('notify::duration', self._compositionDurationChangedCb)
1002
 
        self.track_objects = []
1003
 
        self.transitions = {}
1004
 
        self._update_transitions = True
1005
 
        self._max_priority = 0
1006
 
 
1007
 
        self.mixer = self._getMixerForStream(stream)
1008
 
        if self.mixer:
1009
 
            self.composition.add(self.mixer)
1010
 
        self.default_sources = []
1011
 
 
1012
 
    def _getDefaultTrackObjectForStream(self, stream):
1013
 
        if isinstance(stream, VideoStream):
1014
 
            ret = self._getDefaultVideoTrackObject(stream)
1015
 
        elif isinstance(stream, AudioStream):
1016
 
            ret = self._getDefaultAudioTrackObject(stream)
1017
 
        else:
1018
 
            return None
1019
 
 
1020
 
        ret.makeBin()
1021
 
        ret.gnl_object.props.priority = 2 ** 32 - 1
1022
 
        self.debug("Track Object %r, priority: %r:", ret, ret.gnl_object.props.priority)
1023
 
        return ret
1024
 
 
1025
 
    def _getDefaultVideoTrackObject(self, stream):
1026
 
        factory = VideoTestSourceFactory(pattern='black')
1027
 
        factory.setFilterCaps(stream.caps)
1028
 
        track_object = SourceTrackObject(factory, stream)
1029
 
 
1030
 
        return track_object
1031
 
 
1032
 
    def _getDefaultAudioTrackObject(self, stream):
1033
 
        factory = AudioTestSourceFactory(wave='silence')
1034
 
        track_object = SourceTrackObject(factory, stream)
1035
 
 
1036
 
        return track_object
1037
 
 
1038
 
    def _defaultSourceBlockedCb(self, pad, blocked):
1039
 
        pass
1040
 
 
1041
 
    def _shutdownDefaultSource(self, source):
1042
 
        source = list(source.elements())[0]
1043
 
        for srcpad in source.src_pads():
1044
 
            srcpad = source.get_pad('src')
1045
 
            srcpad.set_blocked_async(True, self._defaultSourceBlockedCb)
1046
 
            srcpad.push_event(gst.event_new_flush_start())
1047
 
 
1048
 
    def _sourceDebug(self, source):
1049
 
        t = gst.TIME_ARGS
1050
 
        return "%s [%s - %s]" % (source, t(source.props.start),
1051
 
                t(source.props.start + source.props.duration))
1052
 
 
1053
 
    def _updateDefaultSourcesUnchecked(self):
1054
 
        for source in self.default_sources:
1055
 
            self.debug("removing default source %s", self._sourceDebug(source))
1056
 
            self._shutdownDefaultSource(source)
1057
 
            self.composition.remove(source)
1058
 
            source.set_state(gst.STATE_NULL)
1059
 
        gaps = Gap.findAllGaps(self.track_objects)
1060
 
 
1061
 
        self.default_sources = []
1062
 
        for gap in gaps:
1063
 
            source = self._getDefaultTrackObjectForStream(self.stream)
1064
 
            gnl_object = source.gnl_object
1065
 
            gnl_object.props.start = gap.start
1066
 
            gnl_object.props.duration = gap.initial_duration
1067
 
            self.debug("adding default source %s",
1068
 
                    self._sourceDebug(source.gnl_object))
1069
 
            self.composition.add(gnl_object)
1070
 
            self.default_sources.append(gnl_object)
1071
 
 
1072
 
    def updateDefaultSources(self):
1073
 
        if not self.composition.props.update:
1074
 
            return
1075
 
 
1076
 
        self.updateDefaultSourcesReal()
1077
 
 
1078
 
    def updateCaps(self, caps):
1079
 
        self.stream.caps = caps
1080
 
        self.updateDefaultSources()
1081
 
 
1082
 
    def updateDefaultSourcesReal(self):
1083
 
        update = self.composition.props.update
1084
 
        self.composition.props.update = True
1085
 
        try:
1086
 
            self._updateDefaultSourcesUnchecked()
1087
 
        finally:
1088
 
            self.composition.props.update = update
1089
 
 
1090
 
    def _getMixerForStream(self, stream):
1091
 
        if isinstance(stream, AudioStream):
1092
 
            gnl = gst.element_factory_make("gnloperation", "top-level-audio-mixer")
1093
 
            m = SmartAdderBin()
1094
 
            gnl.add(m)
1095
 
            gnl.props.expandable = True
1096
 
            gnl.props.priority = 0
1097
 
            self.debug("Props priority: %s", gnl.props.priority)
1098
 
            return gnl
1099
 
        elif isinstance(stream, VideoStream):
1100
 
            gnl = gst.element_factory_make("gnloperation", "top-level-video-mixer")
1101
 
            m = SmartVideomixerBin(self)
1102
 
            gnl.add(m)
1103
 
            gnl.props.expandable = True
1104
 
            gnl.props.priority = 0
1105
 
            gnl.connect("input-priority-changed",
1106
 
                        self._videoInputPriorityChangedCb, m)
1107
 
            return gnl
1108
 
        return None
1109
 
 
1110
 
    def _videoInputPriorityChangedCb(self, operation, pad, priority, mixer):
1111
 
        self.debug("operation %s pad %s priority changed %s",
1112
 
                operation, pad, priority)
1113
 
        mixer.update_priority(pad, priority)
1114
 
 
1115
 
    def _getStart(self):
1116
 
        return self.composition.props.start
1117
 
 
1118
 
    def getPreviousTrackObject(self, obj, priority=-1):
1119
 
        prev = getPreviousObject(obj, self.track_objects, priority)
1120
 
        if prev is None:
1121
 
            raise TrackError("no previous track object", obj)
1122
 
 
1123
 
        return prev
1124
 
 
1125
 
    def getNextTrackObject(self, obj, priority=-1):
1126
 
        next = getNextObject(obj, self.track_objects, priority)
1127
 
        if next is None:
1128
 
            raise TrackError("no next track object", obj)
1129
 
 
1130
 
        return next
1131
 
 
1132
 
    start = property(_getStart)
1133
 
 
1134
 
    def _getDuration(self):
1135
 
        return self.composition.props.duration
1136
 
 
1137
 
    duration = property(_getDuration)
1138
 
 
1139
 
    def _getMaxPriority(self):
1140
 
        return self._max_priority
1141
 
 
1142
 
    max_priority = property(_getMaxPriority)
1143
 
 
1144
 
    def _trackObjectPriorityCb(self, trackobject, priority):
1145
 
        op = self._max_priority
1146
 
        self._max_priority = max((obj.priority for obj in self.track_objects))
1147
 
        if op != self._max_priority:
1148
 
            self.emit("max-priority-changed", self._max_priority)
1149
 
 
1150
 
    def _connectToTrackObjectSignals(self, track_object):
1151
 
        track_object.connect("priority-changed", self._trackObjectPriorityCb)
1152
 
 
1153
 
    def _disconnectTrackObjectSignals(self, track_object):
1154
 
        track_object.disconnect_by_function(self._trackObjectPriorityCb)
1155
 
 
1156
 
    def addTrackObject(self, track_object):
1157
 
        if track_object.track is not None:
1158
 
            raise TrackError()
1159
 
 
1160
 
        if track_object.gnl_object in list(self.composition):
1161
 
            raise TrackError()
1162
 
        track_object.makeBin()
1163
 
 
1164
 
        track_object.track = self
1165
 
 
1166
 
        start_insort_right(self.track_objects, track_object)
1167
 
        self.updateDefaultSources()
1168
 
 
1169
 
        try:
1170
 
            self.composition.add(track_object.gnl_object)
1171
 
        except gst.AddError:
1172
 
            raise TrackError()
1173
 
 
1174
 
        self._connectToTrackObjectSignals(track_object)
1175
 
 
1176
 
        self._updateMaxPriority()
1177
 
        self._connectToTrackObject(track_object)
1178
 
 
1179
 
        self.emit('track-object-added', track_object)
1180
 
        if self._update_transitions:
1181
 
            self.updateTransitions()
1182
 
 
1183
 
    def _justRemoveTrackObject(self, track_object):
1184
 
        if track_object.track is None:
1185
 
            raise TrackError()
1186
 
 
1187
 
        try:
1188
 
            self.composition.remove(track_object.gnl_object)
1189
 
            track_object.gnl_object.set_state(gst.STATE_NULL)
1190
 
        except gst.RemoveError:
1191
 
            raise TrackError()
1192
 
 
1193
 
        self._disconnectFromTrackObject(track_object)
1194
 
        track_object.releaseBin()
1195
 
 
1196
 
        self.track_objects.remove(track_object)
1197
 
        track_object.track = None
1198
 
 
1199
 
        self._disconnectTrackObjectSignals(track_object)
1200
 
        self.emit('track-object-removed', track_object)
1201
 
 
1202
 
    def removeTrackObject(self, track_object):
1203
 
        self._justRemoveTrackObject(track_object)
1204
 
 
1205
 
        self._updateMaxPriority()
1206
 
        self.updateDefaultSources()
1207
 
 
1208
 
        if self._update_transitions:
1209
 
            self.updateTransitions()
1210
 
 
1211
 
    def removeMultipleTrackObjects(self, track_objects):
1212
 
        for track_object in track_objects:
1213
 
            self._justRemoveTrackObject(track_object)
1214
 
 
1215
 
        self._updateMaxPriority()
1216
 
        self.updateDefaultSources()
1217
 
 
1218
 
        if self._update_transitions:
1219
 
            self.updateTransitions()
1220
 
 
1221
 
    def removeAllTrackObjects(self):
1222
 
        self.removeMultipleTrackObjects(list(self.track_objects))
1223
 
 
1224
 
    def _updateMaxPriority(self):
1225
 
        priorities = [track_object.priority for track_object in
1226
 
            self.track_objects]
1227
 
        if not priorities:
1228
 
            max_priority = 0
1229
 
        else:
1230
 
            max_priority = max(priorities)
1231
 
        if max_priority != self._max_priority:
1232
 
            self._max_priority = max_priority
1233
 
            self.emit('max-priority-changed', self._max_priority)
1234
 
 
1235
 
    def _compositionStartChangedCb(self, composition, pspec):
1236
 
        start = composition.props.start
1237
 
        self.emit('start-changed', start)
1238
 
 
1239
 
    def _compositionDurationChangedCb(self, composition, pspec):
1240
 
        duration = composition.props.duration
1241
 
        self.emit('duration-changed', duration)
1242
 
 
1243
 
    def _trackObjectPriorityChangedCb(self, track_object, priority):
1244
 
        self._updateMaxPriority()
1245
 
 
1246
 
    def _trackObjectStartChangedCb(self, track_object, start):
1247
 
        self.track_objects.remove(track_object)
1248
 
        start_insort_right(self.track_objects, track_object)
1249
 
 
1250
 
    def _trackObjectDurationChangedCb(self, track_object, duration):
1251
 
        pass
1252
 
 
1253
 
    def _connectToTrackObject(self, track_object):
1254
 
        track_object.connect('priority-changed',
1255
 
                self._trackObjectPriorityChangedCb)
1256
 
        track_object.connect('start-changed',
1257
 
                self._trackObjectStartChangedCb)
1258
 
        track_object.connect('duration-changed',
1259
 
                self._trackObjectDurationChangedCb)
1260
 
 
1261
 
    def _disconnectFromTrackObject(self, track_object):
1262
 
        track_object.disconnect_by_function(self._trackObjectPriorityChangedCb)
1263
 
        track_object.disconnect_by_function(self._trackObjectStartChangedCb)
1264
 
        track_object.disconnect_by_function(self._trackObjectDurationChangedCb)
1265
 
 
1266
 
    def enableUpdates(self):
1267
 
        self.composition.props.update = True
1268
 
        self.updateDefaultSources()
1269
 
        self._update_transitions = True
1270
 
        self.updateTransitions()
1271
 
 
1272
 
    def disableUpdates(self):
1273
 
        self.composition.props.update = False
1274
 
        self._update_transitions = False
1275
 
 
1276
 
    def addTransition(self, transition):
1277
 
        a, b = transition.a, transition.b
1278
 
        if not ((a in self.track_objects) and
1279
 
                (b in self.track_objects)):
1280
 
            raise TrackError("One or both track objects not in track")
1281
 
        if (a, b) in self.transitions:
1282
 
            raise TrackError(
1283
 
                "A transition is already defined for these objects")
1284
 
        transition.addThyselfToComposition(self.composition)
1285
 
        self.transitions[a, b] = transition
1286
 
        self.emit("transition-added", transition)
1287
 
 
1288
 
    def removeTransition(self, transition):
1289
 
        a, b = transition.a, transition.b
1290
 
        transition.removeThyselfFromComposition(self.composition)
1291
 
        del self.transitions[a, b]
1292
 
        self.emit("transition-removed", transition)
1293
 
 
1294
 
    def getTrackObjectsGroupedByLayer(self):
1295
 
        layers = [[] for x in xrange(0, self.max_priority + 1)]
1296
 
        for track_object in self.track_objects:
1297
 
            if not isinstance(track_object, TrackEffect):
1298
 
                layers[int(track_object.priority)].append(track_object)
1299
 
        return layers
1300
 
 
1301
 
    def getValidTransitionSlots(self, objs):
1302
 
        prev = None
1303
 
        safe = 0
1304
 
        duration = 0
1305
 
        slots = []
1306
 
        valid = True
1307
 
 
1308
 
        def pop():
1309
 
            if len(slots):
1310
 
                slots.pop(-1)
1311
 
        for obj in objs:
1312
 
            end = obj.start + obj.duration
1313
 
            if obj.start >= duration:
1314
 
                safe = obj.start
1315
 
                duration = end
1316
 
                prev = obj
1317
 
            elif end >= duration and obj.start >= safe:
1318
 
                slots.append((prev, obj))
1319
 
                safe = duration
1320
 
                duration = end
1321
 
                prev = obj
1322
 
            elif end >= duration and obj.start < safe:
1323
 
                pop()
1324
 
                valid = False
1325
 
                safe = duration
1326
 
                duration = end
1327
 
                prev = obj
1328
 
            elif end < duration and obj.start >= safe:
1329
 
                safe = end
1330
 
                valid = False
1331
 
            elif end < duration and obj.start < safe:
1332
 
                pop()
1333
 
                valid = False
1334
 
                safe = end
1335
 
 
1336
 
        return slots, valid
1337
 
 
1338
 
    valid_arrangement = True
1339
 
 
1340
 
    def updateTransitions(self):
1341
 
        # create all new transitions
1342
 
        valid_slots = set()
1343
 
        all_valid = True
1344
 
        for layer in self.getTrackObjectsGroupedByLayer():
1345
 
            pos = 0
1346
 
            prev = None
1347
 
            slots, is_valid = self.getValidTransitionSlots(layer)
1348
 
            all_valid &= is_valid
1349
 
            for slot in slots:
1350
 
                a, b = slot
1351
 
                if a == prev:
1352
 
                    b.updatePosition(pos)
1353
 
                    pos += 1
1354
 
                else:
1355
 
                    a.updatePosition(pos)
1356
 
                    b.updatePosition(pos + 1)
1357
 
                    pos += 2
1358
 
                prev = b
1359
 
                valid_slots.add(slot)
1360
 
                if not slot in self.transitions:
1361
 
                    tr = self.TransitionClass(a, b)
1362
 
                    self.addTransition(tr)
1363
 
        current_slots = set(self.transitions.iterkeys())
1364
 
        for slot in current_slots - valid_slots:
1365
 
            self.removeTransition(self.transitions[slot])
1366
 
        self.valid_arrangement = all_valid