group: Make deep copying actually copy deep

Allowing pasting groups paste exactly what had been copied

And not the new version of the contained objects

This technically breaks the C API but this is a new API and I believe
and hope nobody is using it right now.

Reviewed-by: Thibault Saunier <thibault.saunier@collabora.com>
Differential Revision: https://phabricator.freedesktop.org/D616
This commit is contained in:
Thibault Saunier 2016-01-01 11:56:27 +01:00
parent 1c875961fc
commit 2fae9ee50d
12 changed files with 260 additions and 53 deletions

View file

@ -71,6 +71,12 @@ _sync_element_to_layer_property_float (GESTrackElement * trksrc,
gfloat value; gfloat value;
parent = ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (trksrc)); parent = ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (trksrc));
if (!parent) {
GST_DEBUG_OBJECT (trksrc, "Not in a clip... doing nothing");
return;
}
layer = ges_clip_get_layer (GES_CLIP (parent)); layer = ges_clip_get_layer (GES_CLIP (parent));
gst_object_unref (parent); gst_object_unref (parent);
@ -81,12 +87,12 @@ _sync_element_to_layer_property_float (GESTrackElement * trksrc,
g_object_set (element, propname, value, NULL); g_object_set (element, propname, value, NULL);
GST_DEBUG_OBJECT (trksrc, "Setting %s to %f", propname, value); GST_DEBUG_OBJECT (trksrc, "Setting %s to %f", propname, value);
gst_object_unref (layer);
} else { } else {
GST_DEBUG_OBJECT (trksrc, "NOT setting the %s", propname); GST_DEBUG_OBJECT (trksrc, "NOT setting the %s", propname);
} }
gst_object_unref (layer);
} }
static void static void

View file

@ -48,6 +48,7 @@ ges_audio_uri_source_create_source (GESTrackElement * trksrc)
GESAudioUriSource *self; GESAudioUriSource *self;
GESTrack *track; GESTrack *track;
GstElement *decodebin; GstElement *decodebin;
const GstCaps *caps = NULL;
self = (GESAudioUriSource *) trksrc; self = (GESAudioUriSource *) trksrc;
@ -55,7 +56,10 @@ ges_audio_uri_source_create_source (GESTrackElement * trksrc)
decodebin = gst_element_factory_make ("uridecodebin", NULL); decodebin = gst_element_factory_make ("uridecodebin", NULL);
g_object_set (decodebin, "caps", ges_track_get_caps (track), if (track)
caps = ges_track_get_caps (track);
g_object_set (decodebin, "caps", caps,
"expose-all-streams", FALSE, "uri", self->uri, NULL); "expose-all-streams", FALSE, "uri", self->uri, NULL);
return decodebin; return decodebin;

View file

@ -61,6 +61,9 @@ struct _GESClipPrivate
guint nb_effects; guint nb_effects;
GList *copied_track_elements;
GESLayer *copied_layer;
/* The formats supported by this Clip */ /* The formats supported by this Clip */
GESTrackType supportedformats; GESTrackType supportedformats;
}; };
@ -625,21 +628,43 @@ _edit (GESContainer * container, GList * layers,
return ret; return ret;
} }
static gboolean static void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
GList *tmp;
GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
ccopy->priv->copied_track_elements =
g_list_append (ccopy->priv->copied_track_elements,
ges_timeline_element_copy (tmp->data, TRUE));
}
if (self->priv->copied_layer)
ccopy->priv->copied_layer = g_object_ref (self->priv->copied_layer);
else if (self->priv->layer)
ccopy->priv->copied_layer = g_object_ref (self->priv->layer);
}
static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref, _paste (GESTimelineElement * element, GESTimelineElement * ref,
GstClockTime paste_position) GstClockTime paste_position)
{ {
GList *tmp; GList *tmp;
GESClip *self = GES_CLIP (element); GESClip *self = GES_CLIP (element);
GESClip *refclip = GES_CLIP (ref); GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
ges_clip_set_moving_from_layer (self, TRUE); if (self->priv->copied_layer)
ges_layer_add_clip (refclip->priv->layer, self); nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer);
ges_clip_set_moving_from_layer (self, FALSE);
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (self), paste_position); ges_clip_set_moving_from_layer (nclip, TRUE);
if (self->priv->copied_layer)
ges_layer_add_clip (self->priv->copied_layer, nclip);
ges_clip_set_moving_from_layer (nclip, FALSE);
for (tmp = GES_CONTAINER_CHILDREN (refclip); tmp; tmp = tmp->next) { ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) {
GESTrackElement *new_trackelement, *trackelement = GESTrackElement *new_trackelement, *trackelement =
GES_TRACK_ELEMENT (tmp->data); GES_TRACK_ELEMENT (tmp->data);
@ -651,7 +676,7 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
continue; continue;
} }
ges_container_add (GES_CONTAINER (self), ges_container_add (GES_CONTAINER (nclip),
GES_TIMELINE_ELEMENT (new_trackelement)); GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement), ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
@ -661,7 +686,7 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
GST_CLOCK_TIME_NONE); GST_CLOCK_TIME_NONE);
} }
return TRUE; return GES_TIMELINE_ELEMENT (nclip);
} }
@ -703,6 +728,16 @@ ges_clip_set_property (GObject * object, guint property_id,
} }
} }
static void
ges_clip_finalize (GObject * object)
{
GESClip *self = GES_CLIP (object);
g_list_free_full (self->priv->copied_track_elements, g_object_unref);
G_OBJECT_CLASS (ges_clip_parent_class)->finalize (object);
}
static void static void
ges_clip_class_init (GESClipClass * klass) ges_clip_class_init (GESClipClass * klass)
{ {
@ -714,6 +749,7 @@ ges_clip_class_init (GESClipClass * klass)
object_class->get_property = ges_clip_get_property; object_class->get_property = ges_clip_get_property;
object_class->set_property = ges_clip_set_property; object_class->set_property = ges_clip_set_property;
object_class->finalize = ges_clip_finalize;
klass->create_track_elements = ges_clip_create_track_elements_func; klass->create_track_elements = ges_clip_create_track_elements_func;
klass->create_track_element = NULL; klass->create_track_element = NULL;
@ -754,6 +790,7 @@ ges_clip_class_init (GESClipClass * klass)
element_class->set_priority = _set_priority; element_class->set_priority = _set_priority;
element_class->set_max_duration = _set_max_duration; element_class->set_max_duration = _set_max_duration;
element_class->paste = _paste; element_class->paste = _paste;
element_class->deep_copy = _deep_copy;
container_class->add_child = _add_child; container_class->add_child = _add_child;
container_class->remove_child = _remove_child; container_class->remove_child = _remove_child;

View file

@ -81,6 +81,8 @@ struct _GESContainerPrivate
/* List of GESTimelineElement being in the "child-added" signal /* List of GESTimelineElement being in the "child-added" signal
* emission stage */ * emission stage */
GList *adding_children; GList *adding_children;
GList *copied_children;
}; };
enum enum
@ -101,8 +103,11 @@ _free_mapping (ChildMapping * mapping)
GESTimelineElement *child = mapping->child; GESTimelineElement *child = mapping->child;
/* Disconnect all notify listeners */ /* Disconnect all notify listeners */
if (mapping->start_notifyid)
g_signal_handler_disconnect (child, mapping->start_notifyid); g_signal_handler_disconnect (child, mapping->start_notifyid);
if (mapping->duration_notifyid)
g_signal_handler_disconnect (child, mapping->duration_notifyid); g_signal_handler_disconnect (child, mapping->duration_notifyid);
if (mapping->inpoint_notifyid)
g_signal_handler_disconnect (child, mapping->inpoint_notifyid); g_signal_handler_disconnect (child, mapping->inpoint_notifyid);
ges_timeline_element_set_parent (child, NULL); ges_timeline_element_set_parent (child, NULL);
@ -287,28 +292,51 @@ _get_track_types (GESTimelineElement * object)
return types ^ GES_TRACK_TYPE_UNKNOWN; return types ^ GES_TRACK_TYPE_UNKNOWN;
} }
static gboolean static void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
GList *tmp;
GESContainer *self = GES_CONTAINER (element), *ccopy = GES_CONTAINER (copy);
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
ChildMapping *map;
map =
g_slice_dup (ChildMapping, g_hash_table_lookup (self->priv->mappings,
tmp->data));
map->child = ges_timeline_element_copy (tmp->data, TRUE);
map->start_notifyid = 0;
map->inpoint_notifyid = 0;
map->duration_notifyid = 0;
ccopy->priv->copied_children = g_list_prepend (ccopy->priv->copied_children,
map);
}
}
static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref, _paste (GESTimelineElement * element, GESTimelineElement * ref,
GstClockTime paste_position) GstClockTime paste_position)
{ {
GList *tmp; GList *tmp;
GESContainer *self = GES_CONTAINER (element);
GESContainer *refcontainer = GES_CONTAINER (ref);
for (tmp = GES_CONTAINER_CHILDREN (refcontainer); tmp; tmp = tmp->next) {
ChildMapping *map; ChildMapping *map;
GESTimelineElement *child, *refchild = GES_TIMELINE_ELEMENT (tmp->data); GESContainer *ncontainer =
GES_CONTAINER (ges_timeline_element_copy (element, FALSE));
GESContainer *self = GES_CONTAINER (element);
map = g_hash_table_lookup (refcontainer->priv->mappings, refchild); for (tmp = self->priv->copied_children; tmp; tmp = tmp->next) {
child = ges_timeline_element_copy (GES_TIMELINE_ELEMENT (refchild), TRUE); GESTimelineElement *nchild;
ges_timeline_element_paste (child, paste_position + map->start_offset); map = tmp->data;
ges_timeline_element_set_timeline (element, nchild =
ges_timeline_element_paste (map->child,
paste_position - map->start_offset);
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (ncontainer),
GES_TIMELINE_ELEMENT_TIMELINE (ref)); GES_TIMELINE_ELEMENT_TIMELINE (ref));
ges_container_add (self, child); ges_container_add (ncontainer, nchild);
} }
return TRUE; return GES_TIMELINE_ELEMENT (ncontainer);
} }
@ -327,6 +355,17 @@ _dispose (GObject * object)
G_OBJECT_CLASS (ges_container_parent_class)->dispose (object); G_OBJECT_CLASS (ges_container_parent_class)->dispose (object);
} }
static void
_finalize (GObject * object)
{
GESContainer *self = GES_CONTAINER (object);
g_list_free_full (self->priv->copied_children,
(GDestroyNotify) _free_mapping);
G_OBJECT_CLASS (ges_container_parent_class)->dispose (object);
}
static void static void
_get_property (GObject * container, guint property_id, _get_property (GObject * container, guint property_id,
GValue * value, GParamSpec * pspec) GValue * value, GParamSpec * pspec)
@ -366,6 +405,7 @@ ges_container_class_init (GESContainerClass * klass)
object_class->get_property = _get_property; object_class->get_property = _get_property;
object_class->set_property = _set_property; object_class->set_property = _set_property;
object_class->dispose = _dispose; object_class->dispose = _dispose;
object_class->finalize = _finalize;
/** /**
* GESContainer:height: * GESContainer:height:
@ -415,6 +455,7 @@ ges_container_class_init (GESContainerClass * klass)
element_class->lookup_child = _lookup_child; element_class->lookup_child = _lookup_child;
element_class->get_track_types = _get_track_types; element_class->get_track_types = _get_track_types;
element_class->paste = _paste; element_class->paste = _paste;
element_class->deep_copy = _deep_copy;
/* No default implementations */ /* No default implementations */
klass->remove_child = NULL; klass->remove_child = NULL;

View file

@ -598,21 +598,21 @@ _group (GList * containers)
return ret; return ret;
} }
static gboolean static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref, _paste (GESTimelineElement * element, GESTimelineElement * ref,
GstClockTime paste_position) GstClockTime paste_position)
{ {
if (GES_TIMELINE_ELEMENT_CLASS (parent_class)->paste (element, GESTimelineElement *ngroup =
ref, paste_position)) { GES_TIMELINE_ELEMENT_CLASS (parent_class)->paste (element, ref,
paste_position);
if (GES_CONTAINER_CHILDREN (element)) if (ngroup) {
if (GES_CONTAINER_CHILDREN (ngroup))
timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN
(element)->data), GES_GROUP (element)); (ngroup)->data), GES_GROUP (element));
return TRUE;
} }
return FALSE; return ngroup;
} }

View file

@ -1697,25 +1697,27 @@ ges_timeline_element_get_track_types (GESTimelineElement * self)
* using ges_timeline_element_copy with recurse=TRUE set, * using ges_timeline_element_copy with recurse=TRUE set,
* otherwise it will fail. * otherwise it will fail.
* *
* Returns: (transfer none): Paste @self copying the element
*
* Since: 1.6.0 * Since: 1.6.0
*/ */
gboolean GESTimelineElement *
ges_timeline_element_paste (GESTimelineElement * self, ges_timeline_element_paste (GESTimelineElement * self,
GstClockTime paste_position) GstClockTime paste_position)
{ {
gboolean res; GESTimelineElement *res;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
if (!self->priv->copied_from) { if (!self->priv->copied_from) {
GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!"); GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!");
return FALSE; return NULL;
} }
if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste) { if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste) {
GST_ERROR_OBJECT (self, "No paste vmethod implemented"); GST_ERROR_OBJECT (self, "No paste vmethod implemented");
return FALSE; return NULL;
} }
res = GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste (self, res = GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste (self,
@ -1723,5 +1725,5 @@ ges_timeline_element_paste (GESTimelineElement * self,
g_clear_object (&self->priv->copied_from); g_clear_object (&self->priv->copied_from);
return res; return g_object_ref (res);
} }

View file

@ -186,7 +186,9 @@ struct _GESTimelineElementClass
gboolean (*roll_end) (GESTimelineElement *self, guint64 end); gboolean (*roll_end) (GESTimelineElement *self, guint64 end);
gboolean (*trim) (GESTimelineElement *self, guint64 start); gboolean (*trim) (GESTimelineElement *self, guint64 start);
void (*deep_copy) (GESTimelineElement *self, GESTimelineElement *copy); void (*deep_copy) (GESTimelineElement *self, GESTimelineElement *copy);
gboolean (*paste) (GESTimelineElement *self, GESTimelineElement *ref_element,
GESTimelineElement * (*paste) (GESTimelineElement *self,
GESTimelineElement *ref_element,
GstClockTime paste_position); GstClockTime paste_position);
GParamSpec** (*list_children_properties) (GESTimelineElement * self, guint *n_properties); GParamSpec** (*list_children_properties) (GESTimelineElement * self, guint *n_properties);
@ -279,7 +281,7 @@ gboolean ges_timeline_element_add_child_property (GESTimelineElement * self,
gboolean ges_timeline_element_remove_child_property(GESTimelineElement * self, gboolean ges_timeline_element_remove_child_property(GESTimelineElement * self,
GParamSpec *pspec); GParamSpec *pspec);
gboolean ges_timeline_element_paste (GESTimelineElement * self, GESTimelineElement * ges_timeline_element_paste (GESTimelineElement * self,
GstClockTime paste_position); GstClockTime paste_position);
GESTrackType ges_timeline_element_get_track_types (GESTimelineElement * self); GESTrackType ges_timeline_element_get_track_types (GESTimelineElement * self);

View file

@ -124,7 +124,6 @@ ges_video_source_create_element (GESTrackElement * trksrc)
GstElement *positionner, *videoscale, *videorate, *capsfilter, *videoconvert, GstElement *positionner, *videoscale, *videorate, *capsfilter, *videoconvert,
*deinterlace; *deinterlace;
const gchar *props[] = { "alpha", "posx", "posy", "width", "height", NULL }; const gchar *props[] = { "alpha", "posx", "posy", "width", "height", NULL };
GESTimelineElement *parent;
if (!source_class->create_source) if (!source_class->create_source)
return NULL; return NULL;
@ -172,14 +171,7 @@ ges_video_source_create_element (GESTrackElement * trksrc)
capsfilter, NULL); capsfilter, NULL);
} }
parent = ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (trksrc));
if (parent) {
self->priv->positionner = GST_FRAME_POSITIONNER (positionner); self->priv->positionner = GST_FRAME_POSITIONNER (positionner);
gst_object_unref (parent);
} else {
GST_ERROR ("No parent timeline element, SHOULD NOT HAPPEN");
}
self->priv->capsfilter = capsfilter; self->priv->capsfilter = capsfilter;
return topbin; return topbin;

View file

@ -50,14 +50,17 @@ ges_video_uri_source_create_source (GESTrackElement * trksrc)
GESVideoUriSource *self; GESVideoUriSource *self;
GESTrack *track; GESTrack *track;
GstElement *decodebin; GstElement *decodebin;
const GstCaps *caps = NULL;
self = (GESVideoUriSource *) trksrc; self = (GESVideoUriSource *) trksrc;
track = ges_track_element_get_track (trksrc); track = ges_track_element_get_track (trksrc);
if (track)
caps = ges_track_get_caps (track);
decodebin = gst_element_factory_make ("uridecodebin", NULL); decodebin = gst_element_factory_make ("uridecodebin", NULL);
g_object_set (decodebin, "caps", ges_track_get_caps (track), g_object_set (decodebin, "caps", caps,
"expose-all-streams", FALSE, "uri", self->uri, NULL); "expose-all-streams", FALSE, "uri", self->uri, NULL);
return decodebin; return decodebin;

View file

@ -198,6 +198,12 @@ ges_frame_positionner_set_source_and_filter (GstFramePositionner * pos,
pos->capsfilter = capsfilter; pos->capsfilter = capsfilter;
pos->current_track = ges_track_element_get_track (trksrc); pos->current_track = ges_track_element_get_track (trksrc);
if (!pos->current_track) {
GST_INFO_OBJECT (pos, "No track set, won't be usable");
return;
}
g_object_add_weak_pointer (G_OBJECT (pos->track_source), g_object_add_weak_pointer (G_OBJECT (pos->track_source),
((gpointer *) & pos->track_source)); ((gpointer *) & pos->track_source));
g_object_weak_ref (G_OBJECT (pos->current_track), g_object_weak_ref (G_OBJECT (pos->current_track),

View file

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015, Thibault Saunier
#
# 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, write to the
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301, USA.
import gi
gi.require_version("Gst", "1.0")
gi.require_version("GES", "1.0")
from gi.repository import Gst # noqa
from gi.repository import GES # noqa
import unittest # noqa
Gst.init(None)
GES.init()
class TestCopyPaste(unittest.TestCase):
def setUp(self):
self.timeline = GES.Timeline.new_audio_video()
self.assertEqual(len(self.timeline.get_tracks()), 2)
self.layer = self.timeline.append_layer()
def testCopyClipRemoveAndPaste(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
self.layer.add_clip(clip1)
self.assertEqual(len(clip1.get_children(False)), 2)
copy = clip1.copy(True)
self.assertEqual(len(self.layer.get_clips()), 1)
self.layer.remove_clip(clip1)
copy.paste(10)
self.assertEqual(len(self.layer.get_clips()), 1)

View file

@ -61,3 +61,63 @@ class TestCopyPaste(unittest.TestCase):
clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10) clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10)
clips = self.layer.get_clips() clips = self.layer.get_clips()
self.assertEqual(len(clips), 1) self.assertEqual(len(clips), 1)
def testPasteChangedGroup(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
clip2 = GES.TestClip.new()
clip2.props.start = 20
clip2.props.duration = 10
self.layer.add_clip(clip1)
self.layer.add_clip(clip2)
self.assertEqual(len(clip1.get_children(False)), 2)
group = GES.Group.new()
self.assertTrue(group.add(clip1))
self.assertEqual(len(group.get_children(False)), 1)
group_copy = group.copy(True)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group.add(clip2))
self.assertEqual(len(group.get_children(False)), 2)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group_copy.paste(10))
clips = self.layer.get_clips()
self.assertEqual(len(clips), 3)
self.assertEqual(clips[1].props.start, 10)
def testPasteChangedGroup(self):
clip1 = GES.TestClip.new()
clip1.props.duration = 10
clip2 = GES.TestClip.new()
clip2.props.start = 20
clip2.props.duration = 10
self.layer.add_clip(clip1)
self.layer.add_clip(clip2)
self.assertEqual(len(clip1.get_children(False)), 2)
group = GES.Group.new()
self.assertTrue(group.add(clip1))
self.assertEqual(len(group.get_children(False)), 1)
group_copy = group.copy(True)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group.add(clip2))
self.assertEqual(len(group.get_children(False)), 2)
self.assertEqual(len(group_copy.get_children(False)), 0)
self.assertTrue(group_copy.paste(10))
clips = self.layer.get_clips()
self.assertEqual(len(clips), 3)
self.assertEqual(clips[1].props.start, 10)