[pitivi] This commit makes addition of keyframes possible.
- From: Jean-François Fortin Tam <jfft src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] This commit makes addition of keyframes possible.
- Date: Mon, 8 Jul 2013 00:51:26 +0000 (UTC)
commit dc9cefee08cd599a3692b7624dc1241cc2caca27
Author: Mathieu Duponchelle <mathieu duponchelle epitech eu>
Date: Tue Apr 23 03:53:30 2013 +0200
This commit makes addition of keyframes possible.
Conflicts:
pitivi/timeline/elements.py
pitivi/timeline/timeline.py
pitivi/clipproperties.py | 18 ---
pitivi/timeline/elements.py | 244 ++++++++++++++++++++++++++++++++++++++++---
pitivi/timeline/timeline.py | 15 ++-
pitivi/utils/ui.py | 2 +
pitivi/utils/widgets.py | 32 ++++++-
5 files changed, 270 insertions(+), 41 deletions(-)
---
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index d8f7b2a..1cf400c 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -167,12 +167,6 @@ class EffectProperties(Gtk.Expander, Loggable):
removeEffectButton.set_is_important(True)
self._toolbar.insert(removeEffectButton, 0)
- showKeyframesButton = Gtk.ToolButton()
- showKeyframesButton.set_icon_name("document-properties-symbolic")
- showKeyframesButton.set_label(_("Show keyframes"))
- showKeyframesButton.set_is_important(True)
- self._toolbar.insert(showKeyframesButton, 0)
-
# Treeview to display a list of effects (checkbox, effect type and name)
self.treeview_scrollwin = Gtk.ScrolledWindow()
self.treeview_scrollwin.set_policy(Gtk.PolicyType.NEVER,
@@ -244,7 +238,6 @@ class EffectProperties(Gtk.Expander, Loggable):
self.treeview.connect("query-tooltip", self._treeViewQueryTooltipCb)
self._vcontent.connect("notify", self._vcontentNotifyCb)
removeEffectButton.connect("clicked", self._removeEffectClicked)
- showKeyframesButton.connect("clicked", self._showKeyframesClicked)
self.app.connect("new-project-loaded", self._newProjectLoadedCb)
self.connect('notify::expanded', self._expandedCb)
self.connected = False
@@ -317,17 +310,6 @@ class EffectProperties(Gtk.Expander, Loggable):
COL_TRACK_EFFECT)
self._removeEffect(effect)
- def _showKeyframesClicked(self, event):
- if not self.clips:
- return
- effect = self.storemodel.get_value(self.selection.get_selected()[1],
- COL_TRACK_EFFECT)
- track_type = effect.get_track_type()
- for track_element in effect.get_parent().get_children():
- print track_element
- if hasattr(track_element, "ui_element") and track_type == track_element.get_track_type():
- track_element.ui_element.showKeyframes()
-
def _removeEffect(self, effect):
self.app.action_log.begin("remove effect")
self._cleanCache(effect)
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 7a3621c..66dd83f 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -26,15 +26,18 @@ Every GES element which name could be mistaken with a UI element
is prefixed with a little b, example : bTimeline
"""
+import cairo
+import math
+
import os
import cairo
-from gi.repository import Clutter, Cogl, GES, Gdk, GstController
+from gi.repository import Clutter, Cogl, GES, Gdk, Gst, GstController
from pitivi.utils.timeline import Zoomable, EditingContext, Selection, SELECT, UNSELECT, Selected
from previewers import VideoPreviewer, BORDER_WIDTH
import pitivi.configure as configure
-from pitivi.utils.ui import EXPANDED_SIZE, SPACING
+from pitivi.utils.ui import EXPANDED_SIZE, SPACING, KEYFRAME_SIZE, CONTROL_WIDTH
def get_preview_for_object(bElement, timeline):
@@ -344,6 +347,9 @@ class TimelineElement(Clutter.Actor, Zoomable):
self.bElement.ui_element = self
self.track_type = self.bElement.get_track_type() # This won't change
self.isDragged = False
+ self.lines = []
+ self.keyframes = []
+ self.source = None
size = self.bElement.get_duration()
self._createBackground(track)
@@ -399,6 +405,66 @@ class TimelineElement(Clutter.Actor, Zoomable):
pass
self.restore_easing_state()
+ def addKeyframe(self, value, timestamp):
+ self.source.set(timestamp, value)
+ self.updateKeyframes()
+
+ def showKeyframes(self, effect, propname):
+ binding = effect.get_control_binding(propname.name)
+ if not binding:
+ source = GstController.InterpolationControlSource()
+ source.props.mode = GstController.InterpolationMode.LINEAR
+ if not (effect.set_control_source(source, propname.name, "direct")):
+ print "There was something like a problem captain"
+ return
+ val = float(propname.default_value) / (propname.maximum - propname.minimum)
+ source.set(self.bElement.props.in_point, val)
+ source.set(self.bElement.props.duration, val)
+ binding = effect.get_control_binding(propname.name)
+ self.binding = binding
+ self.source = self.binding.props.control_source
+ self.prop = propname
+ self.updateKeyframes()
+
+ def setKeyframePosition(self, keyframe, value):
+ x = self.nsToPixel(value.timestamp) - KEYFRAME_SIZE / 2
+ y = EXPANDED_SIZE - (value.value * EXPANDED_SIZE)
+
+ keyframe.set_z_position(2)
+ keyframe.set_position(x, y)
+
+ def drawLines(self):
+ for line in self.lines:
+ self.remove_child(line)
+
+ self.lines = []
+
+ lastKeyframe = None
+ for keyframe in self.keyframes:
+ if lastKeyframe:
+ self._createLine(keyframe.value, lastKeyframe.value)
+ lastKeyframe = keyframe
+
+ def updateKeyframes(self):
+ if not self.source:
+ return
+
+ values = self.source.get_all()
+ lastPoint = None
+
+ for keyframe in self.keyframes:
+ self.remove_child(keyframe)
+
+ self.keyframes = []
+
+ for value in values:
+ self._createKeyframe(value)
+ lastPoint = value
+
+ self.drawLines()
+
+ # private API
+
def update(self, ease):
size = self.bElement.get_duration()
self.set_size(self.nsToPixel(size), EXPANDED_SIZE, ease)
@@ -409,10 +475,34 @@ class TimelineElement(Clutter.Actor, Zoomable):
brother.isDragged = dragged
self.isDragged = dragged
- def showKeyframes(self, propname):
- pass
-
- # Internal API
+ def _setKeyframePosition(self, keyframe, value):
+ x = self.nsToPixel(value.timestamp) - KEYFRAME_SIZE / 2
+ y = EXPANDED_SIZE - (value.value * EXPANDED_SIZE)
+
+ keyframe.set_z_position(2)
+ keyframe.set_position(x, y)
+
+ def _createKeyframe(self, value):
+ keyframe = Keyframe(self, value)
+
+ self.add_child(keyframe)
+ self.keyframes.append(keyframe)
+ self.setKeyframePosition(keyframe, value)
+
+ def _createLine(self, value, lastPoint):
+ line = Line(self)
+ adj = self.nsToPixel(value.timestamp - lastPoint.timestamp)
+ opp = (lastPoint.value - value.value) * EXPANDED_SIZE
+ hyp = math.sqrt(adj ** 2 + opp ** 2)
+ sinX = opp / hyp
+ line.props.width = hyp
+ line.props.height = 6
+ line.props.rotation_angle_z = math.degrees(math.asin(sinX))
+ line.props.x = self.nsToPixel(lastPoint.timestamp)
+ line.props.y = EXPANDED_SIZE - (EXPANDED_SIZE * lastPoint.value)
+ self.lines.append(line)
+ self.add_child(line)
+ line.canvas.invalidate()
def _createGhostclip(self):
pass
@@ -470,6 +560,7 @@ class TimelineElement(Clutter.Actor, Zoomable):
def zoomChanged(self):
self.update(True)
+ self.updateKeyframes()
# Callbacks
@@ -515,6 +606,135 @@ class Gradient(Clutter.Actor):
cr.fill()
+class Line(Clutter.Actor):
+ def __init__(self, timelineElement):
+ Clutter.Actor.__init__(self)
+
+ self.timelineElement = timelineElement
+
+ self.canvas = Clutter.Canvas()
+ self.canvas.set_size(1000, 4)
+ self.canvas.connect("draw", self._drawCb)
+ self.set_content(self.canvas)
+ self.set_reactive(True)
+
+ self.connect("button-press-event", self._clickedCb)
+ self.connect("motion-event", self._motionEventCb)
+ self.connect("enter-event", self._enterEventCb)
+ self.connect("leave-event", self._leaveEventCb)
+
+ def _drawCb(self, canvas, cr, width, height):
+ cr.set_operator(cairo.OPERATOR_CLEAR)
+ cr.paint()
+ cr.set_operator(cairo.OPERATOR_OVER)
+ cr.set_source_rgb(255, 0, 0)
+ cr.set_line_width(2.)
+ cr.move_to(0, 2)
+ cr.line_to(width, 2)
+ cr.stroke()
+
+ def transposeXY(self, x, y):
+ x -= self.timelineElement.props.x + CONTROL_WIDTH - self.timelineElement.timeline._scroll_point.x
+ y -= self.timelineElement.props.y
+ return x, y
+
+ def _clickedCb(self, actor, event):
+ x, y = self.transposeXY(event.x, event.y)
+ value = 1.0 - (y / EXPANDED_SIZE)
+ value = max(0.0, value)
+ value = min(1.0, value)
+ timestamp = Zoomable.pixelToNs(x)
+ self.timelineElement.addKeyframe(value, timestamp)
+
+ def _enterEventCb(self, actor, event):
+ self.timelineElement.set_reactive(False)
+
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1))
+
+ def _leaveEventCb(self, actor, event):
+ self.timelineElement.set_reactive(True)
+
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.ARROW))
+
+ def _motionEventCb(self, actor, event):
+ pass
+
+
+class Keyframe(Clutter.Actor):
+ def __init__(self, timelineElement, value):
+ Clutter.Actor.__init__(self)
+
+ self.value = value
+ self.timelineElement = timelineElement
+
+ self.set_size(KEYFRAME_SIZE, KEYFRAME_SIZE)
+ self.set_background_color(Clutter.Color.new(0, 255, 0, 255))
+
+ self.dragAction = Clutter.DragAction()
+ self.add_action(self.dragAction)
+
+ self.dragAction.connect("drag-begin", self._dragBeginCb)
+ self.dragAction.connect("drag-end", self._dragEndCb)
+ self.dragAction.connect("drag-progress", self._dragProgressCb)
+ self.connect("key-press-event", self._keyPressEventCb)
+ self.connect("enter-event", self._enterEventCb)
+ self.connect("leave-event", self._leaveEventCb)
+ self.set_reactive(True)
+
+ def _keyPressEventCb(self, actor, event):
+ print event, dir(event)
+
+ def _enterEventCb(self, actor, event):
+ self.timelineElement.set_reactive(False)
+ self.set_background_color(Clutter.Color.new(0, 0, 0, 255))
+
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1))
+
+ def _leaveEventCb(self, actor, event):
+ self.timelineElement.set_reactive(True)
+ self.set_background_color(Clutter.Color.new(0, 255, 0, 255))
+
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.ARROW))
+
+ def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
+ self.dragBeginStartX = event_x
+ self.dragBeginStartY = event_y
+ self.lastTs = self.value.timestamp
+ self.valueStart = self.value.value
+ self.tsStart = self.value.timestamp
+ self.duration = self.timelineElement.bElement.props.duration
+ self.inpoint = self.timelineElement.bElement.props.in_point
+ self.start = self.timelineElement.bElement.props.start
+
+ def _dragProgressCb(self, action, actor, delta_x, delta_y):
+ coords = self.dragAction.get_motion_coords()
+ delta_x = coords[0] - self.dragBeginStartX
+ delta_y = coords[1] - self.dragBeginStartY
+ newTs = self.tsStart + Zoomable.pixelToNs(delta_x)
+ newValue = self.valueStart - (delta_y / EXPANDED_SIZE)
+
+ if newTs < self.inpoint or newTs > self.duration:
+ return False
+
+ if newValue < 0.0 or newValue > 1.0:
+ return False
+
+ self.timelineElement.source.unset(self.lastTs)
+ if (self.timelineElement.source.set(newTs, newValue)):
+ self.value = Gst.TimedValue()
+ self.value.timestamp = newTs
+ self.value.value = newValue
+
+ self.lastTs = newTs
+
+ self.timelineElement.setKeyframePosition(self, self.value)
+ self.timelineElement.keyframes = sorted(self.timelineElement.keyframes, key=lambda keyframe:
keyframe.value.timestamp)
+ self.timelineElement.drawLines()
+ # This will update the viewer. nifty.
+ self.timelineElement.timeline._container.seekInPosition(newTs + self.start)
+
+ return False
+
+ def _dragEndCb(self, action, actor, event_x, event_y, modifiers):
+ pass
+
+
class URISourceElement(TimelineElement):
def __init__(self, bElement, track, timeline):
TimelineElement.__init__(self, bElement, track, timeline)
@@ -525,17 +745,6 @@ class URISourceElement(TimelineElement):
self.rightHandle.hide()
self.leftHandle.hide()
- def showKeyframes(self, effect, propname):
- binding = self.bElement.get_control_binding(propname.name)
- if not binding:
- source = GstController.InterpolationControlSource()
- if not (effect.set_control_source(source, propname.name, "direct")):
- print "There was something like a problem captain"
- return
- binding = effect.get_control_binding(propname.name)
- print binding
- print propname.default_value
-
# private API
def _createGhostclip(self):
@@ -570,6 +779,7 @@ class URISourceElement(TimelineElement):
def _clickedCb(self, action, actor):
#TODO : Let's be more specific, masks etc ..
self.timeline.selection.setToObj(self.bElement, SELECT)
+ return False
def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
mode = self.timeline._container.getEditionMode()
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 1b5f285..c460c72 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -665,6 +665,10 @@ class Timeline(Gtk.VBox, Zoomable):
else:
self._scrollToPosition(position)
+ def seekInPosition(self, position):
+ self.pressed = True
+ self._seeker.seek(position)
+
def setTimeline(self, bTimeline):
self.bTimeline = bTimeline
self.timeline.selection.connect("selection-changed", self._selectionChangedCb)
@@ -685,6 +689,7 @@ class Timeline(Gtk.VBox, Zoomable):
self.embed = GtkClutter.Embed()
self.embed.get_accessible().set_name("timeline canvas") # for dogtail
self.stage = self.embed.get_stage()
+ perspective = self.stage.get_perspective()
self.timeline = TimelineStage(self)
self.controls = ControlContainer(self.timeline)
@@ -692,7 +697,9 @@ class Timeline(Gtk.VBox, Zoomable):
self.shiftMask = False
self.controlMask = False
- # TODO: make the bg a gradient from (0, 0, 0, 255) to (50, 50, 50, 255)
+ perspective.fov_y = 90.
+ self.stage.set_perspective(perspective)
+
self.stage.set_background_color(Clutter.Color.new(31, 30, 33, 255))
self.timeline.set_position(CONTROL_WIDTH, 0)
self.controls.set_position(0, 0)
@@ -1032,7 +1039,7 @@ class Timeline(Gtk.VBox, Zoomable):
def _playPause(self, unused_action):
self.app.current.pipeline.togglePlayback()
- def _transposeXY(self, x, y):
+ def transposeXY(self, x, y):
height = self.ruler.get_allocation().height
x += self.timeline.get_scroll_point().x
return x - CONTROL_WIDTH, y - height
@@ -1236,7 +1243,7 @@ class Timeline(Gtk.VBox, Zoomable):
if self.zoomed_fitted:
self._setBestZoomRatio()
else:
- x, y = self._transposeXY(x, y)
+ x, y = self.transposeXY(x, y)
self.scrollToPosition(Zoomable.pixelToNs(x))
else:
actor = self.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y)
@@ -1257,7 +1264,7 @@ class Timeline(Gtk.VBox, Zoomable):
widget.drag_get_data(context, target, time)
Gdk.drag_status(context, 0, time)
else:
- x, y = self._transposeXY(x, y)
+ x, y = self.transposeXY(x, y)
# dragged from the media library
if not self.timeline.ghostClips and self.isDraggedClip:
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index f07f532..76e976a 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -64,6 +64,8 @@ CANVAS_SPACING = 21
CONTROL_WIDTH = 250
+KEYFRAME_SIZE = 8
+
# Layer creation blocking time in s
LAYER_CREATION_BLOCK_TIME = 0.2
diff --git a/pitivi/utils/widgets.py b/pitivi/utils/widgets.py
index 793104e..77a09dc 100644
--- a/pitivi/utils/widgets.py
+++ b/pitivi/utils/widgets.py
@@ -839,6 +839,8 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
If there are no properties, returns a table containing the label
"No properties."
"""
+ self.bindings = {}
+ self.showKeyframesButtons = []
is_effect = False
if isinstance(self.element, GES.Effect):
is_effect = True
@@ -892,6 +894,11 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
table.attach(label, 0, 1, y, y + 1, xoptions=Gtk.AttachOptions.FILL,
yoptions=Gtk.AttachOptions.FILL)
table.attach(widget, 1, 2, y, y + 1, yoptions=Gtk.AttachOptions.FILL)
+ if not isinstance(widget, ToggleWidget) and not isinstance(widget, ChoiceWidget):
+ button = self._getShowKeyframesButton(prop)
+ self.showKeyframesButtons.append(button)
+ table.attach(button, 3, 4, y, y + 1, xoptions=Gtk.AttachOptions.FILL,
yoptions=Gtk.AttachOptions.FILL)
+
if hasattr(prop, 'blurb'):
widget.set_tooltip_text(prop.blurb)
@@ -899,11 +906,17 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
# The "reset to default" button associated with this property
if default_btn:
+ widget.propName = prop.name.split("-")[0]
+ name = prop.name
+
+ # If this element is controlled, the value means nothing anymore.
+ binding = self.element.get_control_binding(prop.name)
+ if binding:
+ widget.set_sensitive(False)
+ self.bindings[widget] = binding
button = self._getResetToDefaultValueButton(prop, widget)
table.attach(button, 2, 3, y, y + 1, xoptions=Gtk.AttachOptions.FILL,
yoptions=Gtk.AttachOptions.FILL)
self.buttons[button] = widget
- button = self._getShowKeyframesButton(prop)
- table.attach(button, 3, 4, y, y + 1, xoptions=Gtk.AttachOptions.FILL,
yoptions=Gtk.AttachOptions.FILL)
self.element.connect('notify::' + prop.name, self._propertyChangedCb, widget)
@@ -936,6 +949,11 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
return button
def _showKeyframesClickedCb(self, button, prop):
+ for but in self.showKeyframesButtons:
+ but.set_relief(Gtk.ReliefStyle.NONE)
+
+ button.set_relief(Gtk.ReliefStyle.HALF)
+
effect = self.element
track_type = effect.get_track_type()
for track_element in effect.get_parent().get_children():
@@ -943,6 +961,16 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
track_element.ui_element.showKeyframes(effect, prop)
def _defaultBtnClickedCb(self, button, widget):
+ binding = self.bindings[widget]
+ if binding:
+ effect = self.element
+ track_type = effect.get_track_type()
+ for track_element in effect.get_parent().get_children():
+ if hasattr(track_element, "ui_element") and track_type == track_element.get_track_type():
+ binding.props.control_source.unset_all()
+ track_element.ui_element.updateKeyframes()
+
+ widget.set_sensitive(True)
widget.setWidgetToDefault()
def getSettings(self, with_default=False):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]