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

« back to all changes in this revision

Viewing changes to pitivi/timeline/track.py

* New upstream pre-release:
  + debian/control:
    - Update dependencies.
* debian/control:
  + Update Standards-Version to 3.8.2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
import weakref
24
24
 
25
25
from pitivi.signalinterface import Signallable
26
 
from pitivi.utils import UNKNOWN_DURATION
 
26
from pitivi.utils import get_controllable_properties, getPreviousObject, \
 
27
        getNextObject, start_insort_right
 
28
from pitivi.log.loggable import Loggable
27
29
from pitivi.stream import VideoStream, AudioStream
28
30
from pitivi.factories.test import VideoTestSourceFactory, \
29
31
        AudioTestSourceFactory
 
32
from pitivi.elements.mixer import SmartAdderBin
30
33
 
31
34
class TrackError(Exception):
32
35
    pass
33
36
 
34
 
class TrackObject(Signallable):
 
37
class Keyframe(Signallable):
 
38
 
 
39
    """Represents a single point on an interpolation curve"""
 
40
 
 
41
    __signals__ = {
 
42
        "value-changed" : ['value'],
 
43
        "time-changed" : ['time'],
 
44
        "mode-changed" : ['mode'],
 
45
    }
 
46
 
 
47
    def __init__(self, parent):
 
48
        self.parent = parent
 
49
 
 
50
## Properties
 
51
 
 
52
    _mode = gst.INTERPOLATE_LINEAR
 
53
 
 
54
    def setMode(self, mode):
 
55
        if self.parent:
 
56
            self.parent.setKeyframeMode(self, mode)
 
57
        else:
 
58
            self.setObjectMode(mode)
 
59
 
 
60
    def setObjectMode(self, mode):
 
61
        self._mode = mode
 
62
        self.emit("mode-changed", mode)
 
63
 
 
64
    def getMode(self):
 
65
        return self._mode
 
66
 
 
67
    mode = property(getMode, setMode)
 
68
 
 
69
    _time = 0
 
70
 
 
71
    def setTime(self, time):
 
72
        if self.parent:
 
73
            self.parent.setKeyframeTime(self, time)
 
74
        else:
 
75
            self.setObjectTime(time)
 
76
 
 
77
    def setObjectTime(self, time):
 
78
        self._time = time
 
79
        self.emit("time-changed", time)
 
80
 
 
81
    def getTime(self):
 
82
        return self._time
 
83
 
 
84
    time = property(getTime, setTime)
 
85
 
 
86
    _value = None
 
87
 
 
88
    def setValue(self, value):
 
89
        if self.parent:
 
90
            self.parent.setKeyframeValue(self, value)
 
91
        else:
 
92
            self.setObjectValue(value)
 
93
 
 
94
    def setObjectValue(self, value):
 
95
        self._value = value
 
96
        self.emit("value-changed", value)
 
97
 
 
98
    def getValue(self):
 
99
        return self._value
 
100
 
 
101
    value = property(getValue, setValue)
 
102
 
 
103
    def __cmp__(self, other):
 
104
        if other:
 
105
            return cmp(self.time, other.time)
 
106
        return self
 
107
 
 
108
class FixedKeyframe(Keyframe):
 
109
 
 
110
    def setTime(self, time):
 
111
        pass
 
112
 
 
113
    time = property(Keyframe.getTime, setTime)
 
114
 
 
115
class Interpolator(Signallable, Loggable):
 
116
 
 
117
    """The bridge between the gstreamer dynamic property API and pitivi track
 
118
    objects.
 
119
 
 
120
    * creates gnl.Controller() objects
 
121
    * binds controller to track object's gnlobject
 
122
    * allows client code to manipulate the interpolation curve by adding,
 
123
      removing, and mutating discrete keyframe objects
 
124
 
 
125
    There are two special control points: the start and end points, which are
 
126
    "fixed" to the start and end of the clip in the timeline. 
 
127
 
 
128
    Timestamps given are assumed to be relative to the start of the clip. This
 
129
    seems to be the normal behavior when the element being controlled is
 
130
    internal to the gnlsource or gnloperation.
 
131
    """
 
132
 
 
133
    __signals__ = {
 
134
        'keyframe-added' : ['keyframe'],
 
135
        'keyframe-removed' : ['keyframe'],
 
136
        'keyframe-moved' : ['keyframe'],
 
137
    }
 
138
 
 
139
    def __init__(self, trackobject, element, prop):
 
140
        Loggable.__init__(self)
 
141
        self.debug("track:%r, element:%r, property:%r", trackobject, element, prop)
 
142
        self._keyframes = []
 
143
        # FIXME: get this from the property's param spec
 
144
        # NOTE: keyframes necessarily work only on a closed range
 
145
        self.lower = 0
 
146
        self.upper = 1
 
147
 
 
148
        # FIXME: don't necessarily want to create separate controllers for
 
149
        # each Interpolator. We should instead create a ControlSource for each
 
150
        # element. We can't do this until the new controller interface is
 
151
        # exposed in gst-python.
 
152
        self.attachToElementProperty(prop, element)
 
153
        self._default = self._element.get_property(prop.name)
 
154
        self.start = FixedKeyframe(self)
 
155
        self.end = FixedKeyframe(self)
 
156
        self.start.value = self._default
 
157
        self.start.setObjectTime(0)
 
158
        self.end.value = self._default
 
159
        self.end.setObjectTime(trackobject.factory.duration)
 
160
 
 
161
    def attachToElementProperty(self, prop, element):
 
162
        self._element = element
 
163
        self._property = prop
 
164
        self.debug("Creating a GstController for element %r and property %s",
 
165
            self._element, prop.name)
 
166
        self._controller = gst.Controller(self._element, prop.name)
 
167
        self._controller.set_interpolation_mode(prop.name, gst.INTERPOLATE_LINEAR)
 
168
 
 
169
    def newKeyframe(self, time_or_keyframe, value=None, mode=None):
 
170
        """add a new keyframe at the specified time, optionally with specified
 
171
        value and specified mode. If not specified, these will be computed so
 
172
        that the new keyframe likes on the existing curve at that timestampi
 
173
 
 
174
        returns: the keyframe object"""
 
175
 
 
176
        if isinstance(time_or_keyframe, Keyframe):
 
177
            keyframe = time_or_keyframe
 
178
        else:
 
179
            # TODO: calculate value so that the new point doesn't change the shape
 
180
            # of the curve when added. This might be tricky to achieve with cubic
 
181
            # interpolation, but should work fine for linear and step
 
182
            # interpolation.
 
183
            if value is None:
 
184
                value = self._default
 
185
            if mode is None:
 
186
                # FIXME: Controller.get_interpolation_mode is not wrapped in
 
187
                # gst-python, so for now we assume the default is linear.
 
188
                # Use the following code to get the current mode when this method becomes
 
189
                # available.
 
190
                # mode = self._controller.get_interpolation_mode()
 
191
                mode = gst.INTERPOLATE_LINEAR
 
192
 
 
193
            keyframe = Keyframe(self)
 
194
            keyframe._time = time_or_keyframe
 
195
            keyframe._value = value
 
196
            keyframe._mode = mode
 
197
 
 
198
        self.debug("time:%s, value:%r, mode:%r",
 
199
                   gst.TIME_ARGS(keyframe.time), keyframe.value, keyframe.mode)
 
200
 
 
201
        self._keyframes.append(keyframe)
 
202
 
 
203
        self._controller.set(self._property.name, keyframe.time, keyframe.value)
 
204
 
 
205
        self.emit("keyframe-added", keyframe)
 
206
 
 
207
        return keyframe
 
208
 
 
209
    def removeKeyframe(self, keyframe):
 
210
        self._controller.unset(self._property.name, keyframe.time)
 
211
        if keyframe is not self.start and keyframe is not self.end:
 
212
            self._keyframes.remove(keyframe)
 
213
            self.emit("keyframe-removed", keyframe)
 
214
 
 
215
    def setKeyframeMode(self, kf, mode):
 
216
        # FIXME: currently InterpolationSourceControllers only support a
 
217
        # single mode. Suporting per-keyframe modes would require implementing
 
218
        # a custom interplation source controller.
 
219
        # For now, whenever one keyframe's mode changes, we'll set the mode
 
220
        # globally.
 
221
        for keyframe in self.keyframes:
 
222
            keyframe.setObjectMode(mode)
 
223
        self._controller.set_interpolation_mode(self._property.name, mode)
 
224
 
 
225
    def setKeyframeTime(self, kf, time):
 
226
        time = max(self.start.time, min(self.end.time, time))
 
227
        self._keyframeTimeValueChanged(kf, time, kf.value)
 
228
        kf.setObjectTime(time)
 
229
 
 
230
    def setKeyframeValue(self, kf, value):
 
231
        value = max(self.lower, min(self.upper, value))
 
232
        self._keyframeTimeValueChanged(kf, kf.time, value)
 
233
        kf.setObjectValue(value)
 
234
 
 
235
    def _keyframeTimeValueChanged(self, kf, ptime, value):
 
236
        self.debug("kf.time:%s, ptime:%s, value:%r",
 
237
                   gst.TIME_ARGS(kf.time),
 
238
                   gst.TIME_ARGS(ptime), value)
 
239
        if kf.time != ptime:
 
240
            self._controller.unset(self._property.name, kf.time)
 
241
        self._controller.set(self._property.name, ptime, value)
 
242
        self.emit("keyframe-moved", kf)
 
243
 
 
244
    def getKeyframes(self):
 
245
        # TODO: This could be more efficient. We are re-sorting all the keyframes
 
246
        # every time we iterate over them. One thought would be to keep the
 
247
        # list sorted in-place as keyframes are added and moved.
 
248
        yield self.start
 
249
        for kf in sorted(self._keyframes):
 
250
            yield kf
 
251
        yield self.end
 
252
 
 
253
    def getInteriorKeyframes(self):
 
254
        """Same as above but does not include start, or end points"""
 
255
        for kf in sorted(self._keyframes):
 
256
            yield kf
 
257
 
 
258
    keyframes = property(getKeyframes)
 
259
 
 
260
class TrackObject(Signallable, Loggable):
 
261
 
35
262
    __signals__ = {
36
263
        'start-changed': ['start'],
37
264
        'duration-changed': ['duration'],
45
272
    def __init__(self, factory, stream, start=0,
46
273
            duration=0, in_point=0,
47
274
            media_duration=0, priority=0):
 
275
        Loggable.__init__(self)
 
276
        self.debug("factory:%r", factory)
48
277
        self.factory = factory
49
278
        self.stream = stream
50
279
        self.track = None
51
280
        self.timeline_object = None
 
281
        self.interpolators = {}
 
282
        self._rebuild_interpolators = True
52
283
        self.gnl_object = obj = self._makeGnlObject()
53
 
        self.trimmed_start = 0
 
284
        self.keyframes = []
54
285
 
55
286
        if start != 0:
56
287
            obj.props.start = start
73
304
 
74
305
        self._connectToSignals(obj)
75
306
 
 
307
    def getInterpolator(self, property_name):
 
308
        self._maybeBuildInterpolators()
 
309
 
 
310
        try:
 
311
            return self.interpolators[property_name][1]
 
312
        except KeyError:
 
313
            raise TrackError("no interpolator for '%s'" % property_name)
 
314
 
 
315
    def getInterpolators(self):
 
316
        self._maybeBuildInterpolators()
 
317
        return self.interpolators
 
318
 
 
319
    def _maybeBuildInterpolators(self):
 
320
        if not list(self.gnl_object.elements()):
 
321
            raise TrackError("makeBin hasn't been called yet")
 
322
 
 
323
        if not self._rebuild_interpolators:
 
324
            return
 
325
 
 
326
        self._rebuild_interpolators = False
 
327
 
 
328
        factory_properties = self.factory.getInterpolatedProperties(self.stream).keys()
 
329
 
 
330
        old_interpolators = self.interpolators
 
331
        self.interpolators = {}
 
332
        for gst_object, gst_object_property in \
 
333
                get_controllable_properties(self.gnl_object):
 
334
            if gst_object_property.name not in factory_properties:
 
335
                continue
 
336
 
 
337
            try:
 
338
                interpolator = old_interpolators[gst_object_property.name][1]
 
339
            except KeyError:
 
340
                interpolator = Interpolator(self, gst_object, gst_object_property)
 
341
            else:
 
342
                interpolator.attachToElementProperty(gst_object_property,
 
343
                        gst_object)
 
344
 
 
345
                # remove and add again the keyframes so they are set on the
 
346
                # current controller
 
347
                for keyframe in list(interpolator.keyframes):
 
348
                    interpolator.removeKeyframe(keyframe)
 
349
                    interpolator.newKeyframe(keyframe)
 
350
 
 
351
            self.interpolators[gst_object_property.name] = \
 
352
                    (gst_object_property, interpolator)
 
353
 
76
354
    def release(self):
77
355
        self._disconnectFromSignals()
78
356
        self.releaseBin()
84
362
        other = cls(self.factory, self.stream, start=self.start,
85
363
            duration=self.duration, in_point=self.in_point,
86
364
            media_duration=self.media_duration, priority=self.priority)
87
 
        other.trimmed_start = self.trimmed_start
 
365
 
 
366
        if self.track is not None:
 
367
            self.track.addTrackObject(other)
 
368
 
 
369
        interpolators = self.getInterpolators()
 
370
        for property, interpolator in interpolators.itervalues():
 
371
            other_interpolator = other.getInterpolator(property.name)
 
372
            other_interpolator.start.value = interpolator.start.value
 
373
            other_interpolator.start.mode = interpolator.start.mode
 
374
            other_interpolator.end.value = interpolator.end.value
 
375
            other_interpolator.end.mode = interpolator.end.mode
 
376
            for kf in interpolator.getInteriorKeyframes():
 
377
                other_interpolator.newKeyframe(kf.time,
 
378
                    kf.value,
 
379
                    kf.mode)
88
380
 
89
381
        return other
90
382
 
91
383
    def snapStartDurationTime(self, *args):
92
384
        return
93
385
 
94
 
    # FIXME: there's a lot of boilerplate here that could be factored in a
95
 
    # metaclass.  Do we like metaclasses in pitivi?
96
386
    def _getStart(self):
97
387
        return self.gnl_object.props.start
98
388
 
99
 
    def setStart(self, time, snap=False):
 
389
    def setStart(self, position, snap=False):
100
390
        if self.timeline_object is not None:
101
 
            self.timeline_object.setStart(time, snap)
 
391
            self.timeline_object.setStart(position, snap)
102
392
        else:
103
393
            if snap:
104
394
                raise TrackError()
105
395
 
106
 
            self.setObjectStart(time)
 
396
            self.setObjectStart(position)
107
397
 
108
 
    def setObjectStart(self, time):
109
 
        self.gnl_object.props.start = time
 
398
    def setObjectStart(self, position):
 
399
        if self.gnl_object.props.start != position:
 
400
            self.gnl_object.props.start = position
110
401
 
111
402
    start = property(_getStart, setStart)
112
403
 
113
404
    def _getDuration(self):
114
405
        return self.gnl_object.props.duration
115
406
 
116
 
    def setDuration(self, time, snap=False):
 
407
    def setDuration(self, position, snap=False):
117
408
        if self.timeline_object is not None:
118
 
            self.timeline_object.setDuration(time, snap)
 
409
            self.timeline_object.setDuration(position, snap)
119
410
        else:
120
411
            if snap:
121
412
                raise TrackError()
122
413
 
123
 
            self.setObjectDuration(time)
 
414
            self.setObjectDuration(position)
124
415
 
125
 
    def setObjectDuration(self, time):
126
 
        self.gnl_object.props.duration = time
 
416
    def setObjectDuration(self, position):
 
417
        if self.gnl_object.props.duration != position:
 
418
            self.gnl_object.props.duration = position
127
419
 
128
420
    duration = property(_getDuration, setDuration)
129
421
 
130
422
    def _getInPoint(self):
131
423
        return self.gnl_object.props.media_start
132
424
 
133
 
    def setInPoint(self, time, snap=False):
 
425
    def setInPoint(self, position, snap=False):
134
426
        if self.timeline_object is not None:
135
 
            self.timeline_object.setInPoint(time, snap)
 
427
            self.timeline_object.setInPoint(position, snap)
136
428
        else:
137
 
            self.setObjectInPoint(time)
 
429
            self.setObjectInPoint(position)
138
430
 
139
431
    def setObjectInPoint(self, value):
140
 
        self.gnl_object.props.media_start = value
 
432
        if self.gnl_object.props.media_start != value:
 
433
            self.gnl_object.props.media_start = value
141
434
 
142
435
    in_point = property(_getInPoint, setInPoint)
143
436
 
149
442
    def _getMediaDuration(self):
150
443
        return self.gnl_object.props.media_duration
151
444
 
152
 
    def setMediaDuration(self, time, snap=False):
 
445
    def setMediaDuration(self, position, snap=False):
153
446
        if self.timeline_object is not None:
154
 
            self.timeline_object.setMediaDuration(time, snap)
 
447
            self.timeline_object.setMediaDuration(position, snap)
155
448
        else:
156
 
            self.setObjectMediaDuration(time)
 
449
            self.setObjectMediaDuration(position)
157
450
 
158
 
    def setObjectMediaDuration(self, time):
159
 
        self.gnl_object.props.media_duration = time
 
451
    def setObjectMediaDuration(self, position):
 
452
        if self.gnl_object.props.media_duration != position:
 
453
            self.gnl_object.props.media_duration = position
160
454
 
161
455
    media_duration = property(_getMediaDuration, setMediaDuration)
162
456
 
175
469
            self.setObjectPriority(priority)
176
470
 
177
471
    def setObjectPriority(self, priority):
178
 
        self.gnl_object.props.priority = priority
 
472
        if self.gnl_object.props.priority != priority:
 
473
            self.gnl_object.props.priority = priority
179
474
 
180
475
    priority = property(_getPriority, setPriority)
181
476
 
182
 
    def trimStart(self, time, snap=False):
 
477
    def trimStart(self, position, snap=False):
183
478
        if self.timeline_object is not None:
184
 
            self.timeline_object.trimStart(time, snap)
 
479
            self.timeline_object.trimStart(position, snap)
185
480
        else:
186
 
            self.trimObjectStart(time)
187
 
 
188
 
    def trimObjectStart(self, time):
189
 
        # clamp time to be inside the object
190
 
        time = max(self.start - self.trimmed_start, time)
191
 
        time = min(time, self.start + self.duration)
192
 
        new_duration = max(0, self.start + self.duration - time)
193
 
 
194
 
        delta = time - self.start
195
 
        self.trimmed_start += delta
196
 
        self.setObjectStart(time)
 
481
            self.trimObjectStart(position)
 
482
 
 
483
    def trimObjectStart(self, position):
 
484
        # clamp position to be inside the object
 
485
        position = max(self.start - self.in_point, position)
 
486
        position = min(position, self.start + self.duration)
 
487
        new_duration = max(0, self.start + self.duration - position)
 
488
 
 
489
        delta = position - self.start
 
490
        in_point = self.in_point
 
491
        in_point += delta
 
492
        self.setObjectStart(position)
197
493
        self.setObjectDuration(new_duration)
198
 
        self.setObjectInPoint(self.trimmed_start)
 
494
        self.setObjectInPoint(in_point)
199
495
        self.setObjectMediaDuration(new_duration)
200
496
 
201
 
    def split(self, time, snap=False):
 
497
    def split(self, position, snap=False):
202
498
        if self.timeline_object is not None:
203
 
            return self.timeline_object.split(time, snap)
 
499
            return self.timeline_object.split(position, snap)
204
500
        else:
205
 
            return self.splitObject(time)
 
501
            return self.splitObject(position)
206
502
 
207
 
    def splitObject(self, time):
 
503
    def splitObject(self, position):
208
504
        start = self.gnl_object.props.start
209
505
        duration = self.gnl_object.props.duration
210
 
        if time <= start or time >= start + duration:
211
 
            raise TrackError("can't split at time %s" % gst.TIME_ARGS(time))
 
506
        if position <= start or position >= start + duration:
 
507
            raise TrackError("can't split at position %s" % gst.TIME_ARGS(position))
212
508
 
213
509
        other = self.copy()
214
510
 
215
 
        other.trimObjectStart(time)
216
 
        self.setObjectDuration(time - self.gnl_object.props.start)
217
 
        self.setObjectMediaDuration(time - self.gnl_object.props.start)
 
511
        other.trimObjectStart(position)
 
512
        self.setObjectDuration(position - self.gnl_object.props.start)
 
513
        self.setObjectMediaDuration(position - self.gnl_object.props.start)
218
514
 
219
515
        return other
220
516
 
233
529
    selected = property(_getSelected)
234
530
 
235
531
    def makeBin(self):
236
 
        if self.track is None:
 
532
        if self.stream is None:
 
533
            raise TrackError()
 
534
        if self.gnl_object is None:
237
535
            raise TrackError()
238
536
 
239
537
        bin = self.factory.makeBin(self.stream)
240
538
        self.gnl_object.add(bin)
 
539
        self._maybeBuildInterpolators()
241
540
 
242
541
    def releaseBin(self):
243
 
        elts = list(self.gnl_object.elements())
244
 
        if elts:
245
 
            bin = elts[0]
 
542
        for bin in list(self.gnl_object.elements()):
246
543
            self.gnl_object.remove(bin)
247
544
            bin.set_state(gst.STATE_NULL)
248
545
            self.factory.releaseBin(bin)
 
546
        self._rebuild_interpolators = True
249
547
 
250
548
    def _notifyStartCb(self, obj, pspec):
251
549
        self.emit('start-changed', obj.props.start)
288
586
    def _makeGnlObject(self):
289
587
        raise NotImplementedError()
290
588
 
291
 
 
292
589
class SourceTrackObject(TrackObject):
293
590
    def _makeGnlObject(self):
294
591
        source = gst.element_factory_make('gnlsource')
317
614
        if default_track_object:
318
615
            self.setDefaultTrackObject(default_track_object)
319
616
 
 
617
        self.mixer = self._getMixerForStream(stream)
 
618
        if self.mixer:
 
619
            self.composition.add(self.mixer)
 
620
 
320
621
    def _getDefaultTrackObjectForStream(self, stream):
321
622
        if isinstance(stream, VideoStream):
322
623
            return self._getDefaultVideoTrackObject(stream)
337
638
 
338
639
        return track_object
339
640
 
 
641
    def _getMixerForStream(self, stream):
 
642
        if isinstance(stream, AudioStream):
 
643
            gnl = gst.element_factory_make("gnloperation", "top-level-audio-mixer")
 
644
            m = SmartAdderBin()
 
645
            gnl.add(m)
 
646
            gnl.props.expandable = True
 
647
            gnl.props.priority = 0
 
648
            return gnl
 
649
        return None
 
650
 
340
651
    def _getStart(self):
341
652
        return self.composition.props.start
342
653
 
345
656
            self.removeTrackObject(self.default_track_object)
346
657
 
347
658
        self.default_track_object = None
348
 
        # FIXME: implement TrackObject.priority
349
659
        track_object.gnl_object.props.priority = 2**32-1
350
660
        self.default_track_object = track_object
351
661
        try:
354
664
            self.default_track_object = None
355
665
            raise
356
666
 
 
667
    def getPreviousTrackObject(self, obj, priority=-1):
 
668
        prev = getPreviousObject(obj, self.track_objects, priority,
 
669
                [self.default_track_object])
 
670
        if prev is None:
 
671
            raise TrackError("no previous track object", obj)
 
672
 
 
673
        return prev
 
674
 
 
675
    def getNextTrackObject(self, obj, priority=-1):
 
676
        next = getNextObject(obj, self.track_objects, priority,
 
677
                [self.default_track_object])
 
678
        if next is None:
 
679
            raise TrackError("no next track object", obj)
 
680
 
 
681
        return next
 
682
 
357
683
    start = property(_getStart)
358
684
 
359
685
    def _getDuration(self):
389
715
        if track_object.track is not None:
390
716
            raise TrackError()
391
717
 
 
718
        if track_object.gnl_object in list(self.composition):
 
719
            raise TrackError()
 
720
        track_object.makeBin()
 
721
 
 
722
        track_object.track = weakref.proxy(self)
 
723
 
 
724
        start_insort_right(self.track_objects, track_object)
 
725
 
392
726
        try:
393
727
            self.composition.add(track_object.gnl_object)
394
728
        except gst.AddError:
395
729
            raise TrackError()
396
730
 
397
 
        track_object.track = weakref.proxy(self)
398
 
        self.track_objects.append(track_object)
399
 
 
400
 
        track_object.makeBin()
401
731
        self._connectToTrackObjectSignals(track_object)
402
732
 
403
733
        self._updateMaxPriority()
416
746
            raise TrackError()
417
747
 
418
748
        self._disconnectFromTrackObject(track_object)
419
 
        track_object.release()
 
749
        track_object.releaseBin()
420
750
 
421
751
        self.track_objects.remove(track_object)
422
752
        track_object.track = None
452
782
    def _trackObjectPriorityChangedCb(self, track_object, priority):
453
783
        self._updateMaxPriority()
454
784
 
 
785
    def _trackObjectStartChangedCb(self, track_object, start):
 
786
        self.track_objects.remove(track_object)
 
787
        start_insort_right(self.track_objects, track_object)
 
788
 
 
789
    def _trackObjectDurationChangedCb(self, track_object, duration):
 
790
        pass
 
791
 
455
792
    def _connectToTrackObject(self, track_object):
456
793
        track_object.connect('priority-changed',
457
794
                self._trackObjectPriorityChangedCb)
 
795
        track_object.connect('start-changed',
 
796
                self._trackObjectStartChangedCb)
 
797
        track_object.connect('duration-changed',
 
798
                self._trackObjectDurationChangedCb)
458
799
 
459
800
    def _disconnectFromTrackObject(self, track_object):
460
801
        track_object.disconnect_by_function(self._trackObjectPriorityChangedCb)
 
802
        track_object.disconnect_by_function(self._trackObjectStartChangedCb)
 
803
        track_object.disconnect_by_function(self._trackObjectDurationChangedCb)
461
804
 
462
805
    def enableUpdates(self):
463
806
        self.composition.props.update = True