From a933d9540d4677c055d6b1e9a9fe6e9821d59478 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 26 Jun 2013 17:08:57 -0400 Subject: [PATCH] ges: Implement a GESGroup class, subclass of GESContainer The GESGroup class is used to group various GESContainer together, it can contain either GESClips or GESGroup or both. --- docs/libs/ges-docs.sgml | 1 + docs/libs/ges-sections.txt | 17 + ges/Makefile.am | 2 + ges/ges-clip.c | 4 +- ges/ges-container.c | 19 +- ges/ges-container.h | 1 + ges/ges-group.c | 550 ++++++++++++++++++++++++++++++ ges/ges-group.h | 56 +++ ges/ges-internal.h | 8 + ges/ges-timeline-element.c | 11 +- ges/ges-timeline.c | 204 +++++++---- ges/ges-types.h | 3 + ges/ges.c | 2 + ges/ges.h | 1 + tests/check/Makefile.am | 1 + tests/check/ges/basic.c | 4 + tests/check/ges/clip.c | 6 +- tests/check/ges/group.c | 500 +++++++++++++++++++++++++++ tests/check/ges/test-utils.h | 12 + tests/check/ges/timelineedition.c | 336 ++++++++++++++++-- 20 files changed, 1620 insertions(+), 118 deletions(-) create mode 100644 ges/ges-group.c create mode 100644 ges/ges-group.h create mode 100644 tests/check/ges/group.c diff --git a/docs/libs/ges-docs.sgml b/docs/libs/ges-docs.sgml index 9aaae742ac..b5f8c00003 100644 --- a/docs/libs/ges-docs.sgml +++ b/docs/libs/ges-docs.sgml @@ -61,6 +61,7 @@ platform as well as Windows. It is released under the GNU Library General Public + diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 4a9eaa12ce..2475886d1d 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -1091,6 +1091,23 @@ GES_IS_URI_CLIP_ASSET_CLASS GES_URI_CLIP_ASSET_GET_CLASS +
+ges-group +GESGroup +GESGroup +ges_group_new + +GESGroupClass +GESGroupPrivate +GES_GROUP +GES_IS_GROUP +GES_TYPE_GROUP +ges_group_get_type +GES_GROUP_CLASS +GES_IS_GROUP_CLASS +GES_GROUP_GET_CLASS +
+
ges-asset-track-file-source GESUriSourceAsset diff --git a/ges/Makefile.am b/ges/Makefile.am index 9642e3a315..eb26aa760a 100644 --- a/ges/Makefile.am +++ b/ges/Makefile.am @@ -64,6 +64,7 @@ libges_@GST_API_VERSION@_la_SOURCES = \ ges-smart-adder.c \ ges-smart-video-mixer.c \ ges-utils.c \ + ges-group.c \ gstframepositionner.c libges_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/ges/ @@ -125,6 +126,7 @@ libges_@GST_API_VERSION@include_HEADERS = \ ges-smart-adder.h \ ges-smart-video-mixer.h \ ges-utils.h \ + ges-group.h \ gstframepositionner.h noinst_HEADERS = \ diff --git a/ges/ges-clip.c b/ges/ges-clip.c index 66e764df5c..3d5ec814c8 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -686,7 +686,9 @@ ges_clip_class_init (GESClipClass * klass) /** * GESClip:layer: * - * The GESLayer where this clip is being used. + * The GESLayer where this clip is being used. If you want to connect to its + * notify signal you should connect to it with g_signal_connect_after as the + * signal emission can be stop in the first fase. */ properties[PROP_LAYER] = g_param_spec_object ("layer", "Layer", "The GESLayer where this clip is being used.", diff --git a/ges/ges-container.c b/ges/ges-container.c index 4b6c20c12e..13b178bff8 100644 --- a/ges/ges-container.c +++ b/ges/ges-container.c @@ -325,8 +325,11 @@ _child_start_changed_cb (GESTimelineElement * child, map = g_hash_table_lookup (priv->mappings, child); g_assert (map); - GST_FIXME_OBJECT (container, "We should make sure that our child does not" - "involve our start becoming < 0. In that case, undo the child move."); + if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS) { + map->start_offset = _START (container) - _START (child); + + return; + } /* We update all the children calling our set_start method */ container->initiated_move = child; @@ -349,6 +352,12 @@ _child_inpoint_changed_cb (GESTimelineElement * child, map = g_hash_table_lookup (priv->mappings, child); g_assert (map); + if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS) { + map->inpoint_offset = _START (container) - _START (child); + + return; + } + /* We update all the children calling our set_inpoint method */ container->initiated_move = child; _set_inpoint0 (element, _INPOINT (child) + map->inpoint_offset); @@ -370,6 +379,12 @@ _child_duration_changed_cb (GESTimelineElement * child, map = g_hash_table_lookup (priv->mappings, child); g_assert (map); + if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS) { + map->inpoint_offset = _START (container) - _START (child); + + return; + } + /* We update all the children calling our set_duration method */ container->initiated_move = child; _set_duration0 (element, _DURATION (child) + map->duration_offset); diff --git a/ges/ges-container.h b/ges/ges-container.h index 3edbd4786c..26542facf2 100644 --- a/ges/ges-container.h +++ b/ges/ges-container.h @@ -44,6 +44,7 @@ typedef enum GES_CHILDREN_UPDATE, GES_CHILDREN_IGNORE_NOTIFIES, GES_CHILDREN_UPDATE_OFFSETS, + GES_CHILDREN_LAST } GESChildrenControlMode; /** diff --git a/ges/ges-group.c b/ges/ges-group.c new file mode 100644 index 0000000000..777ce0354c --- /dev/null +++ b/ges/ges-group.c @@ -0,0 +1,550 @@ +/* GStreamer Editing Services + * Copyright (C) 2013 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:ges-group + * @short_description: Class that permits to group GESClip-s in a timeline, + * letting the user manage it a single GESTimelineElement + * + * A #GESGroup is an object which controls one or more + * #GESClips in one or more #GESLayer(s). + * + * To instanciate a group, you should use the ges_container_group method, + * this will be responsible for deciding what subclass of #GESContainer + * should be instaciated to group the various #GESTimelineElement passed + * in parametter. + */ + +#include "ges-group.h" +#include "ges.h" +#include "ges-internal.h" + +#include + +#define parent_class ges_group_parent_class +G_DEFINE_TYPE (GESGroup, ges_group, GES_TYPE_CONTAINER); + +#define GES_CHILDREN_INIBIT_SIGNAL_EMISSION (GES_CHILDREN_LAST + 1) + +struct _GESGroupPrivate +{ + gboolean reseting_start; + + guint32 max_layer_prio; + + /* This is used while were are setting ourselve a proper timing value, + * in this case the value should always be kept */ + gboolean setting_value; +}; + +enum +{ + PROP_0, + PROP_LAST +}; + +/* static GParamSpec *properties[PROP_LAST]; */ + +/**************************************************** + * Our listening of children * + ****************************************************/ +static void +_update_our_values (GESGroup * group) +{ + GList *tmp; + GESContainer *container = GES_CONTAINER (group); + guint32 min_layer_prio = G_MAXINT32, max_layer_prio = 0; + + for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { + GESContainer *child = tmp->data; + + if (GES_IS_CLIP (child)) { + GESLayer *layer = ges_clip_get_layer (GES_CLIP (child)); + gint32 prio = ges_layer_get_priority (layer); + + min_layer_prio = MIN (prio, min_layer_prio); + max_layer_prio = MAX (prio, max_layer_prio); + } else if (GES_IS_GROUP (child)) { + gint32 prio = _PRIORITY (child), height = GES_CONTAINER_HEIGHT (child); + + min_layer_prio = MIN (prio, min_layer_prio); + max_layer_prio = MAX ((prio + height), max_layer_prio); + } + } + + if (min_layer_prio != _PRIORITY (group)) { + group->priv->setting_value = TRUE; + _set_priority0 (GES_TIMELINE_ELEMENT (group), min_layer_prio); + group->priv->setting_value = FALSE; + for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + guint32 child_prio = GES_IS_CLIP (child) ? + ges_clip_get_layer_priority (GES_CLIP (child)) : _PRIORITY (child); + + _ges_container_set_priority_offset (container, + child, min_layer_prio - child_prio); + } + } + + group->priv->max_layer_prio = max_layer_prio; + _ges_container_set_height (GES_CONTAINER (group), + max_layer_prio - min_layer_prio + 1); +} + +static void +_child_clip_changed_layer_cb (GESTimelineElement * clip, + GParamSpec * arg G_GNUC_UNUSED, GESGroup * group) +{ + gint offset, layer_prio = ges_clip_get_layer_priority (GES_CLIP (clip)); + GESContainer *container = GES_CONTAINER (group); + + if (container->children_control_mode != GES_CHILDREN_UPDATE) { + if (container->children_control_mode == GES_CHILDREN_INIBIT_SIGNAL_EMISSION) { + container->children_control_mode = GES_CHILDREN_UPDATE; + g_signal_stop_emission_by_name (clip, "notify::layer"); + } + return; + } + + offset = _ges_container_get_priority_offset (container, clip); + + if (layer_prio + offset < 0 || + (GES_TIMELINE_ELEMENT_TIMELINE (group) && + layer_prio + offset + GES_CONTAINER_HEIGHT (group) - 1 > + g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers))) { + GESLayer *old_layer = + g_list_nth_data (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers, + _PRIORITY (group) - offset); + + GST_INFO_OBJECT (container, "Trying to move to a layer outside of" + "the timeline layers, moving back to old layer (prio %i)", + _PRIORITY (group) - offset); + + container->children_control_mode = GES_CHILDREN_INIBIT_SIGNAL_EMISSION; + ges_clip_move_to_layer (GES_CLIP (clip), old_layer); + g_signal_stop_emission_by_name (clip, "notify::layer"); + + return; + } + + container->initiated_move = clip; + _set_priority0 (GES_TIMELINE_ELEMENT (group), layer_prio + offset); + container->initiated_move = NULL; +} + +static void +_child_group_priority_changed (GESTimelineElement * child, + GParamSpec * arg G_GNUC_UNUSED, GESGroup * group) +{ + gint offset; + GESContainer *container = GES_CONTAINER (group); + + if (container->children_control_mode != GES_CHILDREN_UPDATE) { + GST_DEBUG_OBJECT (group, "Ignoring updated"); + return; + } + + offset = _ges_container_get_priority_offset (container, child); + + if (_PRIORITY (group) + offset < 0 || + (GES_TIMELINE_ELEMENT_TIMELINE (group) && + _PRIORITY (group) + offset + GES_CONTAINER_HEIGHT (group) > + g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers))) { + + GST_WARNING_OBJECT (container, "Trying to move to a layer outside of" + "the timeline layers"); + + return; + } + + container->initiated_move = child; + _set_priority0 (GES_TIMELINE_ELEMENT (group), _PRIORITY (child) + offset); + container->initiated_move = NULL; +} + +/**************************************************** + * GESTimelineElement vmethods * + ****************************************************/ +static gboolean +_trim (GESTimelineElement * group, GstClockTime start) +{ + GList *tmp; + GstClockTime last_child_end = 0; + GESContainer *container = GES_CONTAINER (group); + GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group); + gboolean ret = TRUE, expending = (start < _START (group)); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + + return FALSE; + } + + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + + if (expending) { + /* If the start if bigger, we do not touch it (in case we are expending) + */ + if (_START (child) > _START (group)) + continue; + ret &= ges_timeline_element_trim (child, start); + } else { + if (start > _END (child)) + ret &= ges_timeline_element_trim (child, _END (child)); + else if (_START (child) < start && _DURATION (child)) + ret &= ges_timeline_element_trim (child, start); + + } + } + + for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) { + if (_DURATION (tmp->data)) + last_child_end = + MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end); + } + + GES_GROUP (group)->priv->setting_value = TRUE; + _set_start0 (group, start); + _set_duration0 (group, last_child_end - start); + GES_GROUP (group)->priv->setting_value = FALSE; + container->children_control_mode = GES_CHILDREN_UPDATE; + + return ret; +} + +static gboolean +_set_priority (GESTimelineElement * element, guint32 priority) +{ + GList *tmp, *layers; + gint diff = priority - _PRIORITY (element); + GESContainer *container = GES_CONTAINER (element); + + if (GES_GROUP (element)->priv->setting_value == TRUE) + return TRUE; + + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + layers = GES_TIMELINE_ELEMENT_TIMELINE (element) ? + GES_TIMELINE_ELEMENT_TIMELINE (element)->layers : NULL; + + if (layers == NULL) { + GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing" + "anything, timeline: %" GST_PTR_FORMAT, + GES_TIMELINE_ELEMENT_TIMELINE (element)); + + return FALSE; + } else if (priority + GES_CONTAINER_HEIGHT (container) - 1 > + g_list_length (layers)) { + GST_WARNING_OBJECT (container, "Trying to move to a layer outside of" + "the timeline layers"); + return FALSE; + } + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + + if (child != container->initiated_move) { + if (GES_IS_CLIP (child)) { + guint32 layer_prio = + ges_clip_get_layer_priority (GES_CLIP (child)) + diff; + + GST_DEBUG_OBJECT (child, "moving from layer: %i to %i", + ges_clip_get_layer_priority (GES_CLIP (child)), layer_prio); + ges_clip_move_to_layer (GES_CLIP (child), + g_list_nth_data (layers, layer_prio)); + } else if (GES_IS_GROUP (child)) { + GST_DEBUG_OBJECT (child, "moving from %i to %i", + _PRIORITY (child), diff + _PRIORITY (child)); + ges_timeline_element_set_priority (child, diff + _PRIORITY (child)); + } + } + } + container->children_control_mode = GES_CHILDREN_UPDATE; + + return TRUE; +} + +static gboolean +_set_start (GESTimelineElement * element, GstClockTime start) +{ + GList *tmp; + gint64 diff = start - _START (element); + GESContainer *container = GES_CONTAINER (element); + + if (GES_GROUP (element)->priv->setting_value == TRUE) + /* Let GESContainer update itself */ + return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element, + start); + + + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GESTimelineElement *child = (GESTimelineElement *) tmp->data; + + if (child != container->initiated_move && + (_END (child) > _START (element) || _END (child) > start)) { + _set_start0 (child, _START (child) + diff); + } + } + container->children_control_mode = GES_CHILDREN_UPDATE; + + return TRUE; +} + +static gboolean +_set_inpoint (GESTimelineElement * element, GstClockTime inpoint) +{ + return FALSE; +} + +static gboolean +_set_duration (GESTimelineElement * element, GstClockTime duration) +{ + GList *tmp; + GstClockTime last_child_end = 0, new_end; + GESContainer *container = GES_CONTAINER (element); + GESGroupPrivate *priv = GES_GROUP (element)->priv; + + if (priv->setting_value == TRUE) + /* Let GESContainer update itself */ + return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element, + duration); + + if (container->initiated_move == NULL) { + gboolean expending = (_DURATION (element) < duration); + + new_end = _START (element) + duration; + container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + GstClockTime n_dur; + + if ((!expending && _END (child) > new_end) || + (expending && (_END (child) >= _END (element)))) { + n_dur = MAX (0, ((gint64) (new_end - _START (child)))); + _set_duration0 (child, n_dur); + } + } + container->children_control_mode = GES_CHILDREN_UPDATE; + } + + for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) { + if (_DURATION (tmp->data)) + last_child_end = + MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end); + } + + priv->setting_value = TRUE; + _set_duration0 (element, last_child_end - _START (element)); + priv->setting_value = FALSE; + + return FALSE; +} + +/**************************************************** + * * + * GESContainer virtual methods implementation * + * * + ****************************************************/ + +static gboolean +_add_child (GESContainer * group, GESTimelineElement * child) +{ + g_return_val_if_fail (GES_IS_CONTAINER (child), FALSE); + + return TRUE; +} + +static void +_child_added (GESContainer * group, GESTimelineElement * child) +{ + GList *children, *tmp; + + GESGroupPrivate *priv = GES_GROUP (group)->priv; + GstClockTime last_child_end = 0, first_child_start = G_MAXUINT64; + + children = GES_CONTAINER_CHILDREN (group); + + for (tmp = children; tmp; tmp = tmp->next) { + last_child_end = MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end); + first_child_start = + MIN (GES_TIMELINE_ELEMENT_START (tmp->data), first_child_start); + } + + priv->setting_value = TRUE; + if (first_child_start != GES_TIMELINE_ELEMENT_START (group)) { + group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start); + } + + if (last_child_end != GES_TIMELINE_ELEMENT_END (group)) { + _set_duration0 (GES_TIMELINE_ELEMENT (group), + last_child_end - first_child_start); + } + priv->setting_value = FALSE; + + group->children_control_mode = GES_CHILDREN_UPDATE; + _update_our_values (GES_GROUP (group)); + + if (GES_IS_CLIP (child)) { + g_signal_connect (child, "notify::layer", + (GCallback) _child_clip_changed_layer_cb, group); + } else if (GES_IS_GROUP (child), group) { + g_signal_connect (child, "notify::priority", + (GCallback) _child_group_priority_changed, group); + } +} + +static void +_child_removed (GESContainer * group, GESTimelineElement * child) +{ + GList *children; + GstClockTime first_child_start; + + _ges_container_sort_children (group); + + children = GES_CONTAINER_CHILDREN (group); + + if (GES_IS_CLIP (child)) + g_signal_handlers_disconnect_by_func (child, _child_clip_changed_layer_cb, + group); + else if (GES_IS_GROUP (child), group) + g_signal_handlers_disconnect_by_func (child, _child_group_priority_changed, + group); + + if (children == NULL) { + GST_FIXME_OBJECT (group, "Auto destroy myself?"); + return; + } + + first_child_start = GES_TIMELINE_ELEMENT_START (children->data); + if (first_child_start > GES_TIMELINE_ELEMENT_START (group)) { + group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES; + _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start); + group->children_control_mode = GES_CHILDREN_UPDATE; + } +} + +static GList * +_ungroup (GESContainer * group, gboolean recursive) +{ + GList *children, *tmp, *ret = NULL; + + children = ges_container_get_children (group); + for (tmp = children; tmp; tmp = tmp->next) { + GESTimelineElement *child = tmp->data; + + gst_object_ref (child); + ges_container_remove (group, child); + ret = g_list_append (ret, child); + } + g_list_free_full (children, gst_object_unref); + + timeline_remove_group (GES_TIMELINE_ELEMENT_TIMELINE (group), + GES_GROUP (group)); + + return ret; +} + +static GESContainer * +_group (GList * containers) +{ + GList *tmp; + GESTimeline *timeline = NULL; + GESContainer *ret = g_object_new (GES_TYPE_GROUP, NULL); + + for (tmp = containers; tmp; tmp = tmp->next) { + if (!timeline) { + timeline = GES_TIMELINE_ELEMENT_TIMELINE (tmp->data); + } else if (timeline != GES_TIMELINE_ELEMENT_TIMELINE (tmp->data)) { + g_object_unref (ret); + + return NULL; + } + + ges_container_add (ret, tmp->data); + } + + timeline_add_group (timeline, GES_GROUP (ret)); + + return ret; +} + + +/**************************************************** + * * + * GObject virtual methods implementation * + * * + ****************************************************/ +static void +ges_group_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_group_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_group_class_init (GESGroupClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GESGroupPrivate)); + + object_class->get_property = ges_group_get_property; + object_class->set_property = ges_group_set_property; + + element_class->trim = _trim; + element_class->set_duration = _set_duration; + element_class->set_inpoint = _set_inpoint; + element_class->set_start = _set_start; + element_class->set_priority = _set_priority; + /* TODO implement the deep_copy Virtual method */ + + container_class->add_child = _add_child; + container_class->child_added = _child_added; + container_class->child_removed = _child_removed; + container_class->ungroup = _ungroup; + container_class->group = _group; + container_class->grouping_priority = 0; +} + +static void +ges_group_init (GESGroup * self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GES_TYPE_GROUP, GESGroupPrivate); + + self->priv->setting_value = FALSE; +} diff --git a/ges/ges-group.h b/ges/ges-group.h new file mode 100644 index 0000000000..826b85e43c --- /dev/null +++ b/ges/ges-group.h @@ -0,0 +1,56 @@ +/* * Gstreamer + * + * Copyright (C) <2013> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef GES_GROUP_H +#define GES_GROUP_H + +#include +#include +#include + +G_BEGIN_DECLS + +#define GES_TYPE_GROUP (ges_group_get_type ()) +#define GES_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_GROUP, GESGroup)) +#define GES_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_GROUP, GESGroupClass)) +#define GES_IS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_GROUP)) +#define GES_IS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_GROUP)) +#define GES_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_GROUP, GESGroupClass)) + +typedef struct _GESGroupPrivate GESGroupPrivate; + +struct _GESGroup { + GESContainer parent; + + /*< private >*/ + GESGroupPrivate *priv; + + gpointer _ges_reserved[GES_PADDING]; +}; + +struct _GESGroupClass { + GESContainerClass parent_class; + + gpointer _ges_reserved[GES_PADDING]; +}; + +GType ges_group_get_type (void); + +G_END_DECLS +#endif /* _GES_GROUP_H */ diff --git a/ges/ges-internal.h b/ges/ges-internal.h index ccdec6fc76..e03fd7ee1a 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -83,6 +83,14 @@ timeline_move_object (GESTimeline *timeline, GESTrackElement * object, G_GNUC_INTERNAL gboolean timeline_context_to_layer (GESTimeline *timeline, gint offset); +G_GNUC_INTERNAL void +timeline_add_group (GESTimeline *timeline, + GESGroup *group); +G_GNUC_INTERNAL +void +timeline_remove_group (GESTimeline *timeline, + GESGroup *group); + G_GNUC_INTERNAL void ges_asset_cache_init (void); diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index 5fc88b4ab0..744abf969e 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -384,8 +384,8 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start) klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT - " new start: %" GST_TIME_FORMAT, GST_TIME_ARGS (start), - GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self))); + " new start: %" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)), GST_TIME_ARGS (start)); if (klass->set_start) { if (klass->set_start (self, start)) { @@ -393,6 +393,8 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]); } + GST_DEBUG_OBJECT (self, "New start: %" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self))); return; } @@ -489,8 +491,9 @@ ges_timeline_element_set_duration (GESTimelineElement * self, klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT - " new duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (duration), - GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self))); + " new duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)), + GST_TIME_ARGS (duration)); if (klass->set_duration) { if (klass->set_duration (self, duration)) { diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 28786e6766..d10bdb023f 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -119,10 +119,10 @@ struct _MoveContext GList *moving_trackelements; /* We use it as a set of Clip to move between layers */ - GHashTable *moving_clips; - /* Min priority of the objects currently in moving_clips */ + GHashTable *toplevel_containers; + /* Min priority of the objects currently in toplevel_containers */ guint min_move_layer; - /* Max priority of the objects currently in moving_clips */ + /* Max priority of the objects currently in toplevel_containers */ guint max_layer_prio; /* Never trim so duration would becomes < 0 */ @@ -184,6 +184,7 @@ struct _GESTimelinePrivate /* While we are creating and adding the TrackElements for a clip, we need to * ignore the child-added signal */ GESClip *ignore_track_element_added; + GList *groups; }; /* private structure to contain our track-related information */ @@ -324,9 +325,12 @@ ges_timeline_dispose (GObject * object) * objects aren't notified that their gnlobjects have been destroyed. */ - while (tl->tracks) { + while (tl->tracks) ges_timeline_remove_track (GES_TIMELINE (object), tl->tracks->data); - } + + while (priv->groups) + g_list_free_full (ges_container_ungroup (priv->groups->data, FALSE), + gst_object_unref); g_hash_table_unref (priv->by_start); g_hash_table_unref (priv->by_end); @@ -336,7 +340,7 @@ ges_timeline_dispose (GObject * object) g_sequence_free (priv->starts_ends); g_sequence_free (priv->tracksources); g_list_free (priv->movecontext.moving_trackelements); - g_hash_table_unref (priv->movecontext.moving_clips); + g_hash_table_unref (priv->movecontext.toplevel_containers); g_hash_table_unref (priv->auto_transitions); @@ -552,6 +556,18 @@ ges_timeline_init (GESTimeline * self) /* Private methods */ +static inline GESContainer * +get_toplevel_container (GESTrackElement * element) +{ + GESTimelineElement *ret = + ges_timeline_element_get_toplevel_parent ((GESTimelineElement + *) (element)); + + /* We own a ref to the elements ourself */ + gst_object_unref (ret); + return (GESContainer *) ret; +} + /* Sorting utils*/ static gint sort_layers (gpointer a, gpointer b) @@ -885,7 +901,8 @@ static inline void init_movecontext (MoveContext * mv_ctx, gboolean first_init) { if (G_UNLIKELY (first_init)) - mv_ctx->moving_clips = g_hash_table_new (g_direct_hash, g_direct_equal); + mv_ctx->toplevel_containers = + g_hash_table_new (g_direct_hash, g_direct_equal); mv_ctx->moving_trackelements = NULL; mv_ctx->max_trim_pos = G_MAXUINT64; @@ -900,7 +917,7 @@ static inline void clean_movecontext (MoveContext * mv_ctx) { g_list_free (mv_ctx->moving_trackelements); - g_hash_table_remove_all (mv_ctx->moving_clips); + g_hash_table_remove_all (mv_ctx->toplevel_containers); init_movecontext (mv_ctx, FALSE); } @@ -1060,7 +1077,7 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTimelinePrivate *priv = timeline->priv; GSequenceIter *iter, *prev_iter, *nxt_iter; GESTrackElement *tmp_trackelement; - GESClip *tmp_clip, *clip; + GESContainer *tmp_container, *container; GstClockTime *last_snap_ts = priv->movecontext.last_snap_ts; guint64 snap_distance = timeline->priv->snapping_distance; @@ -1081,7 +1098,7 @@ ges_timeline_snap_position (GESTimeline * timeline, } } - clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (trackelement)); + container = get_toplevel_container (trackelement); iter = g_sequence_search (priv->starts_ends, &timecode, (GCompareDataFunc) compare_uint64, NULL); @@ -1092,10 +1109,11 @@ ges_timeline_snap_position (GESTimeline * timeline, while (!g_sequence_iter_is_end (nxt_iter)) { next_tc = g_sequence_get (iter); tmp_trackelement = g_hash_table_lookup (timeline->priv->by_object, next_tc); - tmp_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (tmp_trackelement)); + tmp_container = get_toplevel_container (tmp_trackelement); off = timecode > *next_tc ? timecode - *next_tc : *next_tc - timecode; - if (next_tc != current && off <= snap_distance && clip != tmp_clip) { + if (next_tc != current && off <= snap_distance + && container != tmp_container) { ret = next_tc; break; @@ -1111,11 +1129,11 @@ ges_timeline_snap_position (GESTimeline * timeline, while (!g_sequence_iter_is_begin (prev_iter)) { prev_tc = g_sequence_get (prev_iter); tmp_trackelement = g_hash_table_lookup (timeline->priv->by_object, prev_tc); - tmp_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (tmp_trackelement)); + tmp_container = get_toplevel_container (tmp_trackelement); off1 = timecode > *prev_tc ? timecode - *prev_tc : *prev_tc - timecode; if (prev_tc != current && off1 < off && off1 <= snap_distance && - clip != tmp_clip) { + container != tmp_container) { ret = prev_tc; break; @@ -1139,35 +1157,39 @@ done: return ret; } -static inline GESClip * -add_moving_clip (MoveContext * mv_ctx, GESTrackElement * trackelement) +static inline GESContainer * +add_toplevel_container (MoveContext * mv_ctx, GESTrackElement * trackelement) { - GESClip *clip; - GESLayer *layer; guint layer_prio; - - clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (trackelement)); + GESContainer *toplevel = get_toplevel_container (trackelement); /* Avoid recalculating */ - if (!g_hash_table_lookup (mv_ctx->moving_clips, clip)) { - layer = ges_clip_get_layer (clip); - if (layer == NULL) { - GST_WARNING_OBJECT (clip, "Not in any layer, can not move" - " between layers"); + if (!g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel)) { + if (GES_IS_CLIP (toplevel)) { - } else { + layer_prio = ges_clip_get_layer_priority (GES_CLIP (toplevel)); + if (layer_prio == (guint32) - 1) { + GST_WARNING_OBJECT (toplevel, "Not in any layer, can not move" + " between layers"); - g_hash_table_insert (mv_ctx->moving_clips, clip, clip); - - layer_prio = ges_layer_get_priority (layer); + return toplevel; + } mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer, layer_prio); mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio, layer_prio); + } else if GES_IS_GROUP + (toplevel) { + mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer, + _PRIORITY (toplevel)); + mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio, + _PRIORITY (toplevel) + GES_CONTAINER_HEIGHT (toplevel)); + } else + g_assert_not_reached (); + + g_hash_table_insert (mv_ctx->toplevel_containers, toplevel, toplevel); - gst_object_unref (layer); - } } - return clip; + return toplevel; } static gboolean @@ -1292,10 +1314,10 @@ ges_timeline_set_moving_context (GESTimeline * timeline, GESTrackElement * obj, default: break; } - add_moving_clip (&timeline->priv->movecontext, editor_trackelement); + add_toplevel_container (&timeline->priv->movecontext, editor_trackelement); } else { - /* We add the main object to the moving_clips set */ - add_moving_clip (&timeline->priv->movecontext, obj); + /* We add the main object to the toplevel_containers set */ + add_toplevel_container (&timeline->priv->movecontext, obj); } @@ -1387,7 +1409,7 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj, { GList *tmp, *moved_clips = NULL; GESTrackElement *trackelement; - GESClip *clip; + GESContainer *container; guint64 duration, new_start, *snapped, *cur; gint64 offset; @@ -1415,11 +1437,11 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj, trackelement = GES_TRACK_ELEMENT (tmp->data); new_start = _START (trackelement) + offset; - clip = add_moving_clip (mv_ctx, trackelement); + container = add_toplevel_container (mv_ctx, trackelement); /* Make sure not to move 2 times the same Clip */ - if (g_list_find (moved_clips, clip) == NULL) { + if (g_list_find (moved_clips, container) == NULL) { _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start); - moved_clips = g_list_prepend (moved_clips, clip); + moved_clips = g_list_prepend (moved_clips, container); } } @@ -1445,12 +1467,16 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj, trackelement = GES_TRACK_ELEMENT (tmp->data); new_start = _START (trackelement) + offset; - clip = add_moving_clip (mv_ctx, trackelement); + container = add_toplevel_container (mv_ctx, trackelement); + if (GES_IS_GROUP (container)) + container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS; /* Make sure not to move 2 times the same Clip */ - if (g_list_find (moved_clips, clip) == NULL) { + if (g_list_find (moved_clips, container) == NULL) { _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start); - moved_clips = g_list_prepend (moved_clips, clip); + moved_clips = g_list_prepend (moved_clips, container); } + if (GES_IS_GROUP (container)) + container->children_control_mode = GES_CHILDREN_UPDATE; } @@ -1629,7 +1655,7 @@ gboolean timeline_move_object (GESTimeline * timeline, GESTrackElement * object, GList * layers, GESEdge edge, guint64 position) { - if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_RIPPLE, + if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_NORMAL, edge, layers)) { GST_DEBUG_OBJECT (object, "Could not move to %" GST_TIME_FORMAT, GST_TIME_ARGS (position)); @@ -1656,11 +1682,12 @@ ges_timeline_move_object_simple (GESTimeline * timeline, return FALSE; track_element = GES_TRACK_ELEMENT (element); - end = position + _DURATION (track_element); + end = position + _DURATION (get_toplevel_container (track_element)); cur = g_hash_table_lookup (timeline->priv->by_end, track_element); - GST_DEBUG_OBJECT (timeline, "Moving to %" GST_TIME_FORMAT " (end %" - GST_TIME_FORMAT ")", GST_TIME_ARGS (position), GST_TIME_ARGS (end)); + GST_DEBUG_OBJECT (timeline, "Moving %" GST_PTR_FORMAT "to %" + GST_TIME_FORMAT " (end %" GST_TIME_FORMAT ")", element, + GST_TIME_ARGS (position), GST_TIME_ARGS (end)); snap_end = ges_timeline_snap_position (timeline, track_element, cur, end, FALSE); @@ -1670,7 +1697,8 @@ ges_timeline_move_object_simple (GESTimeline * timeline, off1 = G_MAXUINT64; cur = g_hash_table_lookup (timeline->priv->by_start, track_element); - snap_st = ges_timeline_snap_position (timeline, track_element, cur, position, + snap_st = + ges_timeline_snap_position (timeline, track_element, cur, position, FALSE); if (snap_st) off2 = position > *snap_st ? position - *snap_st : *snap_st - position; @@ -1704,33 +1732,37 @@ timeline_context_to_layer (GESTimeline * timeline, gint offset) /* Layer's priority is always positive */ if (offset != 0 && (offset > 0 || mv_ctx->min_move_layer >= -offset)) { GHashTableIter iter; - GESClip *key, *value; - GESLayer *new_layer, *layer; + GESContainer *key, *value; + GESLayer *new_layer; guint prio; mv_ctx->ignore_needs_ctx = TRUE; GST_DEBUG ("Moving %d object, offset %d", - g_hash_table_size (mv_ctx->moving_clips), offset); + g_hash_table_size (mv_ctx->toplevel_containers), offset); - g_hash_table_iter_init (&iter, mv_ctx->moving_clips); + g_hash_table_iter_init (&iter, mv_ctx->toplevel_containers); while (g_hash_table_iter_next (&iter, (gpointer *) & key, (gpointer *) & value)) { - layer = ges_clip_get_layer (value); - prio = ges_layer_get_priority (layer); - /* We know that the layer exists as we created it */ - new_layer = GES_LAYER (g_list_nth_data (timeline->layers, prio + offset)); + if (GES_IS_CLIP (value)) { + prio = ges_clip_get_layer_priority (GES_CLIP (value)); - if (new_layer == NULL) { - do { - new_layer = ges_timeline_append_layer (timeline); - } while (ges_layer_get_priority (new_layer) < prio + offset); + /* We know that the layer exists as we created it */ + new_layer = + GES_LAYER (g_list_nth_data (timeline->layers, prio + offset)); + + if (new_layer == NULL) { + do { + new_layer = ges_timeline_append_layer (timeline); + } while (ges_layer_get_priority (new_layer) < prio + offset); + } + + ret &= ges_clip_move_to_layer (GES_CLIP (key), new_layer); + } else if (GES_IS_GROUP (value)) { + _set_priority0 (GES_TIMELINE_ELEMENT (value), + _PRIORITY (value) + offset); } - - ret &= ges_clip_move_to_layer (key, new_layer); - - gst_object_unref (layer); } /* Readjust min_move_layer */ @@ -1742,6 +1774,30 @@ timeline_context_to_layer (GESTimeline * timeline, gint offset) return ret; } +void +timeline_add_group (GESTimeline * timeline, GESGroup * group) +{ + GST_DEBUG_OBJECT (timeline, "Adding group %" GST_PTR_FORMAT, group); + + timeline->priv->movecontext.needs_move_ctx = TRUE; + timeline->priv->groups = g_list_prepend (timeline->priv->groups, + gst_object_ref_sink (group)); + + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline); +} + +void +timeline_remove_group (GESTimeline * timeline, GESGroup * group) +{ + GST_DEBUG_OBJECT (timeline, "Removing group %" GST_PTR_FORMAT, group); + + timeline->priv->groups = g_list_remove (timeline->priv->groups, group); + + timeline->priv->movecontext.needs_move_ctx = TRUE; + ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), NULL); + gst_object_unref (group); +} + static GPtrArray * select_tracks_for_object_default (GESTimeline * timeline, GESClip * clip, GESTrackElement * tr_object, gpointer user_data) @@ -1800,8 +1856,8 @@ layer_auto_transition_changed_cb (GESLayer * layer, } static void -clip_track_element_added_cb (GESClip * clip, GESTrackElement * track_element, - GESTimeline * timeline) +clip_track_element_added_cb (GESClip * clip, + GESTrackElement * track_element, GESTimeline * timeline) { guint i; GESTrack *track; @@ -1885,8 +1941,8 @@ clip_track_element_added_cb (GESClip * clip, GESTrackElement * track_element, } static void -clip_track_element_removed_cb (GESClip * clip, GESTrackElement * track_element, - GESTimeline * timeline) +clip_track_element_removed_cb (GESClip * clip, + GESTrackElement * track_element, GESTimeline * timeline) { GESTrack *track = ges_track_element_get_track (track_element); @@ -2081,17 +2137,19 @@ track_element_added_cb (GESTrack * track, GESTrackElement * track_element, /* Auto transition should be updated before we receive the signal */ g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::start", G_CALLBACK (trackelement_start_changed_cb), timeline); - g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::duration", - G_CALLBACK (trackelement_duration_changed_cb), timeline); - g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::priority", - G_CALLBACK (trackelement_priority_changed_cb), timeline); + g_signal_connect_after (GES_TRACK_ELEMENT (track_element), + "notify::duration", G_CALLBACK (trackelement_duration_changed_cb), + timeline); + g_signal_connect_after (GES_TRACK_ELEMENT (track_element), + "notify::priority", G_CALLBACK (trackelement_priority_changed_cb), + timeline); start_tracking_track_element (timeline, track_element); } static void -track_element_removed_cb (GESTrack * track, GESTrackElement * track_element, - GESTimeline * timeline) +track_element_removed_cb (GESTrack * track, + GESTrackElement * track_element, GESTimeline * timeline) { if (GES_IS_SOURCE (track_element)) { diff --git a/ges/ges-types.h b/ges/ges-types.h index b7b6262550..1516e0e256 100644 --- a/ges/ges-types.h +++ b/ges/ges-types.h @@ -86,6 +86,9 @@ typedef struct _GESTextOverlayClipClass GESTextOverlayClipClass; typedef struct _GESEffectClip GESEffectClip; typedef struct _GESEffectClipClass GESEffectClipClass; +typedef struct _GESGroup GESGroup; +typedef struct _GESGroupClass GESGroupClass; + typedef struct _GESTrack GESTrack; typedef struct _GESTrackClass GESTrackClass; diff --git a/ges/ges.c b/ges/ges.c index 2b4b54c491..d55d6b20ba 100644 --- a/ges/ges.c +++ b/ges/ges.c @@ -78,6 +78,8 @@ ges_init (void) GES_TYPE_TRANSITION_CLIP; GES_TYPE_OVERLAY_CLIP; + GES_TYPE_GROUP; + /* register formatter types with the system */ GES_TYPE_PITIVI_FORMATTER; GES_TYPE_XML_FORMATTER; diff --git a/ges/ges.h b/ges/ges.h index 013527f7ba..202f81094d 100644 --- a/ges/ges.h +++ b/ges/ges.h @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index fab4ae5dad..b8d0747bf9 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -41,6 +41,7 @@ check_PROGRAMS = \ ges/overlays\ ges/text_properties\ ges/mixers\ + ges/group\ ges/project noinst_LTLIBRARIES=$(testutils_noisnt_libraries) diff --git a/tests/check/ges/basic.c b/tests/check/ges/basic.c index 05e60f4e0e..7faabb9227 100644 --- a/tests/check/ges/basic.c +++ b/tests/check/ges/basic.c @@ -420,6 +420,7 @@ GST_START_TEST (test_ges_timeline_remove_track) tmp_layer = ges_clip_get_layer (GES_CLIP (s1)); fail_unless (tmp_layer == layer); gst_object_unref (tmp_layer); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); GST_DEBUG ("Creating a source"); s2 = ges_custom_source_clip_new (my_fill_track_func, NULL); @@ -428,6 +429,7 @@ GST_START_TEST (test_ges_timeline_remove_track) tmp_layer = ges_clip_get_layer (GES_CLIP (s2)); fail_unless (tmp_layer == layer); gst_object_unref (tmp_layer); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); GST_DEBUG ("Creating a source"); s3 = ges_custom_source_clip_new (my_fill_track_func, NULL); @@ -436,6 +438,7 @@ GST_START_TEST (test_ges_timeline_remove_track) tmp_layer = ges_clip_get_layer (GES_CLIP (s3)); fail_unless (tmp_layer == layer); gst_object_unref (tmp_layer); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); GST_DEBUG ("Add the layer to the timeline"); fail_unless (ges_timeline_add_layer (timeline, layer)); @@ -447,6 +450,7 @@ GST_START_TEST (test_ges_timeline_remove_track) fail_unless (g_list_find (layers, layer) != NULL); g_list_foreach (layers, (GFunc) gst_object_unref, NULL); g_list_free (layers); + ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1); GST_DEBUG ("Add the track to the timeline"); fail_unless (ges_timeline_add_track (timeline, track)); diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index ee366a5a7f..0fbb0b5a2d 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -287,7 +287,11 @@ GST_START_TEST (test_clip_group_ungroup) assert_equals_uint64 (_DURATION (clip2), 10); regrouped_clip = ges_container_group (containers); - fail_unless (regrouped_clip == NULL); + fail_unless (GES_IS_GROUP (regrouped_clip)); + assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)), + 2); + tmp = ges_container_ungroup (regrouped_clip, FALSE); + g_list_free_full (tmp, gst_object_unref); ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 0); regrouped_clip = ges_container_group (containers); diff --git a/tests/check/ges/group.c b/tests/check/ges/group.c new file mode 100644 index 0000000000..5644b23ef6 --- /dev/null +++ b/tests/check/ges/group.c @@ -0,0 +1,500 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2013> Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" +#include +#include + +GST_START_TEST (test_move_group) +{ + GESAsset *asset; + GESGroup *group; + GESTimeline *timeline; + GESLayer *layer, *layer1; + GESClip *clip, *clip1, *clip2; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + /** + * Our timeline: + * ------------- + * + * 0------------Group1---------------110 + * |-------- | + * layer: | clip | | + * |-------10 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 10--------20 50----------| + * |----------------------------------| + */ + clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clip1 = + ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clip2 = + ges_layer_add_asset (layer1, asset, 50, 0, 60, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, clip); + clips = g_list_prepend (clips, clip1); + clips = g_list_prepend (clips, clip2); + group = GES_GROUP (ges_container_group (clips)); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + ASSERT_OBJECT_REFCOUNT (group, "1 ref for the timeline", 1); + fail_unless (g_list_length (GES_CONTAINER_CHILDREN (group)) == 3); + assert_equals_int (GES_CONTAINER_HEIGHT (group), 2); + CHECK_OBJECT_PROPS (clip, 0, 0, 10); + CHECK_OBJECT_PROPS (clip1, 10, 0, 10); + CHECK_OBJECT_PROPS (clip2, 50, 0, 60); + CHECK_OBJECT_PROPS (group, 0, 0, 110); + + /* + * 0 10------------Group1---------------120 + * |-------- | + * layer: | clip | | + * |-------20 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 10); + CHECK_OBJECT_PROPS (clip, 10, 0, 10); + CHECK_OBJECT_PROPS (clip1, 20, 0, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 60); + CHECK_OBJECT_PROPS (group, 10, 0, 110); + + /* + * 0 10------------Group1---------------120 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 5); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 0, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 60); + CHECK_OBJECT_PROPS (group, 10, 0, 110); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 0--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip2), 50); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 0, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 5--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip1), 5); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * | 5--------- 0-----------| + * layer1: | | clip1 | | clip2 | + * | 20--------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip1), 5); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + + /* + * 0 20---Group1---------------110 + * | | + * layer: | | + * | | + * |--------------------------| + * 5--------- 0------------| + * layer1: | clip1 | | clip2 | + * 20--------30 60----------| + * |--------------------------| + */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 20); + CHECK_OBJECT_PROPS (clip, 15, 5, 0); + CHECK_OBJECT_PROPS (clip1, 20, 5, 10); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 20, 0, 90); + + /* + * 0 25---Group1---------------110 + * | | + * layer: | | + * | | + * |--------------------------| + * 10------ 0------------| + * layer1: | clip1 | | clip2 | + * 25------30 60----------| + * |--------------------------| + */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25); + CHECK_OBJECT_PROPS (clip, 15, 5, 0); + CHECK_OBJECT_PROPS (clip1, 25, 10, 5); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 25, 0, 85); + + /* + * 0 10------------Group1---------------110 + * |------ | + * layer: |clip | | + * |-----15 | + * |----------------------------------| + * 0----------------- 0-----------| + * layer1: | clip1 | | clip2 | + * |-----------------30 60----------| + * |----------------------------------| + */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 10, 0, 20); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 10, 0, 100); + + /* + * 0 25---Group1---------------110 + * | | + * layer: 15 | | + * |clip | | + * - |--------------------------| + * 15------ 0------------| + * layer1: | clip1 | | clip2 | + * 25------30 60----------| + * |--------------------------| + */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25); + CHECK_OBJECT_PROPS (clip, 15, 5, 0); + CHECK_OBJECT_PROPS (clip1, 25, 15, 5); + CHECK_OBJECT_PROPS (clip2, 60, 0, 50); + CHECK_OBJECT_PROPS (group, 25, 0, 85); + + /* + * 0 25---Group1--30 + * | | + * layer: 15 | | + * |clip | | + * - |------------ + * 15-----------| 60 + * layer1: | clip1 | |clip2 + * 25------------| - + * |------------| + */ + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 10); + CHECK_OBJECT_PROPS (clip, 15, 5, 0); + CHECK_OBJECT_PROPS (clip1, 25, 15, 5); + CHECK_OBJECT_PROPS (clip2, 60, 0, 0); + CHECK_OBJECT_PROPS (group, 25, 0, 5); + + /* + * 0 25---Group1---------------125 + * | | + * layer: 15 | | + * |clip | | + * - |--------------------------| + * 15-------------------------| + * layer1: | clip1 | clip2 | + * 25--------------60----------| + * |--------------------------| + */ + ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 100); + CHECK_OBJECT_PROPS (clip, 15, 5, 0); + CHECK_OBJECT_PROPS (clip1, 25, 15, 100); + CHECK_OBJECT_PROPS (clip2, 60, 0, 65); + CHECK_OBJECT_PROPS (group, 25, 0, 100); + + /* + * 0 20---Group1---------------120 + * | | + * layer: 15 | | + * |clip| | + * - |--------------------------| + * 15-------------------------| + * layer1: | clip1 | clip2 | + * 20-------------55----------| + * |--------------------------| + */ + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (group), 20); + CHECK_OBJECT_PROPS (clip, 15, 5, 0); + CHECK_OBJECT_PROPS (clip1, 20, 15, 100); + CHECK_OBJECT_PROPS (clip2, 55, 0, 65); + CHECK_OBJECT_PROPS (group, 20, 0, 100); + + /* + * 0 10---Group1---------------120 + * |-----15 | + * layer: | clip| | + * |------ | + * |--------------------------| + * 5--------------------------| + * layer1: | clip1 | clip2 | + * 10-------------55----------| + * |--------------------------| + */ + ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10); + CHECK_OBJECT_PROPS (clip, 10, 0, 5); + CHECK_OBJECT_PROPS (clip1, 10, 5, 110); + CHECK_OBJECT_PROPS (clip2, 55, 0, 65); + CHECK_OBJECT_PROPS (group, 10, 0, 110); + + ASSERT_OBJECT_REFCOUNT (group, "1 ref for the timeline", 1); + check_destroyed (G_OBJECT (timeline), G_OBJECT (group), NULL); + gst_object_unref (asset); +} + +GST_END_TEST; + + + +static void +_changed_layer_cb (GESTimelineElement * clip, + GParamSpec * arg G_GNUC_UNUSED, guint * nb_calls) +{ + *nb_calls = *nb_calls + 1; +} + +GST_START_TEST (test_group_in_group) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group, *group1; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1, *c2, *c3, *c4, *c5; + + guint nb_layer_notifies = 0; + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + /* Our timeline + * + * --0------------10-Group-----20---------------30-------Group1----------70 + * | +-----------+ |+-----------50 | + * L | | C | || C3 | | + * | +-----------+ |+-----------+ | + * --|-------------------------------------------|-----40----------------| + * | +------------+ +-------------+ | +--------60 | + * L1 | | C1 | | C2 | | | C4 | | + * | +------------+ +-------------+ | +--------+ | + * --|-------------------------------------------|-----------------------| + * | | +--------+| + * L2 | | | c5 || + * | | +--------+| + * --+-------------------------------------------+-----------------------+ + * + * L3 + * + * ----------------------------------------------------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c2 = ges_layer_add_asset (layer1, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + clips = g_list_prepend (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 10); + CHECK_OBJECT_PROPS (group, 0, 0, 30); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (NULL, c3); + clips = g_list_prepend (clips, c4); + clips = g_list_prepend (clips, c5); + group1 = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group1) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group1)); + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group1, 30, 0, 40); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (ges_container_add (GES_CONTAINER (group), + GES_TIMELINE_ELEMENT (group1))); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 10); + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group, 0, 0, 70); + CHECK_OBJECT_PROPS (group1, 30, 0, 40); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group1) == timeline); + + ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (c4), 50); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (c2, 30, 0, 10); + CHECK_OBJECT_PROPS (c3, 40, 0, 20); + CHECK_OBJECT_PROPS (c4, 50, 0, 20); + CHECK_OBJECT_PROPS (c5, 60, 0, 20); + CHECK_OBJECT_PROPS (group, 10, 0, 70); + CHECK_OBJECT_PROPS (group1, 40, 0, 40); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group1) == timeline); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + /* Our timeline + * + * L + * ----------------------------------------------------------------------- + * 0------------10-Group-----20---------------30-------Group1----------70 + * | +-----------+ |+-----------50 | + * L1 | | C | || C3 | | + * | +-----------+ |+-----------+ | + * | | | + * --|-------------------------------------------|-----40----------------| + * | +------------+ +-------------+ | +--------60 | + * L2 | | C1 | | C2 | | | C4 | | + * | +------------+ +-------------+ | +--------+ | + * --|-------------------------------------------|-----------------------| + * | | +--------+| + * L3 | | | c5 || + * | | +--------+| + * --+-------------------------------------------+-----------------------+ + */ + fail_unless (ges_clip_move_to_layer (c, layer1)); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + assert_equals_int (_PRIORITY (group), 1); + assert_equals_int (_PRIORITY (group1), 1); + + /* We can not move so far! */ + g_signal_connect_after (c4, "notify::layer", + (GCallback) _changed_layer_cb, &nb_layer_notifies); + fail_if (ges_clip_move_to_layer (c4, layer)); + assert_equals_int (nb_layer_notifies, 0); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + assert_equals_int (_PRIORITY (group), 1); + assert_equals_int (_PRIORITY (group1), 1); + + clips = ges_container_ungroup (GES_CONTAINER (group), FALSE); + assert_equals_int (g_list_length (clips), 4); + g_list_free_full (clips, gst_object_unref); + + gst_object_unref (timeline); + gst_object_unref (asset); +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-group"); + TCase *tc_chain = tcase_create ("group"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_move_group); + tcase_add_test (tc_chain, test_group_in_group); + + return s; +} + +GST_CHECK_MAIN (ges); diff --git a/tests/check/ges/test-utils.h b/tests/check/ges/test-utils.h index 6210dc4266..e3f3431782 100644 --- a/tests/check/ges/test-utils.h +++ b/tests/check/ges/test-utils.h @@ -72,4 +72,16 @@ G_STMT_START { \ #define _INPOINT(obj) GES_TIMELINE_ELEMENT_INPOINT (obj) #define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj) +#define CHECK_OBJECT_PROPS(obj, start, inpoint, duration) {\ + assert_equals_uint64 (_START (obj), start);\ + assert_equals_uint64 (_INPOINT (obj), inpoint);\ + assert_equals_uint64 (_DURATION (obj), duration);\ +} + +#define check_layer(clip, layer_prio) { \ + GESLayer *tmplayer = ges_clip_get_layer ((clip)); \ + assert_equals_int (ges_layer_get_priority (tmplayer), (layer_prio)); \ + gst_object_unref (tmplayer); \ +} + #endif /* _GES_TEST_UTILS */ diff --git a/tests/check/ges/timelineedition.c b/tests/check/ges/timelineedition.c index 3d42d248a5..f96be2853f 100644 --- a/tests/check/ges/timelineedition.c +++ b/tests/check/ges/timelineedition.c @@ -22,6 +22,33 @@ #include #include +#define DEEP_CHECK(element, start, inpoint, duration) \ +{ \ + GList *track_elements, *tmp; \ + GstClockTime rstart, rinpoint, rduration; \ + \ + rstart = ges_timeline_element_get_start (GES_TIMELINE_ELEMENT (element)); \ + rinpoint = ges_timeline_element_get_inpoint (GES_TIMELINE_ELEMENT (element));\ + rduration = \ + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (element)); \ + \ + assert_equals_uint64 (rstart, start); \ + assert_equals_uint64 (rinpoint, inpoint); \ + assert_equals_uint64 (rduration, duration); \ + \ + track_elements = GES_CONTAINER_CHILDREN (element); \ + for (tmp = track_elements; tmp; tmp = tmp->next) { \ + rstart = ges_timeline_element_get_start (GES_TIMELINE_ELEMENT (tmp->data));\ + rinpoint = \ + ges_timeline_element_get_inpoint (GES_TIMELINE_ELEMENT (tmp->data)); \ + rduration = \ + ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (tmp->data)); \ + assert_equals_uint64 (rstart, start); \ + assert_equals_uint64 (rinpoint, inpoint); \ + assert_equals_uint64 (rduration, duration); \ + } \ +} + static gboolean my_fill_track_func (GESClip * clip, GESTrackElement * track_element, GstElement * gnlobj, gpointer user_data) @@ -46,12 +73,6 @@ create_custom_clip (void) return GES_CLIP (ges_custom_source_clip_new (my_fill_track_func, NULL)); } -#define CHECK_OBJECT_PROPS(obj, start, inpoint, duration) {\ - assert_equals_uint64 (_START (obj), start);\ - assert_equals_uint64 (_INPOINT (obj), inpoint);\ - assert_equals_uint64 (_DURATION (obj), duration);\ -} - GST_START_TEST (test_basic_timeline_edition) { GESAsset *asset; @@ -487,35 +508,6 @@ asset_added_cb (GESProject * project, GESAsset * asset, void *mainloop) g_main_loop_quit ((GMainLoop *) mainloop); } -static void -deep_check (GESTimelineElement * element, GstClockTime start, - GstClockTime inpoint, GstClockTime duration) -{ - GList *track_elements, *tmp; - GstClockTime rstart, rinpoint, rduration; - - rstart = ges_timeline_element_get_start (GES_TIMELINE_ELEMENT (element)); - rinpoint = ges_timeline_element_get_inpoint (GES_TIMELINE_ELEMENT (element)); - rduration = - ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (element)); - - assert_equals_uint64 (rstart, start); - assert_equals_uint64 (rinpoint, inpoint); - assert_equals_uint64 (rduration, duration); - - track_elements = GES_CONTAINER_CHILDREN (element); - for (tmp = track_elements; tmp; tmp = tmp->next) { - rstart = ges_timeline_element_get_start (GES_TIMELINE_ELEMENT (tmp->data)); - rinpoint = - ges_timeline_element_get_inpoint (GES_TIMELINE_ELEMENT (tmp->data)); - rduration = - ges_timeline_element_get_duration (GES_TIMELINE_ELEMENT (tmp->data)); - assert_equals_uint64 (rstart, start); - assert_equals_uint64 (rinpoint, inpoint); - assert_equals_uint64 (rduration, duration); - } -} - GST_START_TEST (test_simple_triming) { GList *assets; @@ -556,10 +548,10 @@ GST_START_TEST (test_simple_triming) element = ges_layer_get_clips (layer)->data; - deep_check (element, 0, 0, 10); + DEEP_CHECK (element, 0, 0, 10); ges_container_edit (GES_CONTAINER (element), NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 5); - deep_check (element, 5, 5, 5); + DEEP_CHECK (element, 5, 5, 5); g_main_loop_unref (mainloop); gst_object_unref (timeline); @@ -1018,6 +1010,274 @@ GST_START_TEST (test_timeline_edition_mode) GST_END_TEST; +GST_START_TEST (test_groups) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1, *c2, *c3, *c4, *c5; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + + /* Our timeline + * + * --0------------10-Group-----20---------------30-----------------------70 + * | +-----------+ |+-----------50 | + * L | | C | || C3 | | + * | +-----------+ |+-----------+ | + * --|-------------------------------------------|-----40----------------| + * | +------------+ +-------------+ | +--------60 | + * L1 | | C1 | | C2 | | | C4 | | + * | +------------+ +-------------+ | +--------+ | + * --|-------------------------------------------|-----------------------| + * | | +--------+| + * L2 | | | c5 || + * | | +--------+| + * --+-------------------------------------------+-----------------------+ + * + * L3 + * + * ----------------------------------------------------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c2 = ges_layer_add_asset (layer1, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + clips = g_list_prepend (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 10); + CHECK_OBJECT_PROPS (group, 0, 0, 30); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); + + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, -1, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE); + + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (c2, 30, 0, 10); + CHECK_OBJECT_PROPS (c3, 40, 0, 20); + CHECK_OBJECT_PROPS (c4, 50, 0, 20); + CHECK_OBJECT_PROPS (c5, 60, 0, 20); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 1, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (c2, 30, 0, 10); + CHECK_OBJECT_PROPS (c3, 40, 0, 20); + CHECK_OBJECT_PROPS (c4, 50, 0, 20); + CHECK_OBJECT_PROPS (c5, 60, 0, 20); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + + fail_unless (ges_container_edit (GES_CONTAINER (c1), NULL, 2, + GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 40) == TRUE); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 20); + CHECK_OBJECT_PROPS (c2, 40, 0, 10); + CHECK_OBJECT_PROPS (c3, 50, 0, 20); + CHECK_OBJECT_PROPS (c4, 60, 0, 20); + CHECK_OBJECT_PROPS (c5, 70, 0, 20); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + + fail_unless (ges_container_edit (GES_CONTAINER (c1), NULL, 2, + GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 30) == TRUE); + CHECK_OBJECT_PROPS (c, 10, 0, 10); + CHECK_OBJECT_PROPS (c1, 20, 0, 10); + CHECK_OBJECT_PROPS (c2, 30, 0, 10); + CHECK_OBJECT_PROPS (c3, 40, 0, 20); + CHECK_OBJECT_PROPS (c4, 50, 0, 20); + CHECK_OBJECT_PROPS (c5, 60, 0, 20); + check_layer (c, 1); + check_layer (c1, 2); + check_layer (c2, 2); + check_layer (c3, 1); + check_layer (c4, 2); + check_layer (c5, 3); + + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0, + GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 0) == TRUE); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 10); + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + fail_unless (ges_container_edit (GES_CONTAINER (c2), NULL, -1, + GES_EDIT_MODE_ROLL, GES_EDGE_END, 40) == TRUE); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 20); + CHECK_OBJECT_PROPS (c3, 40, 10, 10); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group, 0, 0, 40); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + gst_object_unref (timeline); + gst_object_unref (asset); +} + +GST_END_TEST; + +GST_START_TEST (test_snapping_groups) +{ + GESAsset *asset; + GESTimeline *timeline; + GESGroup *group; + GESLayer *layer, *layer1, *layer2, *layer3; + GESClip *c, *c1, *c2, *c3, *c4, *c5; + + GList *clips = NULL; + + ges_init (); + + timeline = ges_timeline_new_audio_video (); + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + + /* Our timeline + * + * --0------------10-Group-----20---------25-----30----------------------70 + * | +-----------+ | +-----------50 | + * L | | C | | | C3 | | + * | +-----------+ | +-----------+ | + * --|------------------------------------|------------40----------------| + * | +------------+ +--------+ +--------60 | + * L1 | | C1 | | C2 | | C4 | | + * | +------------+ +--------+ +--------+ | + * --|------------------------------------+------------------------------| + * | +--------+| + * L2 | | c5 || + * | +--------+| + * --+-------------------------------------------------------------------+ + * + * L3 + * + * ----------------------------------------------------------------------- + */ + + layer = ges_timeline_append_layer (timeline); + layer1 = ges_timeline_append_layer (timeline); + layer2 = ges_timeline_append_layer (timeline); + layer3 = ges_timeline_append_layer (timeline); + assert_equals_int (ges_layer_get_priority (layer3), 3); + asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); + + c = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c1 = ges_layer_add_asset (layer1, asset, 10, 0, 10, GES_TRACK_TYPE_UNKNOWN); + c2 = ges_layer_add_asset (layer1, asset, 20, 0, 5, GES_TRACK_TYPE_UNKNOWN); + clips = g_list_prepend (clips, c); + clips = g_list_prepend (clips, c1); + clips = g_list_prepend (clips, c2); + group = GES_GROUP (ges_container_group (clips)); + fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (group) == timeline); + g_list_free (clips); + + fail_unless (GES_IS_GROUP (group)); + CHECK_OBJECT_PROPS (c, 0, 0, 10); + CHECK_OBJECT_PROPS (c1, 10, 0, 10); + CHECK_OBJECT_PROPS (c2, 20, 0, 5); + CHECK_OBJECT_PROPS (group, 0, 0, 25); + + c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN); + c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN); + + CHECK_OBJECT_PROPS (c3, 30, 0, 20); + CHECK_OBJECT_PROPS (c4, 40, 0, 20); + CHECK_OBJECT_PROPS (c5, 50, 0, 20); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + /* c2 should snap with C3 and make the group moving to 5 */ + fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, -1, + GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 3) == TRUE); + + DEEP_CHECK (c, 5, 0, 10); + DEEP_CHECK (c1, 15, 0, 10); + DEEP_CHECK (c2, 25, 0, 5); + DEEP_CHECK (c2, 25, 0, 5); + DEEP_CHECK (c4, 40, 0, 20); + DEEP_CHECK (c5, 50, 0, 20); + CHECK_OBJECT_PROPS (group, 5, 0, 25); + check_layer (c, 0); + check_layer (c1, 1); + check_layer (c2, 1); + check_layer (c3, 0); + check_layer (c4, 1); + check_layer (c5, 2); + + + gst_object_unref (timeline); + gst_object_unref (asset); +} + +GST_END_TEST; + static Suite * ges_suite (void) { @@ -1030,6 +1290,8 @@ ges_suite (void) tcase_add_test (tc_chain, test_snapping); tcase_add_test (tc_chain, test_timeline_edition_mode); tcase_add_test (tc_chain, test_simple_triming); + tcase_add_test (tc_chain, test_groups); + tcase_add_test (tc_chain, test_snapping_groups); return s; }