[pitivi] clipproperties: Enable specifing a blending mode



commit a748da28a6d2f12d0dce5b683c4e8d305706f8f8
Author: hulettdalton <hulettdalton gmail com>
Date:   Tue Apr 27 10:17:51 2021 -0500

    clipproperties: Enable specifing a blending mode
    
    Changes were made in GES to expose the compositor's "operator"
    property to clips through their video source.
    
    This change implements a selector for blending modes that changes
    an individual clip's operator property, which changes how it is
    blended.
    
    Fixes #2313

 data/ui/clipblending.ui               | 41 ++++++++++++++++++++
 data/ui/clipcompositing.ui            | 55 ++++++++++++++++++++-------
 pitivi/clip_properties/compositing.py | 71 ++++++++++++++++++++++++++---------
 pitivi/clipproperties.py              |  1 +
 tests/test_clipproperties.py          | 36 ++++++++++++++++++
 5 files changed, 174 insertions(+), 30 deletions(-)
---
diff --git a/data/ui/clipblending.ui b/data/ui/clipblending.ui
new file mode 100644
index 000000000..dc4c2978a
--- /dev/null
+++ b/data/ui/clipblending.ui
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkGrid" id="blending_box">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_left">12</property>
+    <property name="margin_top">6</property>
+    <property name="margin_bottom">6</property>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Blending Mode:</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkComboBoxText" id="blending_mode">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="active">1</property>
+        <signal name="changed" handler="_blending_property_changed_cb" swapped="no"/>
+      </object>
+      <packing>
+        <property name="left_attach">1</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/clipcompositing.ui b/data/ui/clipcompositing.ui
index 64062c41e..e08850e49 100644
--- a/data/ui/clipcompositing.ui
+++ b/data/ui/clipcompositing.ui
@@ -1,17 +1,18 @@
 <?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"/>
+  <requires lib="gtk+" version="3.20"/>
   <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>
+    <property name="step-increment">1</property>
+    <property name="page-increment">5</property>
+    <signal name="value-changed" handler="_fade_in_adjustment_value_changed_cb" swapped="no"/>
   </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>
+    <property name="step-increment">1</property>
+    <property name="page-increment">5</property>
+    <signal name="value-changed" handler="_fade_out_adjustment_value_changed_cb" swapped="no"/>
   </object>
   <object class="GtkImage" id="icon_reset1">
     <property name="visible">True</property>
@@ -23,22 +24,20 @@
     <property name="can-focus">False</property>
     <property name="icon-name">edit-clear-all-symbolic</property>
   </object>
-  <!-- n-columns=5 n-rows=2 -->
+  <!-- n-columns=5 n-rows=3 -->
   <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="border-width">10</property>
+    <property name="row-spacing">10</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="halign">end</property>
         <property name="hexpand">True</property>
         <property name="label" translatable="yes">Fade-in:</property>
         <property name="xalign">0</property>
@@ -93,7 +92,7 @@
       <object class="GtkLabel" id="label11">
         <property name="visible">True</property>
         <property name="can-focus">False</property>
-        <property name="halign">start</property>
+        <property name="halign">end</property>
         <property name="hexpand">True</property>
         <property name="label" translatable="yes">Fade-out:</property>
         <property name="xalign">0</property>
@@ -151,6 +150,7 @@
         <property name="tooltip-text" translatable="yes">Reset fade-in</property>
         <property name="image">icon_reset1</property>
         <property name="relief">none</property>
+        <signal name="clicked" handler="_reset_fade_in_clicked_cb" swapped="no"/>
       </object>
       <packing>
         <property name="left-attach">4</property>
@@ -166,11 +166,40 @@
         <property name="tooltip-text" translatable="yes">Reset fade-out</property>
         <property name="image">icon_reset2</property>
         <property name="relief">none</property>
+        <signal name="clicked" handler="_reset_fade_out_clicked_cb" swapped="no"/>
       </object>
       <packing>
         <property name="left-attach">4</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="halign">end</property>
+        <property name="label" translatable="yes">Blending:</property>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkComboBoxText" id="blending_mode">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="halign">start</property>
+        <signal name="changed" handler="_blending_property_changed_cb" swapped="no"/>
+      </object>
+      <packing>
+        <property name="left-attach">1</property>
+        <property name="top-attach">2</property>
+        <property name="width">3</property>
+      </packing>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
   </object>
 </interface>
diff --git a/pitivi/clip_properties/compositing.py b/pitivi/clip_properties/compositing.py
index 77e15eb93..814f90493 100644
--- a/pitivi/clip_properties/compositing.py
+++ b/pitivi/clip_properties/compositing.py
@@ -3,6 +3,9 @@
 # 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>
+# Copyright (c) 2021, Andres Ruiz <andres ruiz3210 gmail com>
+# Copyright (c) 2021, Dalton Hulett <hulettdalton gmail com>
+# Copyright (c) 2021, Reed Lawrence <reed lawrence zenofchem com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -22,6 +25,7 @@ from typing import Iterable
 from typing import Optional
 
 from gi.repository import GES
+from gi.repository import GObject
 from gi.repository import Gst
 from gi.repository import GstController
 from gi.repository import Gtk
@@ -37,7 +41,7 @@ FADE_OPACITY_THRESHOLD = 0.9
 
 
 class CompositingProperties(Gtk.Expander, Loggable):
-    """Widget for setting the opacity-related properties of a clip.
+    """Widget for setting the opacity and compositing properties of a clip.
 
     Attributes:
         app (Pitivi): The app.
@@ -54,12 +58,11 @@ class CompositingProperties(Gtk.Expander, Loggable):
 
         builder = Gtk.Builder()
         builder.add_from_file(os.path.join(get_ui_dir(), "clipcompositing.ui"))
+        builder.connect_signals(self)
 
-        self.add(builder.get_object("compositing_box"))
+        compositing_box = 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
@@ -70,14 +73,25 @@ class CompositingProperties(Gtk.Expander, Loggable):
         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)
+        self.blending_combo = builder.get_object("blending_mode")
+        # Translators: These are compositing operators.
+        # See https://www.cairographics.org/operators/ for explanation and
+        # visualizations.
+        for value_id, text in (("source", _("Source")),
+                               ("over", _("Over")),
+                               # TODO: Add back when 
https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1199 is fixed
+                               # ("add", _("Add")),
+                               ):
+            self.blending_combo.append(value_id, text)
+
+        self.add(compositing_box)
+        compositing_box.show_all()
 
     def set_source(self, video_source: GES.VideoSource) -> None:
+        self.debug("Source set to %s", video_source)
         if self._control_source:
             disconnect_all_by_func(self._control_source, self.__keyframe_changed_cb)
+            self._video_source.disconnect_by_func(self._source_deep_notify_cb)
             self._video_source.disconnect_by_func(self.__keyframe_changed_cb)
             self._control_source = None
 
@@ -89,16 +103,16 @@ class CompositingProperties(Gtk.Expander, Loggable):
             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()
+
+            self._video_source.connect("deep-notify", self._source_deep_notify_cb)
+            self._update_blending()
+
+        self.props.visible = bool(self._video_source)
 
     @property
     def _duration(self) -> int:
@@ -138,22 +152,22 @@ class CompositingProperties(Gtk.Expander, Loggable):
         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:
+    def _fade_in_adjustment_value_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:
+    def _fade_out_adjustment_value_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:
+    def _reset_fade_in_clicked_cb(self, button: Gtk.Button) -> None:
         self._fade_in_adjustment.props.value = 0
 
-    def __reset_fade_out_cb(self, button: Gtk.Button) -> None:
+    def _reset_fade_out_clicked_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:
@@ -219,3 +233,26 @@ class CompositingProperties(Gtk.Expander, Loggable):
                     self._control_source.set(edge_timestamp, 0)
             finally:
                 self._applying_fade = False
+
+    def _update_blending(self) -> None:
+        res, value = self._video_source.get_child_property("operator")
+        assert res
+        self.blending_combo.handler_block_by_func(self._blending_property_changed_cb)
+        try:
+            self.blending_combo.set_active_id(value.value_nick)
+        finally:
+            self.blending_combo.handler_unblock_by_func(self._blending_property_changed_cb)
+
+    def _source_deep_notify_cb(self, element: GES.TimelineElement, obj: GObject.Object, prop: 
GObject.ParamSpec) -> None:
+        self._update_blending()
+
+    def _blending_property_changed_cb(self, combo: Gtk.ComboBox) -> None:
+        pipeline = self.app.project_manager.current_project.pipeline
+        with self.app.action_log.started("set operator",
+                                         finalizing_action=CommitTimelineFinalizingAction(pipeline),
+                                         toplevel=True):
+            self._video_source.handler_block_by_func(self._source_deep_notify_cb)
+            try:
+                self._video_source.set_child_property("operator", self.blending_combo.get_active_id())
+            finally:
+                self._video_source.handler_unblock_by_func(self._source_deep_notify_cb)
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index cb906bc3d..a685602f5 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -74,6 +74,7 @@ DEFAULT_FONT_DESCRIPTION = "Sans 36"
 DEFAULT_VALIGNMENT = "absolute"
 DEFAULT_HALIGNMENT = "absolute"
 DEFAULT_DROP_SHADOW = True
+DEFAULT_BLENDING = "over"
 
 # Max speed rate we allow to be applied to clips.
 # The minimum is 1 / MAX_RATE.
diff --git a/tests/test_clipproperties.py b/tests/test_clipproperties.py
index 3f9a0b632..03755d265 100644
--- a/tests/test_clipproperties.py
+++ b/tests/test_clipproperties.py
@@ -321,6 +321,42 @@ class TransformationPropertiesTest(common.TestCase):
             self.assertTrue(ret)
             self.assertEqual(value, source.ui.default_position[prop])
 
+    @common.setup_timeline
+    @common.setup_clipproperties
+    def test_operator(self):
+        timeline = self.app.gui.editor.timeline_ui.timeline
+
+        clip, = self.add_clips_simple(timeline, 1)
+        timeline.selection.select([clip])
+        source = self.compositing_box._video_source
+        self.assertIsNotNone(source)
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "over"))
+
+        self.compositing_box.blending_combo.set_active_id("source")
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "source"))
+
+        self.compositing_box.blending_combo.set_active_id("over")
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "over"))
+
+        self.app.action_log.undo()
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "source"))
+
+        self.app.action_log.undo()
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "over"))
+
+        self.app.action_log.redo()
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "source"))
+
+        self.app.action_log.redo()
+        ret, value = source.get_child_property("operator")
+        self.assertEqual((ret, value.value_nick), (True, "over"))
+
 
 class TitlePropertiesTest(common.TestCase):
     """Tests for the TitleProperties class."""


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]