1
# PiTiVi , Non-linear video editor
3
# pitivi/timeline/timeline.py
5
# Copyright (c) 2009, Alessandro Decina <alessandro.decina@collabora.co.uk>
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.
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.
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.
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
36
class TrackError(Exception):
40
class Keyframe(Signallable):
42
"""Represents a single point on an interpolation curve"""
45
"value-changed": ['value'],
46
"time-changed": ['time'],
47
"mode-changed": ['mode'],
50
def __init__(self, parent):
55
_mode = gst.INTERPOLATE_LINEAR
57
def setMode(self, mode):
59
self.parent.setKeyframeMode(self, mode)
61
self.setObjectMode(mode)
63
def setObjectMode(self, mode):
65
self.emit("mode-changed", mode)
70
mode = property(getMode, setMode)
74
def setTime(self, time):
76
self.parent.setKeyframeTime(self, time)
78
self.setObjectTime(time)
80
def setObjectTime(self, time):
82
self.emit("time-changed", time)
87
time = property(getTime, setTime)
91
def setValue(self, value):
93
self.parent.setKeyframeValue(self, value)
95
self.setObjectValue(value)
97
def setObjectValue(self, value):
99
self.emit("value-changed", value)
104
value = property(getValue, setValue)
106
def __cmp__(self, other):
108
return cmp(self.time, other.time)
112
class FixedKeyframe(Keyframe):
114
def setTime(self, time):
117
time = property(Keyframe.getTime, setTime)
120
class Interpolator(Signallable, Loggable):
122
"""The bridge between the gstreamer dynamic property API and pitivi track
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
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.
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.
139
'keyframe-added': ['keyframe'],
140
'keyframe-removed': ['keyframe', 'old_value'],
141
'keyframe-moved': ['keyframe', 'old_value'],
144
def __init__(self, trackobject, element, prop, minimum=None, maximum=None,
146
Loggable.__init__(self)
147
self.debug("track:%r, element:%r, property:%r", trackobject, element, prop)
149
self.trackobject = trackobject
152
minimum = prop.minimum
154
maximum = prop.maximum
155
assert not ((minimum is None) or (maximum is None))
158
self.range = maximum - minimum
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)
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
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)
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
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")
198
if isinstance(time_or_keyframe, Keyframe):
199
keyframe = time_or_keyframe
202
value = self._controller.get(self._property.name,
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
209
# mode = self._controller.get_interpolation_mode()
210
mode = gst.INTERPOLATE_LINEAR
212
keyframe = Keyframe(self)
213
keyframe._time = time_or_keyframe
214
keyframe._value = value
215
keyframe._mode = mode
217
self.debug("time:%s, value:%r, mode:%r",
218
gst.TIME_ARGS(keyframe.time), keyframe.value, keyframe.mode)
220
self._keyframes.append(keyframe)
221
self._keyframes.sort()
223
self._controller.set(self._property.name, keyframe.time, keyframe.value)
225
self.emit("keyframe-added", keyframe)
229
def removeKeyframe(self, keyframe):
230
if keyframe not in self._keyframes:
231
self.debug("This keyframe doesn't belong to me")
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)
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
246
for keyframe in self.keyframes:
247
keyframe.setObjectMode(mode)
248
self._controller.set_interpolation_mode(self._property.name, mode)
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)
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)
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)
267
self._controller.unset(self._property.name, kf.time)
268
self.emit("keyframe-moved", kf, old_value)
270
def getKeyframes(self):
272
for kf in self._keyframes:
276
def getInteriorKeyframes(self):
277
"""Same as above but does not include start, or end points"""
278
for kf in self._keyframes:
281
def getVisibleKeyframes(self):
282
"""Return start, end and any keyframes included in between"""
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):
291
def updateMediaStart(self, start):
292
self._keyframeTimeValueChanged(self.start, start, self.start.value)
293
self.start.setObjectTime(start)
295
def updateMediaStop(self, stop):
296
self._keyframeTimeValueChanged(self.end, stop, self.end.value)
297
self.end.setObjectTime(stop)
299
def valueAt(self, time):
300
return self._controller.get(self._property.name, time)
302
def formatValue(self, value):
303
return self.format(value)
305
keyframes = property(getKeyframes)
308
class TrackObject(Signallable, Loggable):
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'],
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
329
self.stream_type = type(stream)
331
self.timeline_object = None
332
self.interpolators = {}
333
self._rebuild_interpolators = False
334
self._public_priority = priority
337
self.gnl_object = obj = self._makeGnlObject()
341
obj.props.start = start
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
349
obj.props.duration = duration
351
obj.props.media_start = in_point
352
if media_duration != 0:
353
obj.props.media_duration = media_duration
355
obj.props.media_duration = duration
357
obj.props.priority = priority
359
self._connectToSignals(obj)
360
self._updatePriority(self._public_priority)
362
def getInterpolator(self, property_name):
363
self._maybeBuildInterpolators()
366
return self.interpolators[property_name][1]
368
raise TrackError("no interpolator for '%s'" % property_name)
370
def getInterpolators(self):
371
self._maybeBuildInterpolators()
372
return self.interpolators
374
def _maybeBuildInterpolators(self):
375
if not self._rebuild_interpolators:
378
if not list(self.gnl_object.elements()):
379
raise TrackError("makeBin hasn't been called yet")
381
self._rebuild_interpolators = False
383
factory_properties = self.factory.getInterpolatedProperties(self.stream)
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:
394
interpolator = old_interpolators[prop_name][1]
396
if factory_properties[prop_name]:
397
lower, upper, formatstr = factory_properties[prop_name]
399
lower, upper, formatstr = None, None, None
400
interpolator = Interpolator(self, gst_object,
401
gst_object_property, lower, upper, formatstr)
403
# remove and add again the keyframes so they are set on the
405
keyframes = list(interpolator.keyframes)
406
for keyframe in keyframes:
407
interpolator.removeKeyframe(keyframe)
409
interpolator.attachToElementProperty(gst_object_property,
411
interpolator.updateMediaStop(self.out_point)
413
for keyframe in keyframes:
414
interpolator.newKeyframe(keyframe)
416
self.interpolators[gst_object_property.name] = \
417
(gst_object_property, interpolator)
420
self._disconnectFromSignals()
422
self.gnl_object = None
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)
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"))
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,
450
def snapStartDurationTime(self, *args):
454
return self.gnl_object.props.start
456
def setStart(self, position, snap=False):
457
if self.timeline_object is not None:
458
self.timeline_object.setStart(position, snap)
463
self.setObjectStart(position)
465
def setObjectStart(self, position):
466
if self.gnl_object.props.start != position:
467
self.gnl_object.props.start = position
469
start = property(_getStart, setStart)
471
def _getActive(self):
472
return self.gnl_object.props.active
474
def setActive(self, active):
475
self.gnl_object.props.active = active
477
active = property(_getActive, setActive)
479
def _getDuration(self):
480
return self.gnl_object.props.duration
482
def setDuration(self, position, snap=False):
483
if self.timeline_object is not None:
484
self.timeline_object.setDuration(position, snap)
489
self.setObjectDuration(position)
491
def setObjectDuration(self, position):
492
if self.gnl_object.props.duration != position:
493
self.gnl_object.props.duration = position
495
duration = property(_getDuration, setDuration)
497
def _getInPoint(self):
498
return self.gnl_object.props.media_start
500
def setInPoint(self, position, snap=False):
501
if self.timeline_object is not None:
503
self.timeline_object.setInPoint(position, snap)
505
self.setObjectInPoint(position)
507
def setObjectInPoint(self, value):
508
if self.gnl_object.props.media_start != value:
509
self.gnl_object.props.media_start = value
511
in_point = property(_getInPoint, setInPoint)
513
def _getOutPoint(self):
514
return self.gnl_object.props.media_stop
516
out_point = property(_getOutPoint)
518
def _getMediaDuration(self):
519
return self.gnl_object.props.media_duration
521
def setMediaDuration(self, position, snap=False):
522
if self.timeline_object is not None:
523
self.timeline_object.setMediaDuration(position, snap)
525
self.setObjectMediaDuration(position)
527
def setObjectMediaDuration(self, position):
528
if self.gnl_object.props.media_duration != position:
529
self.gnl_object.props.media_duration = position
531
media_duration = property(_getMediaDuration, setMediaDuration)
534
return self.gnl_object.props.rate
536
rate = property(_getRate)
538
def _getPriority(self):
539
return self._public_priority
541
def setPriority(self, priority):
542
if self.timeline_object is not None:
543
self.timeline_object.setPriority(priority)
545
self.setObjectPriority(priority)
547
def setObjectPriority(self, priority):
548
if priority != self._public_priority:
549
self._updatePriority(priority)
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)
558
def _updatePriority(self, priority):
559
true_priority = self._getTruePriority(priority)
561
if self.gnl_object.props.priority != true_priority:
562
self.gnl_object.props.priority = true_priority
564
self.debug("New priority: %r", self.gnl_object.props.priority)
566
priority = property(_getPriority, setPriority)
568
def _getStagger(self):
571
stagger = property(_getStagger)
573
def trimStart(self, position, snap=False):
574
if self.timeline_object is not None:
575
self.timeline_object.trimStart(position, snap)
577
self.trimObjectStart(position)
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)
584
delta = position - self.start
585
in_point = self.in_point
588
return in_point, position
590
def trimObjectStart(self, position):
591
in_point, position = self._getTrimInpointAndPosition(position)
592
new_duration = max(0, self.start + self.duration - position)
594
self.setObjectStart(position)
595
self.setObjectDuration(new_duration)
596
self.setObjectInPoint(in_point)
597
self.setObjectMediaDuration(new_duration)
599
def split(self, position, snap=False):
600
if self.timeline_object is not None:
601
return self.timeline_object.split(position, snap)
603
return self.splitObject(position)
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))
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()
621
if kf.getTime() >= (position - start + in_point):
622
duplicates.append(kf)
623
for kf in duplicates:
626
for prop, i in other.interpolators.itervalues():
627
value = i.valueAt(position)
628
i.start.setValue(value)
629
keyframes = i.getInteriorKeyframes()
632
if kf.getTime() <= (position - start + in_point):
633
duplicates.append(kf)
634
for kf in duplicates:
637
other.trimObjectStart(position)
638
self.setObjectDuration(position - self.gnl_object.props.start)
639
self.setObjectMediaDuration(position - self.gnl_object.props.start)
642
# True when the track object is part of the timeline's current selection
645
def _getSelected(self):
646
return self.__selected
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)
654
selected = property(_getSelected)
657
if self.stream is None:
659
if self.gnl_object is None:
662
bin = self.factory.makeBin(self.stream)
663
self.gnl_object.add(bin)
664
self._rebuild_interpolators = True
665
self._maybeBuildInterpolators()
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
674
def _notifyStartCb(self, obj, pspec):
675
self.emit('start-changed', obj.props.start)
677
def _notifyDurationCb(self, obj, pspec):
678
self.emit('duration-changed', obj.props.duration)
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)
686
def _notifyMediaDurationCb(self, obj, pspec):
687
self.emit('media-duration-changed', obj.props.media_duration)
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)
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)
706
def _notifyActiveCb(self, obj, pspec):
707
self.emit('active-changed', obj.props.active)
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)
722
def _disconnectFromSignals(self):
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)
731
def _makeGnlObject(self):
732
raise NotImplementedError()
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)
742
class SourceTrackObject(TrackObject):
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
754
class TrackEffect(TrackObject):
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
765
def _getTrimInpointAndPosition(self, position):
766
# Effect inpoint is meaningless, and we can resize theme
771
other = TrackObject.copy(self)
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)
783
def _getTruePriority(self, priority):
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
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)
794
def getElement(self):
796
Permit to get the gst.Element inside the gnl_object that correspond
799
effect_bin = list(self.gnl_object.elements())[0]
800
return effect_bin.get_by_name("effect")
803
class Transition(Signallable):
806
"start-changed": ["start"],
807
"duration-changed": ["duration"],
808
"priority-changed": ["priority"],
811
def __init__(self, a, b):
817
self._makeGnlObject()
818
self._connectToTrackObjects(a, b)
820
def _makeGnlObject(self):
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()
834
def _updateStartDuration(self, *unused):
836
end = self.a.start + self.a.duration
837
duration = max(0, end - start)
839
if start != self.start:
840
self._updateOperationStart(start)
842
self.emit("start-changed", start)
844
if duration != self.duration:
845
self._updateOperationDuration(duration)
846
self.duration = duration
847
self.emit("duration-changed", duration)
849
self._updateController()
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)
858
def _staggerChanged(self, *unused):
859
self._updateController()
861
def _updateController(self):
864
def _updateOperationStart(self, start):
867
def _updateOperationDuration(self, duration):
870
def _updateOperationPriority(self, priority):
873
def addThyselfToComposition(self, composition):
876
def removeThyselfFromComposition(self, composition):
880
class VideoTransition(Transition):
882
caps = gst.Caps("video/x-raw-yuv,format=(fourcc)AYUV")
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)
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
894
def addThyselfToComposition(self, composition):
895
composition.add(self.operation)
897
def removeThyselfFromComposition(self, composition):
898
composition.remove(self.operation)
899
self.operation.set_state(gst.STATE_NULL)
901
def _updateOperationStart(self, start):
902
self.operation.props.start = start
904
def _updateOperationDuration(self, duration):
905
self.operation.props.duration = duration
906
self.operation.props.media_duration = duration
908
def _updateOperationPriority(self, priority):
909
self.operation.props.priority = 1 + 3 * priority
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)
926
class AudioTransition(Transition):
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)
933
self.a_operation = gst.element_factory_make("gnloperation")
934
self.a_operation.add(trans)
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)
940
self.b_operation = gst.element_factory_make("gnloperation")
941
self.b_operation.add(trans)
943
self.a_operation.props.media_start = 0
945
self.b_operation.props.media_start = 0
947
def addThyselfToComposition(self, composition):
948
composition.add(self.a_operation, self.b_operation)
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)
956
def _updateOperationStart(self, start):
957
self.a_operation.props.start = start
958
self.b_operation.props.start = start
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
966
def _staggerChanged(self, *unused):
967
self._updateOperationPriority(self.priority)
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
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)
980
class Track(Signallable, Loggable):
981
logCategory = "track"
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'],
993
def __init__(self, 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
1007
self.mixer = self._getMixerForStream(stream)
1009
self.composition.add(self.mixer)
1010
self.default_sources = []
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)
1021
ret.gnl_object.props.priority = 2 ** 32 - 1
1022
self.debug("Track Object %r, priority: %r:", ret, ret.gnl_object.props.priority)
1025
def _getDefaultVideoTrackObject(self, stream):
1026
factory = VideoTestSourceFactory(pattern='black')
1027
factory.setFilterCaps(stream.caps)
1028
track_object = SourceTrackObject(factory, stream)
1032
def _getDefaultAudioTrackObject(self, stream):
1033
factory = AudioTestSourceFactory(wave='silence')
1034
track_object = SourceTrackObject(factory, stream)
1038
def _defaultSourceBlockedCb(self, pad, blocked):
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())
1048
def _sourceDebug(self, source):
1050
return "%s [%s - %s]" % (source, t(source.props.start),
1051
t(source.props.start + source.props.duration))
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)
1061
self.default_sources = []
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)
1072
def updateDefaultSources(self):
1073
if not self.composition.props.update:
1076
self.updateDefaultSourcesReal()
1078
def updateCaps(self, caps):
1079
self.stream.caps = caps
1080
self.updateDefaultSources()
1082
def updateDefaultSourcesReal(self):
1083
update = self.composition.props.update
1084
self.composition.props.update = True
1086
self._updateDefaultSourcesUnchecked()
1088
self.composition.props.update = update
1090
def _getMixerForStream(self, stream):
1091
if isinstance(stream, AudioStream):
1092
gnl = gst.element_factory_make("gnloperation", "top-level-audio-mixer")
1095
gnl.props.expandable = True
1096
gnl.props.priority = 0
1097
self.debug("Props priority: %s", gnl.props.priority)
1099
elif isinstance(stream, VideoStream):
1100
gnl = gst.element_factory_make("gnloperation", "top-level-video-mixer")
1101
m = SmartVideomixerBin(self)
1103
gnl.props.expandable = True
1104
gnl.props.priority = 0
1105
gnl.connect("input-priority-changed",
1106
self._videoInputPriorityChangedCb, m)
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)
1115
def _getStart(self):
1116
return self.composition.props.start
1118
def getPreviousTrackObject(self, obj, priority=-1):
1119
prev = getPreviousObject(obj, self.track_objects, priority)
1121
raise TrackError("no previous track object", obj)
1125
def getNextTrackObject(self, obj, priority=-1):
1126
next = getNextObject(obj, self.track_objects, priority)
1128
raise TrackError("no next track object", obj)
1132
start = property(_getStart)
1134
def _getDuration(self):
1135
return self.composition.props.duration
1137
duration = property(_getDuration)
1139
def _getMaxPriority(self):
1140
return self._max_priority
1142
max_priority = property(_getMaxPriority)
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)
1150
def _connectToTrackObjectSignals(self, track_object):
1151
track_object.connect("priority-changed", self._trackObjectPriorityCb)
1153
def _disconnectTrackObjectSignals(self, track_object):
1154
track_object.disconnect_by_function(self._trackObjectPriorityCb)
1156
def addTrackObject(self, track_object):
1157
if track_object.track is not None:
1160
if track_object.gnl_object in list(self.composition):
1162
track_object.makeBin()
1164
track_object.track = self
1166
start_insort_right(self.track_objects, track_object)
1167
self.updateDefaultSources()
1170
self.composition.add(track_object.gnl_object)
1171
except gst.AddError:
1174
self._connectToTrackObjectSignals(track_object)
1176
self._updateMaxPriority()
1177
self._connectToTrackObject(track_object)
1179
self.emit('track-object-added', track_object)
1180
if self._update_transitions:
1181
self.updateTransitions()
1183
def _justRemoveTrackObject(self, track_object):
1184
if track_object.track is None:
1188
self.composition.remove(track_object.gnl_object)
1189
track_object.gnl_object.set_state(gst.STATE_NULL)
1190
except gst.RemoveError:
1193
self._disconnectFromTrackObject(track_object)
1194
track_object.releaseBin()
1196
self.track_objects.remove(track_object)
1197
track_object.track = None
1199
self._disconnectTrackObjectSignals(track_object)
1200
self.emit('track-object-removed', track_object)
1202
def removeTrackObject(self, track_object):
1203
self._justRemoveTrackObject(track_object)
1205
self._updateMaxPriority()
1206
self.updateDefaultSources()
1208
if self._update_transitions:
1209
self.updateTransitions()
1211
def removeMultipleTrackObjects(self, track_objects):
1212
for track_object in track_objects:
1213
self._justRemoveTrackObject(track_object)
1215
self._updateMaxPriority()
1216
self.updateDefaultSources()
1218
if self._update_transitions:
1219
self.updateTransitions()
1221
def removeAllTrackObjects(self):
1222
self.removeMultipleTrackObjects(list(self.track_objects))
1224
def _updateMaxPriority(self):
1225
priorities = [track_object.priority for track_object in
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)
1235
def _compositionStartChangedCb(self, composition, pspec):
1236
start = composition.props.start
1237
self.emit('start-changed', start)
1239
def _compositionDurationChangedCb(self, composition, pspec):
1240
duration = composition.props.duration
1241
self.emit('duration-changed', duration)
1243
def _trackObjectPriorityChangedCb(self, track_object, priority):
1244
self._updateMaxPriority()
1246
def _trackObjectStartChangedCb(self, track_object, start):
1247
self.track_objects.remove(track_object)
1248
start_insort_right(self.track_objects, track_object)
1250
def _trackObjectDurationChangedCb(self, track_object, duration):
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)
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)
1266
def enableUpdates(self):
1267
self.composition.props.update = True
1268
self.updateDefaultSources()
1269
self._update_transitions = True
1270
self.updateTransitions()
1272
def disableUpdates(self):
1273
self.composition.props.update = False
1274
self._update_transitions = False
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:
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)
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)
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)
1301
def getValidTransitionSlots(self, objs):
1312
end = obj.start + obj.duration
1313
if obj.start >= duration:
1317
elif end >= duration and obj.start >= safe:
1318
slots.append((prev, obj))
1322
elif end >= duration and obj.start < safe:
1328
elif end < duration and obj.start >= safe:
1331
elif end < duration and obj.start < safe:
1338
valid_arrangement = True
1340
def updateTransitions(self):
1341
# create all new transitions
1344
for layer in self.getTrackObjectsGroupedByLayer():
1347
slots, is_valid = self.getValidTransitionSlots(layer)
1348
all_valid &= is_valid
1352
b.updatePosition(pos)
1355
a.updatePosition(pos)
1356
b.updatePosition(pos + 1)
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