[pitivi] clipproperties: Add a compositing expander
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] clipproperties: Add a compositing expander
- Date: Sat, 2 Apr 2022 00:15:52 +0000 (UTC)
commit b3f916187f929f9b341f9978ac316e1880f32843
Author: Aaron Friesen <maugrift maugrift com>
Date: Fri Apr 1 04:11:11 2022 -0500
clipproperties: Add a compositing expander
Pitivi allows users to manually keyframe the opacity of a clip to create
fade-in and fade-out transitions.
However, this is relatively complex for basic use cases, and can be
tedious to do on a large scale.
This commit adds a compositing expander to the clip properties panel, in
which the user can easily set fade-in and fade-out durations for a clip.
Fixes #1472
data/ui/clipcompositing.ui | 176 ++++++++++++++++++++++++
help/C/transitions.page | 2 +-
pitivi/clip_properties/compositing.py | 221 +++++++++++++++++++++++++++++++
pitivi/clipproperties.py | 7 +
tests/common.py | 11 +-
tests/test_clipproperties_compositing.py | 139 +++++++++++++++++++
6 files changed, 553 insertions(+), 3 deletions(-)
---
diff --git a/data/ui/clipcompositing.ui b/data/ui/clipcompositing.ui
new file mode 100644
index 000000000..64062c41e
--- /dev/null
+++ b/data/ui/clipcompositing.ui
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <object class="GtkActionGroup" id="actiongroup1"/>
+ <object class="GtkAdjustment" id="fade_in_adjustment">
+ <property name="upper">1</property>
+ <property name="step-increment">0.01</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="fade_out_adjustment">
+ <property name="upper">1</property>
+ <property name="step-increment">0.01</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkImage" id="icon_reset1">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">edit-clear-all-symbolic</property>
+ </object>
+ <object class="GtkImage" id="icon_reset2">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">edit-clear-all-symbolic</property>
+ </object>
+ <!-- n-columns=5 n-rows=2 -->
+ <object class="GtkGrid" id="compositing_box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="margin-left">12</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Fade-in:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">seconds</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="adjustment">fade_in_adjustment</property>
+ <property name="climb-rate">0.10</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScale">
+ <property name="width-request">150</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-top">1</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">fade_in_adjustment</property>
+ <property name="round-digits">1</property>
+ <property name="draw-value">False</property>
+ <property name="value-pos">left</property>
+ </object>
+ <packing>
+ <property name="left-attach">3</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Fade-out:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="adjustment">fade_out_adjustment</property>
+ <property name="climb-rate">0.10</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">seconds</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScale">
+ <property name="width-request">150</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">fade_out_adjustment</property>
+ <property name="round-digits">1</property>
+ <property name="draw-value">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">3</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="reset_fade_in_button">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="focus-on-click">False</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text" translatable="yes">Reset fade-in</property>
+ <property name="image">icon_reset1</property>
+ <property name="relief">none</property>
+ </object>
+ <packing>
+ <property name="left-attach">4</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="reset_fade_out_button">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="focus-on-click">False</property>
+ <property name="receives-default">True</property>
+ <property name="tooltip-text" translatable="yes">Reset fade-out</property>
+ <property name="image">icon_reset2</property>
+ <property name="relief">none</property>
+ </object>
+ <packing>
+ <property name="left-attach">4</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/help/C/transitions.page b/help/C/transitions.page
index ccf8aed50..5a5cf8179 100644
--- a/help/C/transitions.page
+++ b/help/C/transitions.page
@@ -62,7 +62,7 @@
</section>
<section id="fades">
<title>Fade-ins and fade-outs</title>
- <p>You can create fade-in and fade-out transitions in single clips by using keyframes controlling the
clip's opacity. For more information on keyframes, see <link xref="keyframecurves">Keyframe curves</link>. To
understand opacity, see <link xref="layers">Understanding layers</link>. The following images illustrate how
to fade a clip to black:</p>
+ <p>You can create fade-in and fade-out transitions in single clips by specifying their duration in the
<gui>Compositing</gui> section of the <gui>Clip Properties</gui> panel. These can be fine-tuned by
interacting with the <link xref="keyframecurves">Keyframe curve</link> controlling the clip's opacity. To
understand opacity, see <link xref="layers">Understanding layers</link>. The following images illustrate how
to fade a clip to black by editing the clip's keyframes:</p>
<figure>
<title>Keyframe curves controlling the video opacity</title>
<desc>The default curve is flat indicating the same opacity at any position within the clip.</desc>
diff --git a/pitivi/clip_properties/compositing.py b/pitivi/clip_properties/compositing.py
new file mode 100644
index 000000000..77e15eb93
--- /dev/null
+++ b/pitivi/clip_properties/compositing.py
@@ -0,0 +1,221 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2021, Tyler Senne <tsenne2 huskers unl edu>
+# Copyright (c) 2021, Michael Ervin <michael ervin huskers unl edu>
+# Copyright (c) 2021, Aaron Friesen <afriesen4 huskers unl edu>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, see <http://www.gnu.org/licenses/>.
+import os
+from gettext import gettext as _
+from typing import Iterable
+from typing import Optional
+
+from gi.repository import GES
+from gi.repository import Gst
+from gi.repository import GstController
+from gi.repository import Gtk
+
+from pitivi.configure import get_ui_dir
+from pitivi.undo.timeline import CommitTimelineFinalizingAction
+from pitivi.utils.loggable import Loggable
+from pitivi.utils.misc import disconnect_all_by_func
+
+
+# The threshold for the lower value of a keyframe segment to consider it a fade.
+FADE_OPACITY_THRESHOLD = 0.9
+
+
+class CompositingProperties(Gtk.Expander, Loggable):
+ """Widget for setting the opacity-related properties of a clip.
+
+ Attributes:
+ app (Pitivi): The app.
+ """
+
+ def __init__(self, app: Gtk.Application) -> None:
+ Gtk.Expander.__init__(self)
+ Loggable.__init__(self)
+
+ self.app: Gtk.Application = app
+
+ self.set_expanded(True)
+ self.set_label(_("Compositing"))
+
+ builder = Gtk.Builder()
+ builder.add_from_file(os.path.join(get_ui_dir(), "clipcompositing.ui"))
+
+ self.add(builder.get_object("compositing_box"))
+ self._fade_in_adjustment = builder.get_object("fade_in_adjustment")
+ self._fade_out_adjustment = builder.get_object("fade_out_adjustment")
+ reset_fade_in_button = builder.get_object("reset_fade_in_button")
+ reset_fade_out_button = builder.get_object("reset_fade_out_button")
+
+ self._video_source: Optional[GES.VideoSource] = None
+ self._control_source: Optional[Gst.ControlSource] = None
+
+ self._fade_in: int = 0
+ self._fade_out: int = 0
+
+ self._applying_fade: bool = False
+ self._updating_adjustments: bool = False
+
+ self._fade_in_adjustment.connect("value-changed", self.__fade_in_adjustment_changed_cb)
+ self._fade_out_adjustment.connect("value-changed", self.__fade_out_adjustment_changed_cb)
+ reset_fade_in_button.connect("pressed", self.__reset_fade_in_cb)
+ reset_fade_out_button.connect("pressed", self.__reset_fade_out_cb)
+
+ def set_source(self, video_source: GES.VideoSource) -> None:
+ if self._control_source:
+ disconnect_all_by_func(self._control_source, self.__keyframe_changed_cb)
+ self._video_source.disconnect_by_func(self.__keyframe_changed_cb)
+ self._control_source = None
+
+ self._video_source = video_source
+
+ if self._video_source:
+ assert isinstance(self._video_source, GES.VideoSource)
+
+ control_binding = self._video_source.get_control_binding("alpha")
+ assert control_binding
+ self._control_source = control_binding.props.control_source
+
+ self._control_source.connect("value-added", self.__keyframe_changed_cb)
+ self._control_source.connect("value-changed", self.__keyframe_changed_cb)
+ self._control_source.connect("value-removed", self.__keyframe_changed_cb)
+ self._video_source.connect("notify::duration", self.__keyframe_changed_cb)
+
+ self._update_adjustments()
+ self.show_all()
+ else:
+ self.hide()
+
+ @property
+ def _duration(self) -> int:
+ return self._video_source.duration
+
+ def _update_adjustments(self) -> None:
+ """Updates the UI to reflect the current opacity keyframes."""
+ assert self._video_source
+ assert self._control_source
+
+ keyframes = self._control_source.get_all()
+ if len(keyframes) < 2:
+ self._fade_in = 0
+ self._fade_out = 0
+ return
+
+ start_opacity = keyframes[0].value
+ end_opacity = keyframes[-1].value
+
+ fade_in = keyframes[-1].timestamp
+ fade_out = keyframes[0].timestamp
+
+ if len(keyframes) >= 3:
+ fade_in = keyframes[1].timestamp
+ fade_out = keyframes[-2].timestamp
+
+ self._fade_in = fade_in if start_opacity <= FADE_OPACITY_THRESHOLD else 0
+ self._fade_out = self._duration - fade_out if end_opacity <= FADE_OPACITY_THRESHOLD else 0
+
+ self._updating_adjustments = True
+ try:
+ self._fade_in_adjustment.props.value = self._fade_in / Gst.SECOND
+ self._fade_out_adjustment.props.value = self._fade_out / Gst.SECOND
+ finally:
+ self._updating_adjustments = False
+
+ self._fade_in_adjustment.props.upper = (self._duration - self._fade_out) / Gst.SECOND
+ self._fade_out_adjustment.props.upper = (self._duration - self._fade_in) / Gst.SECOND
+
+ def __fade_in_adjustment_changed_cb(self, adjustment: Gtk.Adjustment) -> None:
+ if not self._updating_adjustments:
+ fade_timestamp: int = int(self._fade_in_adjustment.props.value * Gst.SECOND)
+ self._move_keyframe(self._fade_in, fade_timestamp, 0, self._duration - self._fade_out)
+ self._update_adjustments()
+
+ def __fade_out_adjustment_changed_cb(self, adjustment: Gtk.Adjustment) -> None:
+ if not self._updating_adjustments:
+ fade_timestamp: int = self._duration - int(self._fade_out_adjustment.props.value * Gst.SECOND)
+ self._move_keyframe(self._duration - self._fade_out, fade_timestamp, self._duration,
self._fade_in)
+ self._update_adjustments()
+
+ def __reset_fade_in_cb(self, button: Gtk.Button) -> None:
+ self._fade_in_adjustment.props.value = 0
+
+ def __reset_fade_out_cb(self, button: Gtk.Button) -> None:
+ self._fade_out_adjustment.props.value = 0
+
+ def __keyframe_changed_cb(self, control_source: GstController.TimedValueControlSource, timed_value:
GstController.ControlPoint) -> None:
+ if not self._applying_fade:
+ self._update_adjustments()
+
+ def _get_keyframe(self, timestamp: int) -> Gst.TimedValue:
+ assert self._control_source
+ for keyframe in self._control_source.get_all():
+ if keyframe.timestamp == timestamp:
+ return keyframe
+ return None
+
+ def _get_keyframes_in_range(self, start: int, end: int) -> Iterable[Gst.TimedValue]:
+ assert self._control_source
+ for keyframe in self._control_source.get_all():
+ if start <= keyframe.timestamp <= end:
+ yield keyframe
+ elif keyframe.timestamp > end:
+ break
+
+ def _move_keyframe(self, current_fade_timestamp: int, fade_timestamp: int, edge_timestamp: int,
middle_timestamp: int) -> None:
+ """Moves a fade keyframe.
+
+ Args:
+ current_fade_timestamp: The current position of the keyframe.
+ fade_timestamp: The new position of the keyframe.
+ edge_timestamp: 0 for fade-in, duration for fade-out.
+ middle_timestamp: The position of the keyframe of the other fade.
+ """
+ if current_fade_timestamp == fade_timestamp:
+ return
+
+ assert self._video_source
+
+ pipeline = self.app.project_manager.current_project.pipeline
+ with self.app.action_log.started("apply fade",
+ finalizing_action=CommitTimelineFinalizingAction(pipeline),
+ toplevel=True, mergeable=True):
+ self._applying_fade = True
+ try:
+ keyframe = self._get_keyframe(current_fade_timestamp)
+ assert keyframe
+ opacity = keyframe.value
+
+ # Unset the keyframes in the delta interval.
+ start = min(current_fade_timestamp, fade_timestamp)
+ end = max(current_fade_timestamp, fade_timestamp)
+ for keyframe in self._get_keyframes_in_range(start, end):
+ timestamp = keyframe.timestamp
+ if 0 < timestamp < self._duration:
+ self._control_source.unset(timestamp)
+
+ self._control_source.set(fade_timestamp, opacity)
+
+ if current_fade_timestamp == middle_timestamp:
+ # The keyframe at current_fade_timestamp was being used for
+ # both fade-in and fade-out. Make sure it persists.
+ self._control_source.set(middle_timestamp, opacity)
+ elif current_fade_timestamp == edge_timestamp:
+ # The fade has just been created.
+ # Make sure the edge keyframe is transparent.
+ self._control_source.set(edge_timestamp, 0)
+ finally:
+ self._applying_fade = False
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index 39431fd9a..ef5ed92fa 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -35,6 +35,7 @@ from gi.repository import Gtk
from pitivi.clip_properties.alignment import AlignmentEditor
from pitivi.clip_properties.color import ColorProperties
+from pitivi.clip_properties.compositing import CompositingProperties
from pitivi.clip_properties.title import TitleProperties
from pitivi.configure import get_pixmap_dir
from pitivi.configure import get_ui_dir
@@ -121,6 +122,10 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
self.color_expander.set_vexpand(False)
vbox.pack_start(self.color_expander, False, False, 0)
+ self.compositing_expander = CompositingProperties(app)
+ self.compositing_expander.set_vexpand(False)
+ vbox.pack_start(self.compositing_expander, False, False, 0)
+
self.effect_expander = EffectProperties(app)
self.effect_expander.set_vexpand(False)
vbox.pack_start(self.effect_expander, False, False, 0)
@@ -138,6 +143,7 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
self.speed_expander.set_clip(None)
self.title_expander.set_source(None)
self.color_expander.set_source(None)
+ self.compositing_expander.set_source(None)
self.effect_expander.set_clip(None)
self.marker_expander.set_clip(None)
@@ -234,6 +240,7 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
self.speed_expander.set_clip(ges_clip if (not title_source and not color_clip_source) else None)
self.title_expander.set_source(title_source)
self.color_expander.set_source(color_clip_source)
+ self.compositing_expander.set_source(video_source)
self.effect_expander.set_clip(ges_clip)
self.marker_expander.set_clip(ges_clip)
diff --git a/tests/common.py b/tests/common.py
index 3129b7c0b..500f5e5ae 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -305,6 +305,7 @@ def setup_timeline(func):
def setup_clipproperties(func):
+ """Wraps a test, providing a usable ClipProperties."""
def wrapped(self):
app = self.timeline_container.app
@@ -315,10 +316,14 @@ def setup_clipproperties(func):
self.transformation_box._new_project_loaded_cb(None, self.project)
self.speed_box = self.clipproperties.speed_expander
+ self.compositing_box = self.clipproperties.compositing_expander
self.markers_box = self.clipproperties.marker_expander
func(self)
+ del self.markers_box
+ del self.compositing_box
+ del self.speed_box
del self.transformation_box
del self.clipproperties
@@ -539,10 +544,12 @@ class TestCase(unittest.TestCase, Loggable):
self.assertEqual(len(effects), count)
def assert_control_source_values(self, control_source, expected_values, expected_timestamps):
- values = [timed_value.value for timed_value in control_source.get_all()]
+ keyframes = control_source.get_all()
+
+ values = [timed_value.value for timed_value in keyframes]
self.assertListEqual(values, expected_values)
- timestamps = [timed_value.timestamp for timed_value in control_source.get_all()]
+ timestamps = [timed_value.timestamp for timed_value in keyframes]
self.assertListEqual(timestamps, expected_timestamps)
def get_timeline_clips(self):
diff --git a/tests/test_clipproperties_compositing.py b/tests/test_clipproperties_compositing.py
new file mode 100644
index 000000000..8dd73b387
--- /dev/null
+++ b/tests/test_clipproperties_compositing.py
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2021, Tyler Senne <tsenne2 huskers unl edu>
+# Copyright (c) 2021, Michael Ervin <michael ervin huskers unl edu>
+# Copyright (c) 2021, Aaron Friesen <afriesen4 huskers unl edu>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, see <http://www.gnu.org/licenses/>.
+"""Tests for the pitivi.clipproperties module."""
+# pylint: disable=protected-access,no-self-use,import-outside-toplevel,no-member
+from gi.repository import Gst
+
+from tests import common
+
+
+class CompositingPropertiesTest(common.TestCase):
+
+ @common.setup_project_with_clips(assets_names=["1sec_simpsons_trailer.mp4"])
+ @common.setup_clipproperties
+ def test_max_fade_duration(self):
+ fade_in_adjustment = self.compositing_box._fade_in_adjustment
+ fade_out_adjustment = self.compositing_box._fade_out_adjustment
+
+ clip, = self.layer.get_clips()
+ self.timeline_container.timeline.selection.select([clip])
+
+ self.assertEqual(fade_in_adjustment.props.upper * Gst.SECOND, clip.duration)
+ self.assertEqual(fade_out_adjustment.props.upper * Gst.SECOND, clip.duration)
+
+ fade_in_adjustment.props.value = 0.5
+ self.assertEqual(fade_out_adjustment.props.upper * Gst.SECOND, clip.duration - (0.5 * Gst.SECOND))
+
+ fade_out_adjustment.props.value = 0.3
+ self.assertEqual(fade_in_adjustment.props.upper * Gst.SECOND, clip.duration - (0.3 * Gst.SECOND))
+
+ def _get_control_source(self, clip):
+ source = self.get_clip_element(clip)
+ control_binding = source.get_control_binding("alpha")
+ self.assertIsNotNone(control_binding)
+ control_source = control_binding.props.control_source
+ self.assertIsNotNone(control_source)
+ return control_source
+
+ @common.setup_project_with_clips(assets_names=["1sec_simpsons_trailer.mp4"])
+ @common.setup_clipproperties
+ def test_apply_keyframes(self):
+ clip, = self.layer.get_clips()
+ self.timeline_container.timeline.selection.select([clip])
+
+ fade_in_adjustment = self.compositing_box._fade_in_adjustment
+ fade_out_adjustment = self.compositing_box._fade_out_adjustment
+ fade_in_adjustment.props.value = 0.5
+ fade_out_adjustment.props.value = 0.3
+
+ control_source = self._get_control_source(clip)
+ self.assert_control_source_values(control_source,
+ [0, 1, 1, 0],
+ [0, 0.5 * Gst.SECOND, clip.duration - 0.3 * Gst.SECOND,
clip.duration])
+
+ @common.setup_project_with_clips(assets_names=["1sec_simpsons_trailer.mp4"])
+ @common.setup_clipproperties
+ def test_move_keyframes(self):
+ clip, = self.layer.get_clips()
+ self.timeline_container.timeline.selection.select([clip])
+
+ control_source = self._get_control_source(clip)
+ control_source.set(0, 0) # Necessary in order for fade-in to be recognized
+ control_source.set(clip.duration, 0) # Necessary in order for fade-out to be recognized
+ control_source.set(0.6 * Gst.SECOND, 0.5) # This keyframe should be unaffected
+ control_source.set(0.1 * Gst.SECOND, 1) # This keyframe should be the fade-in
+ control_source.set(clip.duration - (0.2 * Gst.SECOND), 1) # This keyframe should be the fade-out
+
+ fade_in_adjustment = self.compositing_box._fade_in_adjustment
+ fade_out_adjustment = self.compositing_box._fade_out_adjustment
+ self.assertEqual(fade_in_adjustment.props.value, 0.1)
+ self.assertEqual(fade_out_adjustment.props.value, 0.2)
+
+ fade_in_adjustment.props.value = 0.5
+ fade_out_adjustment.props.value = 0.3
+
+ self.assert_control_source_values(control_source,
+ [0, 1, 0.5, 1, 0],
+ [0, 0.5 * Gst.SECOND, 0.6 * Gst.SECOND, clip.duration - 0.3 *
Gst.SECOND, clip.duration])
+
+ @common.setup_project_with_clips(assets_names=["1sec_simpsons_trailer.mp4",
"30fps_numeroted_frames_blue.webm"])
+ @common.setup_clipproperties
+ def test_adjustments_updated_when_switching_clips(self):
+ clip1, clip2 = self.layer.get_clips()
+ self.timeline_container.timeline.selection.select([clip2])
+
+ fade_in_adjustment = self.compositing_box._fade_in_adjustment
+ fade_out_adjustment = self.compositing_box._fade_out_adjustment
+ self.assertEqual(fade_in_adjustment.props.upper * Gst.SECOND, clip2.duration)
+ self.assertEqual(fade_out_adjustment.props.upper * Gst.SECOND, clip2.duration)
+
+ fade_in_adjustment.props.value = 1.3
+ mainloop = common.create_main_loop()
+ mainloop.run(until_empty=True)
+ self.assertEqual(int(fade_out_adjustment.props.upper * Gst.SECOND), clip2.duration - int(1.3 *
Gst.SECOND))
+
+ fade_out_adjustment.props.value = 0.5
+ self.assertEqual(int(fade_in_adjustment.props.upper * Gst.SECOND), clip2.duration - int(0.5 *
Gst.SECOND))
+
+ self.timeline_container.timeline.selection.select([clip1])
+ self.assertEqual(fade_in_adjustment.props.value, 0)
+ self.assertEqual(fade_out_adjustment.props.value, 0)
+ self.assertEqual(fade_in_adjustment.props.upper * Gst.SECOND, clip1.duration)
+ self.assertEqual(fade_out_adjustment.props.upper * Gst.SECOND, clip1.duration)
+
+ @common.setup_project_with_clips(assets_names=["1sec_simpsons_trailer.mp4"])
+ @common.setup_clipproperties
+ def test_adjustments_updated_when_keyframes_updated(self):
+ clip, = self.layer.get_clips()
+ self.timeline_container.timeline.selection.select([clip])
+
+ fade_in_adjustment = self.compositing_box._fade_in_adjustment
+ fade_out_adjustment = self.compositing_box._fade_out_adjustment
+ fade_in_adjustment.props.value = 0.5
+ fade_out_adjustment.props.value = 0.3
+
+ # Move the keyframes
+ control_source = self._get_control_source(clip)
+ control_source.unset(0.5 * Gst.SECOND)
+ control_source.set(0.4 * Gst.SECOND, 1)
+ control_source.unset(clip.duration - (0.3 * Gst.SECOND))
+ control_source.set(clip.duration - (0.2 * Gst.SECOND), 1)
+
+ self.assertEqual(fade_in_adjustment.props.value, 0.4)
+ self.assertEqual(fade_out_adjustment.props.value, 0.2)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]