[pitivi] Added functionality for Color Clips
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] Added functionality for Color Clips
- Date: Fri, 1 May 2020 21:27:25 +0000 (UTC)
commit 81c3d4b81aca7a758153d83f1507c08271c1f195
Author: Andrew Hazel <aceknifes gmail com>
Date: Wed Apr 29 20:20:47 2020 -0500
Added functionality for Color Clips
This allows filmmakers to create and edit color clips
by using the Clip properties tab.
Closes issue #2296
data/ui/clipcolor.ui | 32 ++++++++
help/C/usingclips.page | 11 ++-
pitivi/clip_properties/color.py | 118 ++++++++++++++++++++++++++++
pitivi/clip_properties/title.py | 7 +-
pitivi/clipproperties.py | 153 ++++++++++++++++++++++++++-----------
pitivi/editorperspective.py | 2 +
pitivi/timeline/elements.py | 46 ++++++++++-
pitivi/timeline/previewers.py | 42 +++++++---
pitivi/utils/widgets.py | 9 +++
pitivi/viewer/overlay.py | 2 +-
tests/test_clipproperties.py | 2 +-
tests/test_clipproperties_color.py | 77 +++++++++++++++++++
12 files changed, 437 insertions(+), 64 deletions(-)
---
diff --git a/data/ui/clipcolor.ui b/data/ui/clipcolor.ui
new file mode 100644
index 00000000..c994f936
--- /dev/null
+++ b/data/ui/clipcolor.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <object class="GtkBox" id="color_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_left">12</property>
+ <property name="margin_right">12</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkColorButton" id="color_button">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_alpha">True</property>
+ <property name="title" translatable="yes">Pick the clip color</property>
+ <property name="rgba">rgb(255,255,255)</property>
+ <signal name="color-set" handler="_color_button_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/help/C/usingclips.page b/help/C/usingclips.page
index 56b82c6c..50df2218 100644
--- a/help/C/usingclips.page
+++ b/help/C/usingclips.page
@@ -2,7 +2,7 @@
<page xmlns="http://projectmallard.org/1.0/" xmlns:e="http://projectmallard.org/experimental/" type="topic"
id="usingclips">
<info>
<link type="guide" xref="index#gettingstarted"/>
- <revision pkgversion="0.96" version="0.2" date="2016-02-17" status="complete"/>
+ <revision pkgversion="0.96" version="0.2" date="2020-04-21" status="complete"/>
<credit type="author">
<name>Jean-François Fortin Tam</name>
<email>nekohayo gmail com</email>
@@ -15,6 +15,10 @@
<name>Tomáš Karger</name>
<email>tomkarger gmail com</email>
</credit>
+ <credit type="contributor">
+ <name>UNL SOFT261 - Team 3</name>
+ <email>troy ogden huskers unl edu</email>
+ </credit>
<desc>
Learn the difference between clips and files and how to do basic operations on clips in the timeline.
</desc>
@@ -70,4 +74,9 @@
<p>You can release and press <key>Shift</key> at any time during the drag operation to disable or
enable ripple editing.</p>
</section>
</section>
+ <section id="color">
+ <title>Using Color Clips</title>
+ <p>You can create custom color clips using the <gui>Create a color clip</gui> button located in the
<guiseq><gui>middle pane</gui><gui>Clip</gui></guiseq> tab. The button is available when no clip is <link
xref="selectiongrouping#selection">selected</link>.</p>
+ <p>Select a color clip to change its color. When a new color clip is created, it is automatically
selected. The color can be changed using the <guiseq><gui>middle
pane</gui><gui>Clip</gui><gui>Color</gui></guiseq> expander. The <gui>color</gui> button opens a dialog for
selecting a color or entering a hexadecimal color value. Alternatively, any color on the screen can be picked
by using the <gui>color picker</gui> button.</p>
+ </section>
</page>
diff --git a/pitivi/clip_properties/color.py b/pitivi/clip_properties/color.py
new file mode 100644
index 00000000..56a445c8
--- /dev/null
+++ b/pitivi/clip_properties/color.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (C) 2020 Andrew Hazel, Thomas Braccia, Troy Ogden, Robert Kirkpatrick
+#
+# 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/>.
+"""Widgets to control clips properties."""
+import os
+from gettext import gettext as _
+
+from gi.repository import GES
+from gi.repository import Gtk
+
+from pitivi.configure import get_ui_dir
+from pitivi.settings import GlobalSettings
+from pitivi.undo.timeline import CommitTimelineFinalizingAction
+from pitivi.utils.loggable import Loggable
+from pitivi.utils.ui import argb_to_gdk_rgba
+from pitivi.utils.ui import gdk_rgba_to_argb
+from pitivi.utils.widgets import ColorPickerButton
+
+
+GlobalSettings.add_config_section("user-interface")
+
+GlobalSettings.add_config_option("ColorClipLength",
+ section="user-interface",
+ key="color-clip-length",
+ default=5000,
+ notify=True)
+
+
+class ColorProperties(Gtk.Expander, Loggable):
+ """Widget for configuring the properties of a color clip."""
+
+ def __init__(self, app):
+ Gtk.Expander.__init__(self)
+ Loggable.__init__(self)
+
+ self.app = app
+ self.source = None
+ self._children_props_handler = None
+
+ self.set_label(_("Color"))
+ self.set_expanded(True)
+
+ self._create_ui()
+
+ def _create_ui(self):
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(os.path.join(get_ui_dir(), "clipcolor.ui"))
+ self.builder.connect_signals(self)
+
+ box = self.builder.get_object("color_box")
+ self.add(box)
+
+ self.color_button = self.builder.get_object("color_button")
+
+ self.color_picker_button = ColorPickerButton()
+ box.add(self.color_picker_button)
+ self.color_picker_button.connect(
+ "value-changed", self._color_picker_value_changed_cb)
+
+ self.show_all()
+
+ def _set_child_property(self, name, value):
+ with self.app.action_log.started("Color change property",
+
finalizing_action=CommitTimelineFinalizingAction(self.app.project_manager.current_project.pipeline),
+ toplevel=True):
+ res = self.source.set_child_property(name, value)
+ assert res
+
+ def _color_picker_value_changed_cb(self, widget):
+ argb = widget.calculate_argb()
+ self._set_child_property("foreground-color", argb)
+
+ def _color_button_cb(self, widget):
+ argb = gdk_rgba_to_argb(widget.get_rgba())
+ self._set_child_property("foreground-color", argb)
+
+ def set_source(self, source):
+ """Sets the clip source to be edited with this editor.
+
+ Args:
+ source (GES.VideoTestSource): The source of the clip.
+ """
+ self.debug("Source set to %s", source)
+ if self._children_props_handler is not None:
+ self.source.disconnect(self._children_props_handler)
+ self._children_props_handler = None
+ self.source = None
+ if source:
+ assert isinstance(source, GES.VideoTestSource)
+ self.source = source
+ self._update_color_button()
+ self._children_props_handler = self.source.connect("deep-notify",
+ self._source_deep_notify_cb)
+ self.set_visible(bool(self.source))
+
+ def _source_deep_notify_cb(self, source, unused_gstelement, pspec):
+ """Handles updates in the VideoTestSource backing the current TestClip."""
+ if pspec.name == "foreground-color":
+ self._update_color_button()
+
+ def _update_color_button(self):
+ res, argb = self.source.get_child_property("foreground-color")
+ assert res
+ color = argb_to_gdk_rgba(argb)
+ self.color_button.set_rgba(color)
diff --git a/pitivi/clip_properties/title.py b/pitivi/clip_properties/title.py
index 91031b7a..9f375ce9 100644
--- a/pitivi/clip_properties/title.py
+++ b/pitivi/clip_properties/title.py
@@ -112,7 +112,6 @@ class TitleProperties(Gtk.Expander, Loggable):
self.settings["halignment"].append(value_id, text)
self.show_all()
- self.hide()
def _set_child_property(self, name, value):
with self.app.action_log.started("Title change property",
@@ -125,11 +124,7 @@ class TitleProperties(Gtk.Expander, Loggable):
self._setting_props = False
def _color_picker_value_changed_cb(self, widget, color_button, color_layer):
- argb = 0
- argb += (1 * 255) * 256 ** 3
- argb += float(widget.color_r) * 256 ** 2
- argb += float(widget.color_g) * 256 ** 1
- argb += float(widget.color_b) * 256 ** 0
+ argb = widget.calculate_argb()
self.debug("Setting text %s to %x", color_layer, argb)
self._set_child_property(color_layer, argb)
rgba = argb_to_gdk_rgba(argb)
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index d4452840..c3c6e181 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -27,6 +27,7 @@ from gi.repository import GstController
from gi.repository import Gtk
from gi.repository import Pango
+from pitivi.clip_properties.color import ColorProperties
from pitivi.clip_properties.title import TitleProperties
from pitivi.configure import get_ui_dir
from pitivi.effects import EffectsPropertiesManager
@@ -93,6 +94,10 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
self.title_expander.set_vexpand(False)
vbox.pack_start(self.title_expander, False, False, 0)
+ self.color_expander = ColorProperties(app)
+ self.color_expander.set_vexpand(False)
+ vbox.pack_start(self.color_expander, False, False, 0)
+
self.effect_expander = EffectProperties(app, self)
self.effect_expander.set_vexpand(False)
vbox.pack_start(self.effect_expander, False, False, 0)
@@ -100,6 +105,9 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
self.helper_box = self.create_helper_box()
self.clips_box.pack_start(self.helper_box, False, False, 0)
+ self.title_expander.set_source(None)
+ self.color_expander.set_source(None)
+
self._project = None
self._selection = None
self.app.project_manager.connect_after(
@@ -119,13 +127,18 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
title_button = Gtk.Button()
title_button.set_label(_("Create a title clip"))
- title_button.connect("clicked", self.create_cb)
+ title_button.connect("clicked", self.create_title_clip_cb)
box.pack_start(title_button, False, False, SPACING)
+ color_button = Gtk.Button()
+ color_button.set_label(_("Create a color clip"))
+ color_button.connect("clicked", self.create_color_clip_cb)
+ box.pack_start(color_button, False, False, SPACING)
+
box.show_all()
return box
- def create_cb(self, unused_button):
+ def create_title_clip_cb(self, unused_button):
title_clip = GES.TitleClip()
duration = self.app.settings.titleClipLength * Gst.MSECOND
title_clip.set_duration(duration)
@@ -146,6 +159,18 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
assert res, prop
self._selection.set_selection([title_clip], SELECT)
+ def create_color_clip_cb(self, unused_widget):
+ color_clip = GES.TestClip.new()
+ duration = self.app.settings.ColorClipLength * Gst.MSECOND
+ color_clip.set_duration(duration)
+ color_clip.set_vpattern(GES.VideoTestPattern.SOLID_COLOR)
+ color_clip.set_supported_formats(GES.TrackType.VIDEO)
+ with self.app.action_log.started("add color clip",
+
finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
+ toplevel=True):
+ self.app.gui.editor.timeline_ui.insert_clips_on_first_layer([color_clip])
+ self._selection.set_selection([color_clip], SELECT)
+
def new_project_loaded_cb(self, unused_project_manager, project):
if self._selection is not None:
self._selection.disconnect_by_func(self._selection_changed_cb)
@@ -161,12 +186,17 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
self.helper_box.set_visible(not single_clip_selected)
title_source = None
+ color_clip_source = None
if single_clip_selected:
for child in list(selected_clips)[0].get_children(False):
if isinstance(child, GES.TitleSource):
title_source = child
break
+ if isinstance(child, GES.VideoTestSource):
+ color_clip_source = child
+ break
self.title_expander.set_source(title_source)
+ self.color_expander.set_source(color_clip_source)
def __project_closed_cb(self, unused_project_manager, unused_project):
self._project = None
@@ -217,8 +247,7 @@ class EffectProperties(Gtk.Expander, Loggable):
remove_effect_button.set_image(remove_icon)
remove_effect_button.set_always_show_image(True)
remove_effect_button.set_label(_("Remove effect"))
- buttons_box.pack_start(remove_effect_button,
- expand=False, fill=False, padding=0)
+ buttons_box.pack_start(remove_effect_button, expand=False, fill=False, padding=0)
# We need to specify Gtk.TreeDragSource because otherwise we are hitting
# bug https://bugzilla.gnome.org/show_bug.cgi?id=730740.
@@ -309,25 +338,32 @@ class EffectProperties(Gtk.Expander, Loggable):
self.hide()
effects_actions_group = Gio.SimpleActionGroup()
- self.treeview.insert_action_group("clipproperties-effects", effects_actions_group)
- buttons_box.insert_action_group("clipproperties-effects", effects_actions_group)
+ self.treeview.insert_action_group(
+ "clipproperties-effects", effects_actions_group)
+ buttons_box.insert_action_group(
+ "clipproperties-effects", effects_actions_group)
self.remove_effect_action = Gio.SimpleAction.new("remove-effect", None)
self.remove_effect_action.connect("activate", self._remove_effect_cb)
effects_actions_group.add_action(self.remove_effect_action)
- self.app.set_accels_for_action("clipproperties-effects.remove-effect", ["Delete"])
+ self.app.set_accels_for_action(
+ "clipproperties-effects.remove-effect", ["Delete"])
self.remove_effect_action.set_enabled(False)
- remove_effect_button.set_action_name("clipproperties-effects.remove-effect")
+ remove_effect_button.set_action_name(
+ "clipproperties-effects.remove-effect")
# Connect all the widget signals
- self.treeview_selection.connect("changed", self._treeview_selection_changed_cb)
+ self.treeview_selection.connect(
+ "changed", self._treeview_selection_changed_cb)
self.connect("drag-motion", self._drag_motion_cb)
self.connect("drag-leave", self._drag_leave_cb)
self.connect("drag-data-received", self._drag_data_received_cb)
self.treeview.connect("drag-motion", self._drag_motion_cb)
self.treeview.connect("drag-leave", self._drag_leave_cb)
- self.treeview.connect("drag-data-received", self._drag_data_received_cb)
- self.treeview.connect("query-tooltip", self._tree_view_query_tooltip_cb)
+ self.treeview.connect("drag-data-received",
+ self._drag_data_received_cb)
+ self.treeview.connect(
+ "query-tooltip", self._tree_view_query_tooltip_cb)
self.app.project_manager.connect_after(
"new-project-loaded", self._new_project_loaded_cb)
self.connect('notify::expanded', self._expanded_cb)
@@ -339,7 +375,8 @@ class EffectProperties(Gtk.Expander, Loggable):
self._project = project
if project:
self._selection = project.ges_timeline.ui.selection
- self._selection.connect('selection-changed', self._selection_changed_cb)
+ self._selection.connect(
+ 'selection-changed', self._selection_changed_cb)
self.__update_all()
def _selection_changed_cb(self, selection):
@@ -406,9 +443,7 @@ class EffectProperties(Gtk.Expander, Loggable):
def _remove_effect(self, effect):
pipeline = self._project.pipeline
- with self.app.action_log.started("remove effect",
- finalizing_action=CommitTimelineFinalizingAction(pipeline),
- toplevel=True):
+ with self.app.action_log.started("remove effect",
finalizing_action=CommitTimelineFinalizingAction(pipeline), toplevel=True):
self.__remove_configuration_widget()
self.effects_properties_manager.clean_cache(effect)
effect.get_parent().remove(effect)
@@ -447,7 +482,8 @@ class EffectProperties(Gtk.Expander, Loggable):
effect_info = self.app.effects.get_info(factory_name)
pipeline = self._project.pipeline
with self.app.action_log.started("add effect",
- finalizing_action=CommitTimelineFinalizingAction(pipeline),
+ finalizing_action=CommitTimelineFinalizingAction(
+ pipeline),
toplevel=True):
effect = self.clip.ui.add_effect(effect_info)
if effect:
@@ -500,7 +536,8 @@ class EffectProperties(Gtk.Expander, Loggable):
effect = effects[source_index]
pipeline = self._project.ges_timeline.get_parent()
with self.app.action_log.started("move effect",
- finalizing_action=CommitTimelineFinalizingAction(pipeline),
+ finalizing_action=CommitTimelineFinalizingAction(
+ pipeline),
toplevel=True):
clip.set_top_effect_index(effect, drop_index)
@@ -526,7 +563,8 @@ class EffectProperties(Gtk.Expander, Loggable):
effect = self.storemodel.get_value(_iter, COL_TRACK_EFFECT)
pipeline = self._project.ges_timeline.get_parent()
with self.app.action_log.started("change active state",
- finalizing_action=CommitTimelineFinalizingAction(pipeline),
+ finalizing_action=CommitTimelineFinalizingAction(
+ pipeline),
toplevel=True):
effect.props.active = not effect.props.active
# This is not strictly necessary, but makes sure
@@ -552,6 +590,7 @@ class EffectProperties(Gtk.Expander, Loggable):
def __update_all(self, path=None):
if self.clip:
self.show()
+ self.no_effect_infobar.hide()
self._update_treeview()
if path:
self.treeview_selection.select_path(path)
@@ -565,7 +604,8 @@ class EffectProperties(Gtk.Expander, Loggable):
for effect in self.clip.get_top_effects():
if effect.props.bin_description in HIDDEN_EFFECTS:
continue
- effect_info = self.app.effects.get_info(effect.props.bin_description)
+ effect_info = self.app.effects.get_info(
+ effect.props.bin_description)
to_append = [effect.props.active]
track_type = effect.get_track_type()
if track_type == GES.TrackType.AUDIO:
@@ -660,7 +700,8 @@ class TransformationProperties(Gtk.Expander, Loggable):
self._project = project
if project:
self._selection = project.ges_timeline.ui.selection
- self._selection.connect('selection-changed', self._selection_changed_cb)
+ self._selection.connect(
+ 'selection-changed', self._selection_changed_cb)
self._project.pipeline.connect("position", self._position_cb)
def __project_closed_cb(self, unused_project_manager, unused_project):
@@ -670,15 +711,21 @@ class TransformationProperties(Gtk.Expander, Loggable):
clear_button = self.builder.get_object("clear_button")
clear_button.connect("clicked", self._default_values_cb)
- self._activate_keyframes_btn = self.builder.get_object("activate_keyframes_button")
- self._activate_keyframes_btn.connect("toggled", self.__show_keyframes_toggled_cb)
+ self._activate_keyframes_btn = self.builder.get_object(
+ "activate_keyframes_button")
+ self._activate_keyframes_btn.connect(
+ "toggled", self.__show_keyframes_toggled_cb)
- self._next_keyframe_btn = self.builder.get_object("next_keyframe_button")
- self._next_keyframe_btn.connect("clicked", self.__go_to_keyframe_cb, True)
+ self._next_keyframe_btn = self.builder.get_object(
+ "next_keyframe_button")
+ self._next_keyframe_btn.connect(
+ "clicked", self.__go_to_keyframe_cb, True)
self._next_keyframe_btn.set_sensitive(False)
- self._prev_keyframe_btn = self.builder.get_object("prev_keyframe_button")
- self._prev_keyframe_btn.connect("clicked", self.__go_to_keyframe_cb, False)
+ self._prev_keyframe_btn = self.builder.get_object(
+ "prev_keyframe_button")
+ self._prev_keyframe_btn.connect(
+ "clicked", self.__go_to_keyframe_cb, False)
self._prev_keyframe_btn.set_sensitive(False)
self.__setup_spin_button("xpos_spinbtn", "posx")
@@ -690,8 +737,10 @@ class TransformationProperties(Gtk.Expander, Loggable):
def __get_keyframes_timestamps(self):
keyframes_ts = []
for prop in ["posx", "posy", "width", "height"]:
- prop_keyframes = self.__control_bindings[prop].props.control_source.get_all()
- keyframes_ts.extend([keyframe.timestamp for keyframe in prop_keyframes])
+ prop_keyframes = self.__control_bindings[prop].props.control_source.get_all(
+ )
+ keyframes_ts.extend(
+ [keyframe.timestamp for keyframe in prop_keyframes])
return sorted(set(keyframes_ts))
@@ -726,9 +775,11 @@ class TransformationProperties(Gtk.Expander, Loggable):
self._prev_keyframe_btn.set_sensitive(False)
self._next_keyframe_btn.set_sensitive(False)
if self.__source_uses_keyframes():
- self._activate_keyframes_btn.set_tooltip_text(_("Show keyframes"))
+ self._activate_keyframes_btn.set_tooltip_text(
+ _("Show keyframes"))
else:
- self._activate_keyframes_btn.set_tooltip_text(_("Activate keyframes"))
+ self._activate_keyframes_btn.set_tooltip_text(
+ _("Activate keyframes"))
self.source.ui_element.show_default_keyframes()
else:
self._prev_keyframe_btn.set_sensitive(True)
@@ -759,7 +810,8 @@ class TransformationProperties(Gtk.Expander, Loggable):
# control_source.unset_all() can't be used here as it doesn't emit
# the 'value-removed' signal, so the undo system wouldn't notice
# the removed keyframes
- keyframes_ts = [keyframe.timestamp for keyframe in control_source.get_all()]
+ keyframes_ts = [
+ keyframe.timestamp for keyframe in control_source.get_all()]
for ts in keyframes_ts:
control_source.unset(ts)
self.__own_bindings_change = True
@@ -781,7 +833,8 @@ class TransformationProperties(Gtk.Expander, Loggable):
control_source = GstController.InterpolationControlSource()
control_source.props.mode = GstController.InterpolationMode.LINEAR
self.__own_bindings_change = True
- self.source.set_control_source(control_source, prop, "direct-absolute")
+ self.source.set_control_source(
+ control_source, prop, "direct-absolute")
self.__own_bindings_change = False
self.__set_default_keyframes_values(control_source, prop)
@@ -789,23 +842,27 @@ class TransformationProperties(Gtk.Expander, Loggable):
self.__control_bindings[prop] = binding
if adding_kfs:
- self.app.action_log.commit("Transformation properties keyframes activate")
+ self.app.action_log.commit(
+ "Transformation properties keyframes activate")
def __set_default_keyframes_values(self, control_source, prop):
res, val = self.source.get_child_property(prop)
assert res
control_source.set(self.source.props.in_point, val)
- control_source.set(self.source.props.in_point + self.source.props.duration, val)
+ control_source.set(self.source.props.in_point +
+ self.source.props.duration, val)
def _default_values_cb(self, unused_widget):
with self.app.action_log.started("Transformation properties reset default",
-
finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
+ finalizing_action=CommitTimelineFinalizingAction(
+ self._project.pipeline),
toplevel=True):
if self.__source_uses_keyframes():
self.__remove_control_bindings()
for prop in ["posx", "posy", "width", "height"]:
- self.source.set_child_property(prop, self.source.ui.default_position[prop])
+ self.source.set_child_property(
+ prop, self.source.ui.default_position[prop])
self.__update_keyframes_ui()
@@ -819,8 +876,10 @@ class TransformationProperties(Gtk.Expander, Loggable):
# If the position is outside of the clip, take the property
# value at the start/end (whichever is closer) of the clip.
- source_position = max(0, min(position - start, duration - 1)) + in_point
- value = self.__control_bindings[prop].get_value(source_position)
+ source_position = max(
+ 0, min(position - start, duration - 1)) + in_point
+ value = self.__control_bindings[prop].get_value(
+ source_position)
res = value is not None
return res, value
except PipelineError:
@@ -879,22 +938,26 @@ class TransformationProperties(Gtk.Expander, Loggable):
with self.app.action_log.started(
"Transformation property change",
- finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
+ finalizing_action=CommitTimelineFinalizingAction(
+ self._project.pipeline),
toplevel=True):
- self.__control_bindings[prop].props.control_source.set(source_position, value)
+ self.__control_bindings[prop].props.control_source.set(
+ source_position, value)
except PipelineError:
self.warning("Could not get pipeline position")
return
else:
with self.app.action_log.started("Transformation property change",
-
finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
+ finalizing_action=CommitTimelineFinalizingAction(
+ self._project.pipeline),
toplevel=True):
self.source.set_child_property(prop, value)
def __setup_spin_button(self, widget_name, property_name):
"""Creates a SpinButton for editing a property value."""
spinbtn = self.builder.get_object(widget_name)
- handler_id = spinbtn.connect("value-changed", self._on_value_changed_cb, property_name)
+ handler_id = spinbtn.connect(
+ "value-changed", self._on_value_changed_cb, property_name)
disable_scroll(spinbtn)
self.spin_buttons[property_name] = spinbtn
self.spin_buttons_handler_ids[property_name] = handler_id
@@ -916,8 +979,10 @@ class TransformationProperties(Gtk.Expander, Loggable):
def __set_source(self, source):
if self.source:
try:
- self.source.disconnect_by_func(self.__source_property_changed_cb)
- disconnect_all_by_func(self.source, self._control_bindings_changed)
+ self.source.disconnect_by_func(
+ self.__source_property_changed_cb)
+ disconnect_all_by_func(
+ self.source, self._control_bindings_changed)
except TypeError:
pass
self.source = source
diff --git a/pitivi/editorperspective.py b/pitivi/editorperspective.py
index 881b4867..66468713 100644
--- a/pitivi/editorperspective.py
+++ b/pitivi/editorperspective.py
@@ -283,6 +283,8 @@ class EditorPerspective(Perspective, Loggable):
page = 0
elif isinstance(ges_clip, GES.TransitionClip):
page = 1
+ elif isinstance(ges_clip, GES.TestClip):
+ page = 0
else:
self.warning("Unknown clip type: %s", ges_clip)
return
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 6f70041d..24beb6bb 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -963,7 +963,7 @@ class TitleSource(VideoSource):
for spec in self._ges_elem.list_children_properties():
if spec.name == "alpha":
return spec
- return None
+ return None
def _get_previewer(self):
previewer = TitlePreviewer(self._ges_elem)
@@ -977,6 +977,27 @@ class TitleSource(VideoSource):
"height": self._project_height}
+class VideoTestSource(VideoSource):
+
+ __gtype_name__ = "PitiviVideoTestSource"
+
+ def _get_default_mixing_property(self):
+ for spec in self._ges_elem.list_children_properties():
+ if spec.name == "alpha":
+ return spec
+ return None
+
+ def _get_previewer(self):
+ previewer = ImagePreviewer(self._ges_elem, self.timeline.app.settings.previewers_max_cpu)
+ return previewer
+
+ def _get_default_position(self):
+ return {"posx": 0,
+ "posy": 0,
+ "width": self._project_width,
+ "height": self._project_height}
+
+
class VideoUriSource(VideoSource):
__gtype_name__ = "PitiviUriVideoSource"
@@ -1415,6 +1436,26 @@ class UriClip(SourceClip):
self.video_widget.set_visible(True)
+class TestClip(SourceClip):
+ __gtype_name__ = "PitiviTestClip"
+
+ def __init__(self, layer, ges_clip):
+ SourceClip.__init__(self, layer, ges_clip)
+ self.get_style_context().add_class("TestClip")
+
+ def _add_child(self, ges_timeline_element):
+ SourceClip._add_child(self, ges_timeline_element)
+
+ if not isinstance(ges_timeline_element, GES.Source):
+ return
+
+ if ges_timeline_element.get_track_type() == GES.TrackType.VIDEO:
+ self.video_widget = VideoTestSource(ges_timeline_element, self.timeline)
+ ges_timeline_element.ui = self.video_widget
+ self._elements_container.pack_start(self.video_widget, True, False, 0)
+ self.video_widget.set_visible(True)
+
+
class TitleClip(SourceClip):
__gtype_name__ = "PitiviTitleClip"
@@ -1486,5 +1527,6 @@ class TransitionClip(Clip):
GES_TYPE_UI_TYPE = {
GES.UriClip.__gtype__: UriClip,
GES.TitleClip.__gtype__: TitleClip,
- GES.TransitionClip.__gtype__: TransitionClip
+ GES.TransitionClip.__gtype__: TransitionClip,
+ GES.TestClip.__gtype__: TestClip
}
diff --git a/pitivi/timeline/previewers.py b/pitivi/timeline/previewers.py
index fcf71c9d..98ab9a89 100644
--- a/pitivi/timeline/previewers.py
+++ b/pitivi/timeline/previewers.py
@@ -404,7 +404,10 @@ class Previewer(GObject.Object):
class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
- """An image previewer widget, drawing thumbnails."""
+ """A previewer widget drawing the same thumbnail repeatedly.
+
+ Can be used for Image clips or Color clips.
+ """
# We could define them in Previewer, but for some reason they are ignored.
__gsignals__ = PREVIEW_GENERATOR_SIGNALS
@@ -418,9 +421,7 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
self.get_style_context().add_class("VideoPreviewer")
self.ges_elem = ges_elem
-
- # Guard against malformed URIs
- self.uri = quote_uri(get_proxy_target(ges_elem).props.id)
+ self.uri = get_proxy_target(ges_elem).props.id
self.__start_id = 0
@@ -432,6 +433,9 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
self.ges_elem.connect("notify::duration", self._duration_changed_cb)
+ if isinstance(self.ges_elem, GES.VideoTestSource):
+ self.ges_elem.connect("deep-notify", self._source_deep_notify_cb)
+
self.become_controlled()
self.connect("notify::height-request", self._height_changed_cb)
@@ -445,16 +449,32 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
self.__start_id = None
- self.debug("Generating thumbnail for image: %s", path_from_uri(self.uri))
- self.__image_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
- Gst.uri_get_location(self.uri), -1, self.thumb_height, True)
+ self.__image_pixbuf = self._generate_thumbnail()
self.thumb_width = self.__image_pixbuf.props.width
+
self._update_thumbnails()
self.emit("done")
# Stop calling me, I started already.
return False
+ def _generate_thumbnail(self):
+ if isinstance(self.ges_elem, GES.ImageSource):
+ self.debug("Generating thumbnail for image: %s", path_from_uri(self.uri))
+ return GdkPixbuf.Pixbuf.new_from_file_at_scale(
+ Gst.uri_get_location(self.uri), -1, self.thumb_height, True)
+
+ if isinstance(self.ges_elem, GES.VideoTestSource):
+ self.debug("Generating thumbnail for color")
+ pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, THUMB_HEIGHT, THUMB_HEIGHT)
+ res, argb = self.ges_elem.get_child_property("foreground-color")
+ assert res
+ rgba = ((argb & 0xffffff) << 8) | ((argb & 0xff000000) >> 24)
+ pixbuf.fill(rgba)
+ return pixbuf
+
+ raise Exception("Unsupported ges_source type: %s" % type(self.ges_elem))
+
def _update_thumbnails(self):
"""Updates the thumbnail widgets for the clip at the current zoom."""
if not self.thumb_width:
@@ -493,6 +513,11 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
"""Handles the changing of the duration of the clip."""
self._update_thumbnails()
+ def _source_deep_notify_cb(self, source, unused_gstelement, pspec):
+ """Handles updates in the VideoTestSource."""
+ if pspec.name == "foreground-color":
+ self.become_controlled()
+
def set_selected(self, selected):
if selected:
opacity = 0.5
@@ -503,8 +528,7 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable):
thumb.props.opacity = opacity
def start_generation(self):
- self.debug("Waiting for UI to become idle for: %s",
- path_from_uri(self.uri))
+ self.debug("Waiting for UI to become idle for: %s", self.uri)
self.__start_id = GLib.idle_add(self._start_thumbnailing_cb,
priority=GLib.PRIORITY_LOW)
diff --git a/pitivi/utils/widgets.py b/pitivi/utils/widgets.py
index 1d33bb93..8247b8fb 100644
--- a/pitivi/utils/widgets.py
+++ b/pitivi/utils/widgets.py
@@ -1454,3 +1454,12 @@ class ColorPickerButton(Gtk.Button):
self.dropper_grab_widget.grab_remove()
self.pointer_device = None
self.dropper_grab_widget = None
+
+ def calculate_argb(self):
+ argb = 0
+ argb += (1 * 255) * 256 ** 3
+ argb += float(self.color_r) * 256 ** 2
+ argb += float(self.color_g) * 256 ** 1
+ argb += float(self.color_b) * 256 ** 0
+ argb = int(argb)
+ return argb
diff --git a/pitivi/viewer/overlay.py b/pitivi/viewer/overlay.py
index fbb79e6a..5d30f1a8 100644
--- a/pitivi/viewer/overlay.py
+++ b/pitivi/viewer/overlay.py
@@ -54,7 +54,7 @@ class Overlay(Gtk.DrawingArea, Loggable):
def _select(self):
self.stack.selected_overlay = self
self.stack.app.gui.editor.timeline_ui.timeline.selection.set_selection([self._source], SELECT)
- if isinstance(self._source, (GES.TitleSource, GES.VideoUriSource)):
+ if isinstance(self._source, (GES.TitleSource, GES.VideoUriSource, GES.VideoTestSource)):
page = 0
else:
self.warning("Unknown clip type: %s", self._source)
diff --git a/tests/test_clipproperties.py b/tests/test_clipproperties.py
index 203a1397..472b6885 100644
--- a/tests/test_clipproperties.py
+++ b/tests/test_clipproperties.py
@@ -357,7 +357,7 @@ class ClipPropertiesTest(BaseTestUndoTimeline):
clipproperties.new_project_loaded_cb(None, self.project)
self.project.pipeline.get_position = mock.Mock(return_value=0)
- clipproperties.create_cb(None)
+ clipproperties.create_title_clip_cb(None)
ps1 = self._get_title_source_child_props()
self.action_log.undo()
diff --git a/tests/test_clipproperties_color.py b/tests/test_clipproperties_color.py
new file mode 100644
index 00000000..17c75ff2
--- /dev/null
+++ b/tests/test_clipproperties_color.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (C) 2020 Andrew Hazel, Thomas Braccia, Troy Ogden, Robert Kirkpatrick
+#
+# 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.clip_properties.color module."""
+# pylint: disable=protected-access
+from unittest import mock
+
+from gi.repository import GES
+
+from pitivi.clipproperties import ClipProperties
+from tests import common
+from tests.test_undo_timeline import BaseTestUndoTimeline
+
+
+class ColorPropertiesTest(BaseTestUndoTimeline):
+ """Tests for the ColorProperties class."""
+
+ def test_create_hard_coded(self):
+ """Exercise creation of a color test clip."""
+ # Wait until the project creates a layer in the timeline.
+ common.create_main_loop().run(until_empty=True)
+
+ from pitivi.timeline.timeline import TimelineContainer
+ timeline_container = TimelineContainer(self.app)
+ timeline_container.set_project(self.project)
+ self.app.gui.editor.timeline_ui = timeline_container
+
+ clipproperties = ClipProperties(self.app)
+ clipproperties.new_project_loaded_cb(None, self.project)
+ self.project.pipeline.get_position = mock.Mock(return_value=0)
+
+ clipproperties.create_color_clip_cb(None)
+ clips = self.layer.get_clips()
+ pattern = clips[0].get_vpattern()
+ self.assertEqual(pattern, GES.VideoTestPattern.SOLID_COLOR)
+
+ self.action_log.undo()
+ self.assertListEqual(self.layer.get_clips(), [])
+
+ self.action_log.redo()
+ self.assertListEqual(self.layer.get_clips(), clips)
+
+ def test_color_change(self):
+ """Exercise the changing of colors for color clip."""
+ # Wait until the project creates a layer in the timeline.
+ common.create_main_loop().run(until_empty=True)
+
+ from pitivi.timeline.timeline import TimelineContainer
+ timeline_container = TimelineContainer(self.app)
+ timeline_container.set_project(self.project)
+ self.app.gui.editor.timeline_ui = timeline_container
+
+ clipproperties = ClipProperties(self.app)
+ clipproperties.new_project_loaded_cb(None, self.project)
+ self.project.pipeline.get_position = mock.Mock(return_value=0)
+
+ clipproperties.create_color_clip_cb(None)
+
+ color_expander = clipproperties.color_expander
+ color_picker_mock = mock.Mock()
+ color_picker_mock.calculate_argb.return_value = 1 << 24 | 2 << 16 | 3 << 8 | 4
+ color_expander._color_picker_value_changed_cb(color_picker_mock)
+ color = color_expander.source.get_child_property("foreground-color")[1]
+ self.assertEqual(color, 0x1020304)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]