diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 326b19a5d7..7f390d3fb8 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -30,6 +30,8 @@ GESTextVAlign DEFAULT_VALIGNMENT GESVideoTestPattern GESPipelineFlags +GESEdge +GESEditMode GES_TYPE_TRACK_TYPE ges_track_type_get_type @@ -104,6 +106,8 @@ ges_track_object_set_child_property_by_pspec ges_track_object_get_child_property ges_track_object_get_child_property_valist ges_track_object_get_child_property_by_pspec +ges_track_object_edit +ges_track_object_copy GES_TRACK_OBJECT_DURATION GES_TRACK_OBJECT_INPOINT @@ -321,6 +325,12 @@ ges_timeline_object_set_top_effect_priority ges_timeline_object_set_supported_formats ges_timeline_object_get_supported_formats ges_timeline_object_split +ges_timeline_object_edit +ges_timeline_object_ripple +ges_timeline_object_ripple_end +ges_timeline_object_roll_start +ges_timeline_object_roll_end +ges_timeline_object_trim_start GES_TIMELINE_OBJECT_DURATION GES_TIMELINE_OBJECT_INPOINT diff --git a/ges/ges-enums.c b/ges/ges-enums.c index 44eb127a92..c3bc10b9c3 100644 --- a/ges/ges-enums.c +++ b/ges/ges-enums.c @@ -18,6 +18,11 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION:ges-enums + * @short_description: Various enums for the Gstreamer Editing Services + */ + #include "ges-enums.h" #define C_ENUM(v) ((guint) v) @@ -74,6 +79,64 @@ ges_pipeline_flags_get_type (void) return id; } +static void +register_ges_edit_mode (GType * id) +{ + static const GEnumValue edit_mode[] = { + {C_ENUM (GES_EDIT_MODE_NORMAL), "GES_EDIT_MODE_NORMAL", + "edit_normal"}, + + {C_ENUM (GES_EDIT_MODE_RIPPLE), "GES_EDIT_MODE_RIPPLE", + "edit_ripple"}, + + {C_ENUM (GES_EDIT_MODE_ROLL), "GES_EDIT_MODE_ROLL", + "edit_roll"}, + + {C_ENUM (GES_EDIT_MODE_TRIM), "GES_EDIT_MODE_TRIM", + "edit_trim"}, + + {C_ENUM (GES_EDIT_MODE_SLIDE), "GES_EDIT_MODE_SLIDE", + "edit_slide"}, + + {0, NULL, NULL} + }; + + *id = g_enum_register_static ("GESEditMode", edit_mode); +} + +GType +ges_edit_mode_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_edit_mode, &id); + return id; +} + +static void +register_ges_edge (GType * id) +{ + static const GEnumValue edges[] = { + {C_ENUM (GES_EDGE_START), "GES_EDGE_START", "edge_start"}, + {C_ENUM (GES_EDGE_END), "GES_EDGE_END", "edge_end"}, + {C_ENUM (GES_EDGE_NONE), "GES_EDGE_NONE", "edge_none"}, + {0, NULL, NULL} + }; + + *id = g_enum_register_static ("GESEdge", edges); +} + +GType +ges_edge_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_edge, &id); + return id; +} + static GEnumValue transition_types[] = { { 0, diff --git a/ges/ges-enums.h b/ges/ges-enums.h index 3799e101cd..bd61bf56c2 100644 --- a/ges/ges-enums.h +++ b/ges/ges-enums.h @@ -323,6 +323,70 @@ typedef enum { GType ges_pipeline_flags_get_type (void); +/** + * GESEditMode: + * @GES_EDIT_MODE_NORMAL: The object is edited the normal way (default). + * @GES_EDIT_MODE_RIPPLE: The objects are edited in ripple mode. + * The Ripple mode allows you to modify the beginning/end of a clip + * and move the neighbours accordingly. This will change the overall + * timeline duration. In the case of ripple end, the duration of the + * clip being rippled can't be supperior to it max-duration - inpoint + * and if it would be the case, nothing wil happen. + * @GES_EDIT_MODE_ROLL: The object is edited in roll mode. + * The Roll mode allows you to modify the position of an editing point + * between two clips without modifying the inpoint of the first clip + * nor the out-point of the second clip. This will not change the + * overall timeline duration. In the case of ripple end, the duration of the + * clip being rolled can't be supperior to it max-duration - inpoint and if + * it would be the case, nothing wil happen. + * @GES_EDIT_MODE_TRIM: The object is edited in trim mode. + * The Trim mode allows you to modify the in-point/out-point of a clip without + * modifying it's duration or position in the timeline. A clip being trim + * @GES_EDIT_MODE_SLIDE: The object is edited in slide mode. + * The Slide mode allows you to modify the position of a clip in a + * timeline without modifying it's duration or it's in-point, but will + * modify the out-point of the previous clip and in-point of the + * following clip so as not to modify the overall timeline duration. + * (not implemented yet) + * + * The various edition modes a clip can be edited with. + * + * + * You can also find more explanation about the behaviour of those modes at: + * trim, ripple and roll + * and clip management. + */ +typedef enum { + GES_EDIT_MODE_NORMAL, + GES_EDIT_MODE_RIPPLE, + GES_EDIT_MODE_ROLL, + GES_EDIT_MODE_TRIM, + GES_EDIT_MODE_SLIDE +} GESEditMode; + +#define GES_TYPE_EDIT_MODE ges_edit_mode_get_type() + +GType ges_edit_mode_get_type (void); + +/** + * GESEdge: + * @GES_EDGE_START: Represents the start of an object. + * @GES_EDGE_END: Represents the end of an object. + * @GES_EDGE_NONE: Represent the fact we are not workin with any edge of an + * object. + * + * The edges of an object contain in a #GESTimeline or #GESTrack + */ +typedef enum { + GES_EDGE_START, + GES_EDGE_END, + GES_EDGE_NONE +} GESEdge; + +#define GES_TYPE_EDGE ges_edge_get_type() + +GType ges_edge_get_type (void); + G_END_DECLS #endif /* __GES_ENUMS_H__ */ diff --git a/ges/ges-internal.h b/ges/ges-internal.h index a104dae0d9..f863c679de 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -22,8 +22,41 @@ #define __GES_INTERNAL_H__ #include +#include "ges-timeline.h" +#include "ges-track-object.h" GST_DEBUG_CATEGORY_EXTERN (_ges_debug); #define GST_CAT_DEFAULT _ges_debug +gboolean +timeline_ripple_object (GESTimeline *timeline, GESTrackObject *obj, + GList * layers, GESEdge edge, + guint64 position); + +gboolean +timeline_slide_object (GESTimeline *timeline, GESTrackObject *obj, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_roll_object (GESTimeline *timeline, GESTrackObject *obj, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_trim_object (GESTimeline *timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position); +gboolean +ges_timeline_trim_object_simple (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position, gboolean snapping); + +gboolean +ges_timeline_move_object_simple (GESTimeline * timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_move_object (GESTimeline *timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_context_to_layer (GESTimeline *timeline, gint offset); + #endif /* __GES_INTERNAL_H__ */ diff --git a/ges/ges-timeline-file-source.c b/ges/ges-timeline-file-source.c index 8df045691f..b17abf48dc 100644 --- a/ges/ges-timeline-file-source.c +++ b/ges/ges-timeline-file-source.c @@ -231,7 +231,8 @@ filesource_set_max_duration (GESTimelineObject * object, guint64 maxduration) tckobjs = ges_timeline_object_get_track_objects (object); for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { - g_object_set (tmp->data, "max-duration", maxduration, NULL); + ges_track_object_set_max_duration (GES_TRACK_OBJECT (tmp->data), + maxduration); } g_list_free_full (tckobjs, g_object_unref); diff --git a/ges/ges-timeline-layer.c b/ges/ges-timeline-layer.c index 16e583d809..4c991fbc2f 100644 --- a/ges/ges-timeline-layer.c +++ b/ges/ges-timeline-layer.c @@ -623,7 +623,6 @@ static void track_object_added_cb (GESTrack * track, GESTrackObject * track_object, GESTimelineLayer * layer) { - GST_ERROR ("TRACKOBJECTADDED %i", GES_IS_TRACK_SOURCE (track_object)); if (GES_IS_TRACK_SOURCE (track_object)) { g_signal_connect (G_OBJECT (track_object), "notify::start", G_CALLBACK (track_object_changed_cb), NULL); diff --git a/ges/ges-timeline-object.c b/ges/ges-timeline-object.c index 6f2582686b..e3639940f3 100644 --- a/ges/ges-timeline-object.c +++ b/ges/ges-timeline-object.c @@ -59,10 +59,6 @@ track_object_priority_changed_cb (GESTrackObject * child, GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object); static void update_height (GESTimelineObject * object); -void -tck_object_added_cb (GESTimelineObject * object, - GESTrackObject * track_object, GList * track_objects); - static gint sort_track_effects (gpointer a, gpointer b, GESTimelineObject * object); static void @@ -398,6 +394,7 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); klass->need_fill_track = TRUE; + klass->snaps = FALSE; } static void @@ -478,6 +475,7 @@ ges_timeline_object_create_track_objects (GESTimelineObject * object, GST_WARNING ("no GESTimelineObject::create_track_objects implentation"); return FALSE; } + return klass->create_track_objects (object, track); } @@ -609,7 +607,7 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject g_signal_connect (G_OBJECT (trobj), "notify::duration", G_CALLBACK (track_object_duration_changed_cb), object); mapping->inpoint_notifyid = - g_signal_connect (G_OBJECT (trobj), "notify::inpoint", + g_signal_connect (G_OBJECT (trobj), "notify::in-point", G_CALLBACK (track_object_inpoint_changed_cb), object); mapping->priority_notifyid = g_signal_connect (G_OBJECT (trobj), "notify::priority", @@ -647,12 +645,13 @@ ges_timeline_object_release_track_object (GESTimelineObject * object, { GList *tmp; ObjectMapping *mapping = NULL; - GESTimelineObjectClass *klass = GES_TIMELINE_OBJECT_GET_CLASS (object); + GESTimelineObjectClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); g_return_val_if_fail (GES_IS_TRACK_OBJECT (trackobject), FALSE); GST_DEBUG ("object:%p, trackobject:%p", object, trackobject); + klass = GES_TIMELINE_OBJECT_GET_CLASS (object); if (!(g_list_find (object->priv->trackobjects, trackobject))) { GST_WARNING ("TrackObject isn't controlled by this object"); @@ -771,12 +770,21 @@ ges_timeline_object_set_start_internal (GESTimelineObject * object, GList *tmp; GESTrackObject *tr; ObjectMapping *map; + GESTimeline *timeline = NULL; + GESTimelineObjectPrivate *priv = object->priv; + gboolean snap = FALSE; g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); GST_DEBUG ("object:%p, start:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (start)); + /* If the class has snapping enabled and the object is in a timeline, + * we snap */ + if (priv->layer && GES_TIMELINE_OBJECT_GET_CLASS (object)->snaps) + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + snap = timeline && priv->initiated_move == NULL ? TRUE : FALSE; + object->priv->ignore_notifies = TRUE; for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) { @@ -793,7 +801,12 @@ ges_timeline_object_set_start_internal (GESTimelineObject * object, continue; } - ges_track_object_set_start (tr, new_start); + /* Make the snapping happen if in a timeline */ + if (snap) + ges_timeline_move_object_simple (timeline, tr, NULL, GES_EDGE_NONE, + start); + else + ges_track_object_set_start (tr, start); } else { /* ... or update the offset */ map->start_offset = start - tr->start; @@ -812,6 +825,9 @@ ges_timeline_object_set_start_internal (GESTimelineObject * object, * @start: the position in #GstClockTime * * Set the position of the object in its containing layer + * + * Note that if the timeline snap-distance property of the timeline containing + * @object is set, @object will properly snap to its neighboors. */ void ges_timeline_object_set_start (GESTimelineObject * object, guint64 start) @@ -873,19 +889,37 @@ ges_timeline_object_set_duration_internal (GESTimelineObject * object, { GList *tmp; GESTrackObject *tr; + GESTimeline *timeline = NULL; + GESTimelineObjectPrivate *priv = object->priv; + gboolean snap = FALSE; g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); GST_DEBUG ("object:%p, duration:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (duration)); + if (priv->layer && GES_TIMELINE_OBJECT_GET_CLASS (object)->snaps) + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + /* If the class has snapping enabled, the object is in a timeline, + * and we are not following a moved TrackObject, we snap */ + snap = timeline && priv->initiated_move == NULL ? TRUE : FALSE; + + object->priv->ignore_notifies = TRUE; for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) { tr = (GESTrackObject *) tmp->data; - if (ges_track_object_is_locked (tr)) - /* call set_duration on each trackobject */ - ges_track_object_set_duration (tr, duration); + if (ges_track_object_is_locked (tr)) { + /* call set_duration on each trackobject + * and make the snapping happen if in a timeline */ + if (G_LIKELY (snap)) + ges_timeline_trim_object_simple (timeline, tr, NULL, GES_EDGE_END, + tr->start + duration, TRUE); + else + ges_track_object_set_duration (tr, duration); + } } + object->priv->ignore_notifies = FALSE; object->duration = duration; return TRUE; @@ -897,6 +931,9 @@ ges_timeline_object_set_duration_internal (GESTimelineObject * object, * @duration: the duration in #GstClockTime * * Set the duration of the object + * + * Note that if the timeline snap-distance property of the timeline containing + * @object is set, @object will properly snap to its neighboors. */ void ges_timeline_object_set_duration (GESTimelineObject * object, guint64 duration) @@ -924,12 +961,10 @@ ges_timeline_object_set_priority_internal (GESTimelineObject * object, GST_DEBUG ("object:%p, priority:%" G_GUINT32_FORMAT, object, priority); priv = object->priv; - priv->ignore_notifies = TRUE; - - object->priv->ignore_notifies = TRUE; get_layer_priorities (priv->layer, &layer_min_gnl_prio, &layer_max_gnl_prio); + priv->ignore_notifies = TRUE; for (tmp = priv->trackobjects; tmp; tmp = g_list_next (tmp)) { tr = (GESTrackObject *) tmp->data; map = find_object_mapping (object, tr); @@ -978,7 +1013,7 @@ void ges_timeline_object_set_moving_from_layer (GESTimelineObject * object, gboolean is_moving) { - g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + g_return_if_fail (GES_IS_TIMELINE_OBJECT (object)); object->priv->is_moving = is_moving; } @@ -998,6 +1033,8 @@ ges_timeline_object_set_moving_from_layer (GESTimelineObject * object, gboolean ges_timeline_object_is_moving_from_layer (GESTimelineObject * object) { + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + return object->priv->is_moving; } @@ -1305,38 +1342,78 @@ ges_timeline_object_set_top_effect_priority (GESTimelineObject * object, return TRUE; } -void -tck_object_added_cb (GESTimelineObject * object, - GESTrackObject * track_object, GList * track_objects) +/** + * ges_timeline_object_edit: + * @object: the #GESTimelineObject to edit + * @layers: (element-type GESTimelineLayer): The layers you want the edit to + * happen in, %NULL means that the edition is done in all the + * #GESTimelineLayers contained in the current timeline. + * @new_layer_priority: The priority of the layer @object should land in. + * If the layer you're trying to move the object to doesn't exist, it will + * be created automatically. -1 means no move. + * @mode: The #GESEditMode in which the editition will happen. + * @edge: The #GESEdge the edit should happen on. + * @position: The position at which to edit @object (in nanosecond) + * + * Edit @object in the different exisiting #GESEditMode modes. In the case of + * slide, and roll, you need to specify a #GESEdge + * + * Returns: %TRUE if the object as been edited properly, %FALSE if an error + * occured + * + * Since: 0.10.XX + */ +gboolean +ges_timeline_object_edit (GESTimelineObject * object, GList * layers, + gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) { - gint64 duration, start, inpoint, position; GList *tmp; - gboolean locked; + gboolean ret = TRUE; + GESTimelineLayer *layer; - ges_track_object_set_locked (track_object, FALSE); - g_object_get (object, "start", &position, NULL); - for (tmp = track_objects; tmp; tmp = tmp->next) { - if (ges_track_object_get_track (track_object)->type == - ges_track_object_get_track (tmp->data)->type) { - locked = ges_track_object_is_locked (tmp->data); - ges_track_object_set_locked (tmp->data, FALSE); - g_object_get (tmp->data, "duration", &duration, "start", &start, - "in-point", &inpoint, NULL); - g_object_set (tmp->data, "duration", - duration - (duration + start - position), NULL); - g_object_set (track_object, "start", position, "in-point", - duration - (duration + start - inpoint - position), "duration", - duration + start - position, NULL); - ges_track_object_set_locked (tmp->data, locked); - ges_track_object_set_locked (track_object, locked); + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + if (!G_UNLIKELY (object->priv->trackobjects)) { + GST_WARNING_OBJECT (object, "Trying to edit, but not containing" + "any TrackObject yet."); + return FALSE; + } else if (position < 0) { + GST_DEBUG_OBJECT (object, "Trying to move before 0, not moving"); + } + + for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret &= ges_track_object_edit (tmp->data, layers, mode, edge, position); + break; } } + + /* Moving to layer */ + if (new_layer_priority == -1) { + GST_DEBUG_OBJECT (object, "Not moving new prio %d", new_layer_priority); + } else { + gint priority_offset; + + layer = object->priv->layer; + if (layer == NULL) { + GST_WARNING_OBJECT (object, "Not in any layer yet, not moving"); + + return FALSE; + } + priority_offset = new_layer_priority - + ges_timeline_layer_get_priority (layer); + + ret &= timeline_context_to_layer (layer->timeline, priority_offset); + } + + return ret; } /** * ges_timeline_object_split: * @object: the #GESTimelineObject to split - * @position: The position at which to split the @object (in nanosecond) + * @position: a #GstClockTime representing the position at which to split + * @object * * The function modifies @object, and creates another #GESTimelineObject so * we have two clips at the end, splitted at the time specified by @position. @@ -1347,40 +1424,103 @@ tck_object_added_cb (GESTimelineObject * object, * Since: 0.10.XX */ GESTimelineObject * -ges_timeline_object_split (GESTimelineObject * object, gint64 position) +ges_timeline_object_split (GESTimelineObject * object, guint64 position) { - GList *track_objects, *tmp; - GESTimelineLayer *layer; + GList *tmp; + gboolean locked; GESTimelineObject *new_object; - gint64 duration, start, inpoint; + GESTimelineObjectPrivate *priv; + + GstClockTime start, inpoint, duration; g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), NULL); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL); - g_object_get (object, "duration", &duration, "start", &start, "in-point", - &inpoint, NULL); + priv = object->priv; - track_objects = ges_timeline_object_get_track_objects (object); - layer = ges_timeline_object_get_layer (object); + duration = GES_TIMELINE_OBJECT_DURATION (object); + start = GES_TIMELINE_OBJECT_START (object); + inpoint = GES_TIMELINE_OBJECT_INPOINT (object); - new_object = ges_timeline_object_copy (object, FALSE); - - if (g_list_length (track_objects) == 2) { - g_object_set (new_object, "start", position, NULL); - g_signal_connect (G_OBJECT (new_object), "track-object-added", - G_CALLBACK (tck_object_added_cb), track_objects); - } else { - for (tmp = track_objects; tmp; tmp = tmp->next) { - g_object_set (tmp->data, "duration", - duration - (duration + start - position), NULL); - g_object_set (new_object, "start", position, "in-point", - duration - (duration + start - position), "duration", - (duration + start - position), NULL); - g_object_set (object, "duration", - duration - (duration + start - position), NULL); - } + if (position >= start + duration || position <= start) { + GST_WARNING_OBJECT (object, "Can not split %" GST_TIME_FORMAT + " out of boundaries", GST_TIME_ARGS (position)); + return NULL; } - ges_timeline_layer_add_object (layer, new_object); + GST_DEBUG_OBJECT (object, "Spliting at %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + /* Create the new TimelineObject */ + new_object = ges_timeline_object_copy (object, FALSE); + + /* Set new timing properties on the TimelineObject */ + ges_timeline_object_set_start (new_object, position); + ges_timeline_object_set_inpoint (new_object, object->inpoint + + duration - (duration + start - position)); + ges_timeline_object_set_duration (new_object, duration + start - position); + + if (object->priv->layer) { + /* We do not want the timeline to create again TrackObject-s */ + ges_timeline_object_set_moving_from_layer (new_object, TRUE); + ges_timeline_layer_add_object (object->priv->layer, new_object); + ges_timeline_object_set_moving_from_layer (new_object, FALSE); + } + + /* We first set the new duration and the child mapping will be updated + * properly in the following loop */ + object->duration = position - object->start; + for (tmp = priv->trackobjects; tmp; tmp = tmp->next) { + GESTrack *track; + + GESTrackObject *new_tckobj, *tckobj = GES_TRACK_OBJECT (tmp->data); + + duration = ges_track_object_get_duration (tckobj); + start = ges_track_object_get_start (tckobj); + inpoint = ges_track_object_get_inpoint (tckobj); + + if (position <= start || position >= (start + duration)) { + GST_DEBUG_OBJECT (tckobj, "Outside %" GST_TIME_FORMAT "the boundaries " + "not copying it ( start %" GST_TIME_FORMAT ", end %" GST_TIME_FORMAT + ")", GST_TIME_ARGS (position), GST_TIME_ARGS (tckobj->start), + GST_TIME_ARGS (tckobj->start + tckobj->duration)); + continue; + } + + new_tckobj = ges_track_object_copy (tckobj, TRUE); + if (new_tckobj == NULL) { + GST_WARNING_OBJECT (tckobj, "Could not create a copy"); + continue; + } + + ges_timeline_object_add_track_object (new_object, new_tckobj); + + track = ges_track_object_get_track (tckobj); + if (track == NULL) + GST_DEBUG_OBJECT (tckobj, "Was not in a track, not adding %p to" + "any track", new_tckobj); + else + ges_track_add_object (track, new_tckobj); + + /* Unlock TrackObject-s as we do not want the container to move + * syncronously */ + locked = ges_track_object_is_locked (tckobj); + ges_track_object_set_locked (new_tckobj, FALSE); + ges_track_object_set_locked (tckobj, FALSE); + + /* Set 'new' track object timing propeties */ + ges_track_object_set_start (new_tckobj, position); + ges_track_object_set_inpoint (new_tckobj, inpoint + duration - (duration + + start - position)); + ges_track_object_set_duration (new_tckobj, duration + start - position); + + /* Set 'old' track object duration */ + ges_track_object_set_duration (tckobj, position - start); + + /* And let track objects in the same locking state as before. */ + ges_track_object_set_locked (tckobj, locked); + ges_track_object_set_locked (new_tckobj, locked); + } return new_object; } @@ -1414,18 +1554,6 @@ ges_timeline_object_copy (GESTimelineObject * object, gboolean * deep) ret = g_object_newv (G_TYPE_FROM_INSTANCE (object), n_params, params); - if (GES_IS_TIMELINE_FILE_SOURCE (ret)) { - GList *tck_objects; - tck_objects = ges_timeline_object_get_track_objects (object); - if (g_list_length (tck_objects) == 1) { - GESTrackType type; - type = ges_track_object_get_track (tck_objects->data)->type; - ges_timeline_filesource_set_supported_formats (GES_TIMELINE_FILE_SOURCE - (ret), type); - } - g_list_free (tck_objects); - } - g_free (specs); g_free (params); @@ -1531,6 +1659,264 @@ ges_timeline_object_set_max_duration (GESTimelineObject * object, klass->set_max_duration (object, maxduration); } +/** + * ges_timeline_object_ripple: + * @object: The #GESTimeline to ripple. + * @start: The new start of @object in ripple mode. + * + * Edits @object in ripple mode. It allows you to modify the + * start of @object and move the following neighbours accordingly. + * This will change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, + * @position); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been rippled properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_ripple (GESTimelineObject * object, guint64 start) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_ripple_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_NONE, start); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_ripple_end: + * @object: The #GESTimeline to ripple. + * @end: The new end (start + duration) of @object in ripple mode. It will + * basically only change the duration of @object. + * + * Edits @object in ripple mode. It allows you to modify the + * duration of a @object and move the following neighbours accordingly. + * This will change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_RIPPLE, GES_EDGE_END, @end); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been rippled properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_ripple_end (GESTimelineObject * object, guint64 end) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_ripple_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_END, end); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_roll_start: + * @start: The new start of @object in roll mode, it will also adapat + * the in-point of @object according + * + * Edits @object in roll mode. It allows you to modify the + * start and inpoint of a @object and "resize" (basicly change the duration + * in this case) of the previous neighbours accordingly. + * This will not change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_ROLL, GES_EDGE_START, @start); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been roll properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_roll_start (GESTimelineObject * object, guint64 start) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_roll_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_START, start); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_roll_end: + * @object: The #GESTimeline to roll. + * @end: The new end (start + duration) of @object in roll mode + * + * Edits @object in roll mode. It allows you to modify the + * duration of a @object and trim (basicly change the start + inpoint + * in this case) the following neighbours accordingly. + * This will not change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_ROLL, GES_EDGE_END, @end); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been rolled properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_roll_end (GESTimelineObject * object, guint64 end) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_roll_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_END, end); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_trim_start: + * @object: The #GESTimeline to trim. + * @start: The new start of @object in trim mode, will adapt the inpoint + * of @object accordingly + * + * Edits @object in trim mode. It allows you to modify the + * inpoint and start of @object. + * This will not change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_TRIM, GES_EDGE_START, @start); + * + * Which lets you more control over layer management. + * + * Note that to trim the end of an object you can just set its duration. The same way + * as this method, it will take into account the snapping-distance property of the + * timeline in which @object is. + * + * Returns: %TRUE if the object as been trimmed properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_trim_start (GESTimelineObject * object, guint64 start) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_trim_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_START, start); + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + static void update_height (GESTimelineObject * object) { diff --git a/ges/ges-timeline-object.h b/ges/ges-timeline-object.h index c116e39a9a..5c752cd311 100644 --- a/ges/ges-timeline-object.h +++ b/ges/ges-timeline-object.h @@ -182,6 +182,9 @@ struct _GESTimelineObject { * #GESTrack. * @fill_track_object: method to fill an associated #GESTrackObject. * @need_fill_track: Set to TRUE if @fill_track_object needs to be called. + * @snaps: Set to %TRUE if the objects of this type snap with + * other objects in a timeline %FALSE otherwise (default is %FALSE). Basically only + * sources snap. * @track_object_added: Should be overridden by subclasses if they need to perform an * operation when a #GESTrackObject is added. Since: 0.10.2 * @track_object_released: Should be overridden by subclasses if they need to perform @@ -202,6 +205,7 @@ struct _GESTimelineObjectClass { /* FIXME : might need a release_track_object */ GESFillTrackObjectFunc fill_track_object; gboolean need_fill_track; + gboolean snaps; void (*track_object_added) (GESTimelineObject *object, GESTrackObject *tck_object); @@ -212,7 +216,7 @@ struct _GESTimelineObjectClass { /*< private >*/ /* Padding for API extension */ - gpointer _ges_reserved[GES_PADDING - 3]; + gpointer _ges_reserved[GES_PADDING - 4]; }; GType ges_timeline_object_get_type (void); @@ -298,10 +302,31 @@ ges_timeline_object_set_supported_formats (GESTimelineObject * object, GESTrackType supportedformats); GESTimelineObject * -ges_timeline_object_split (GESTimelineObject * object, gint64 position); +ges_timeline_object_split (GESTimelineObject * object, guint64 position); + +gboolean +ges_timeline_object_edit (GESTimelineObject * object, + GList *layers, gint new_layer_priority, + GESEditMode mode, GESEdge edge, + guint64 position); void ges_timeline_object_objects_set_locked (GESTimelineObject * object, gboolean locked); + +gboolean ges_timeline_object_ripple (GESTimelineObject *object, + guint64 start); + +gboolean ges_timeline_object_ripple_end (GESTimelineObject *object, + guint64 end); + +gboolean ges_timeline_object_roll_start (GESTimelineObject *object, + guint64 start); + +gboolean ges_timeline_object_roll_end (GESTimelineObject *object, + guint64 end); + +gboolean ges_timeline_object_trim_start (GESTimelineObject *object, + guint64 start); G_END_DECLS #endif /* _GES_TIMELINE_OBJECT */ diff --git a/ges/ges-timeline-pipeline.c b/ges/ges-timeline-pipeline.c index ae58d18d2e..7be4d4ec37 100644 --- a/ges/ges-timeline-pipeline.c +++ b/ges/ges-timeline-pipeline.c @@ -72,6 +72,8 @@ static OutputChain *get_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track); static OutputChain *new_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track); +static gboolean play_sink_multiple_seeks_send_event (GstElement * element, + GstEvent * event); static void ges_timeline_pipeline_dispose (GObject * object) @@ -122,6 +124,8 @@ ges_timeline_pipeline_class_init (GESTimelinePipelineClass * klass) static void ges_timeline_pipeline_init (GESTimelinePipeline * self) { + GstElementClass *playsinkclass; + GST_INFO_OBJECT (self, "Creating new 'playsink'"); self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GES_TYPE_TIMELINE_PIPELINE, GESTimelinePipelinePrivate); @@ -141,6 +145,12 @@ ges_timeline_pipeline_init (GESTimelinePipeline * self) if (G_UNLIKELY (self->priv->encodebin == NULL)) goto no_encodebin; + /* TODO : Remove this hack once we depend on gst-p-base 0.10.37 */ + /* HACK : Intercept events going through playsink */ + playsinkclass = GST_ELEMENT_GET_CLASS (self->priv->playsink); + /* Replace playsink's GstBin::send_event with our own */ + playsinkclass->send_event = play_sink_multiple_seeks_send_event; + ges_timeline_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE); return; @@ -997,3 +1007,16 @@ ges_timeline_pipeline_preview_set_audio_sink (GESTimelinePipeline * self, { g_object_set (self->priv->playsink, "audio-sink", sink, NULL); }; + + +static gboolean +play_sink_multiple_seeks_send_event (GstElement * element, GstEvent * event) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); + + GST_DEBUG ("%s", GST_EVENT_TYPE_NAME (event)); + + return + GST_ELEMENT_CLASS (g_type_class_peek_parent (klass))->send_event (element, + event); +} diff --git a/ges/ges-timeline-source.c b/ges/ges-timeline-source.c index c0d2c2b961..50c4e7ec91 100644 --- a/ges/ges-timeline-source.c +++ b/ges/ges-timeline-source.c @@ -79,6 +79,9 @@ ges_timeline_source_class_init (GESTimelineSourceClass * klass) object_class->get_property = ges_timeline_source_get_property; object_class->set_property = ges_timeline_source_set_property; object_class->finalize = ges_timeline_source_finalize; + + /* All subclasses should have snapping enabled */ + GES_TIMELINE_OBJECT_CLASS (klass)->snaps = TRUE; } static void diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 3e2729d775..008d9c8c1a 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -42,8 +42,9 @@ #include "ges-timeline-layer.h" #include "ges.h" -static void track_duration_cb (GstElement * track, - GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline); +typedef struct _MoveContext MoveContext; + +static inline void init_movecontext (MoveContext * mv_ctx); G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN); @@ -54,6 +55,44 @@ G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN); #define GES_TIMELINE_PENDINGOBJS_UNLOCK(timeline) \ (g_mutex_unlock(GES_TIMELINE_PENDINGOBJS_GET_LOCK (timeline))) +/** + * The move context is used for the timeline editing modes functions in order to + * + Ripple / Roll / Slide / Move / Trim + * + * The context aims at avoiding to recalculate values/objects on each call of the + * editing functions. + */ +struct _MoveContext +{ + GESTimelineObject *obj; + GESEdge edge; + GESEditMode mode; + + /* Ripple and Roll Objects */ + GList *moving_tckobjs; + + /* We use it as a set of TimelineObject to move between layers */ + GHashTable *moving_tlobjs; + /* Min priority of the objects currently in moving_tlobjs */ + guint min_move_layer; + /* Max priority of the objects currently in moving_tlobjs */ + guint max_layer_prio; + + /* Never trim so duration would becomes < 0 */ + guint64 max_trim_pos; + + /* fields to force/avoid new context */ + /* Set to %TRUE when the track is doing updates of track objects + * properties so we don't end up always needing new move context */ + gboolean ignore_needs_ctx; + gboolean needs_move_ctx; + + /* Last snapping properties */ + GESTrackObject *last_snaped1; + GESTrackObject *last_snaped2; + GstClockTime last_snap_ts; +}; + struct _GESTimelinePrivate { GList *layers; /* A list of GESTimelineLayer sorted by priority */ @@ -70,6 +109,23 @@ struct _GESTimelinePrivate /* Whether we are changing state asynchronously or not */ gboolean async_pending; + + /* Timeline edition modes and snapping management */ + guint64 snapping_distance; + + /* FIXME: Should we offer an API over those fields ? + * FIXME: Should other classes than subclasses of TrackSource also + * be tracked? */ + + /* Snapping fields */ + GHashTable *by_start; /* {TrackSource: start} */ + GHashTable *by_end; /* {TrackSource: end} */ + GHashTable *by_object; /* {timecode: TrackSource} */ + GSequence *starts_ends; /* Sorted list of starts/ends */ + /* We keep 1 reference to our trackobject here */ + GSequence *tracksources; /* TrackSource-s sorted by start/priorities */ + + MoveContext movecontext; }; /* private structure to contain our track-related information */ @@ -86,6 +142,7 @@ enum { PROP_0, PROP_DURATION, + PROP_SNAPPING_DISTANCE, PROP_LAST }; @@ -98,6 +155,8 @@ enum LAYER_ADDED, LAYER_REMOVED, DISCOVERY_ERROR, + SNAPING_STARTED, + SNAPING_ENDED, LAST_SIGNAL }; @@ -127,6 +186,9 @@ ges_timeline_get_property (GObject * object, guint property_id, case PROP_DURATION: g_value_set_uint64 (value, timeline->priv->duration); break; + case PROP_SNAPPING_DISTANCE: + g_value_set_uint64 (value, timeline->priv->snapping_distance); + break; } } @@ -134,7 +196,12 @@ static void ges_timeline_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { + GESTimeline *timeline = GES_TIMELINE (object); + switch (property_id) { + case PROP_SNAPPING_DISTANCE: + timeline->priv->snapping_distance = g_value_get_uint64 (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -166,6 +233,12 @@ ges_timeline_dispose (GObject * object) ges_timeline_remove_track (GES_TIMELINE (object), tr_priv->track); } + g_hash_table_unref (priv->by_start); + g_hash_table_unref (priv->by_end); + g_hash_table_unref (priv->by_object); + g_sequence_free (priv->starts_ends); + g_sequence_free (priv->tracksources); + G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object); } @@ -197,11 +270,9 @@ ges_timeline_class_init (GESTimelineClass * klass) object_class->finalize = ges_timeline_finalize; /** - * GESTimelineObject:duration + * GESTimeline:duration * * Current duration (in nanoseconds) of the #GESTimeline - * - * Default value: 0 */ properties[PROP_DURATION] = g_param_spec_uint64 ("duration", "Duration", @@ -210,6 +281,19 @@ ges_timeline_class_init (GESTimelineClass * klass) g_object_class_install_property (object_class, PROP_DURATION, properties[PROP_DURATION]); + /** + * GESTimeline:snapping-distance + * + * Distance (in nanoseconds) from which a moving object will snap + * with it neighboors. 0 means no snapping. + */ + properties[PROP_SNAPPING_DISTANCE] = + g_param_spec_uint64 ("snapping-distance", "Snapping distance", + "Distance from which moving an object will snap with neighboors", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE, + properties[PROP_SNAPPING_DISTANCE]); + /** * GESTimeline::track-added * @timeline: the #GESTimeline @@ -262,6 +346,7 @@ ges_timeline_class_init (GESTimelineClass * klass) /** * GESTimeline::discovery-error: + * @timeline: the #GESTimeline * @formatter: the #GESFormatter * @source: The #GESTimelineFileSource that could not be discovered properly * @error: (type GLib.Error): #GError, which will be non-NULL if an error @@ -271,27 +356,74 @@ ges_timeline_class_init (GESTimelineClass * klass) g_signal_new ("discovery-error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, GES_TYPE_TIMELINE_FILE_SOURCE, G_TYPE_ERROR); + + /** + * GESTimeline::track-objects-snapping: + * @timeline: the #GESTimeline + * @obj1: the first #GESTrackObject that was snapping. + * @obj2: the second #GESTrackObject that was snapping. + * @position: the position where the two objects finally snapping. + * + * Will be emitted when the 2 #GESTrackObject first snapped + * + * Since: 0.10.XX + */ + ges_timeline_signals[SNAPING_STARTED] = + g_signal_new ("snapping-started", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 3, GES_TYPE_TRACK_OBJECT, GES_TYPE_TRACK_OBJECT, + G_TYPE_UINT64); + + /** + * GESTimeline::snapping-end: + * @timeline: the #GESTimeline + * @obj1: the first #GESTrackObject that was snapping. + * @obj2: the second #GESTrackObject that was snapping. + * @position: the position where the two objects finally snapping. + * + * Will be emitted when the 2 #GESTrackObject ended to snap + * + * Since: 0.10.XX + */ + ges_timeline_signals[SNAPING_ENDED] = + g_signal_new ("snapping-ended", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 3, GES_TYPE_TRACK_OBJECT, GES_TYPE_TRACK_OBJECT, + G_TYPE_UINT64); } static void ges_timeline_init (GESTimeline * self) { + GESTimelinePrivate *priv; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GES_TYPE_TIMELINE, GESTimelinePrivate); - self->priv->layers = NULL; - self->priv->tracks = NULL; - self->priv->duration = 0; + priv = self->priv; + priv->layers = NULL; + priv->tracks = NULL; + priv->duration = 0; + priv->snapping_distance = 0; - g_mutex_init (&self->priv->pendingobjects_lock); + /* Move context initialization */ + init_movecontext (&self->priv->movecontext); + priv->movecontext.ignore_needs_ctx = FALSE; + + priv->by_start = g_hash_table_new (g_direct_hash, g_direct_equal); + priv->by_end = g_hash_table_new (g_direct_hash, g_direct_equal); + priv->by_object = g_hash_table_new (g_direct_hash, g_direct_equal); + priv->starts_ends = g_sequence_new (g_free); + priv->tracksources = g_sequence_new (g_object_unref); + + g_mutex_init (&priv->pendingobjects_lock); /* New discoverer with a 15s timeout */ - self->priv->discoverer = gst_discoverer_new (15 * GST_SECOND, NULL); - g_signal_connect (self->priv->discoverer, "finished", + priv->discoverer = gst_discoverer_new (15 * GST_SECOND, NULL); + g_signal_connect (priv->discoverer, "finished", G_CALLBACK (discoverer_finished_cb), self); - g_signal_connect (self->priv->discoverer, "discovered", + g_signal_connect (priv->discoverer, "discovered", G_CALLBACK (discoverer_discovered_cb), self); - gst_discoverer_start (self->priv->discoverer); + gst_discoverer_start (priv->discoverer); } /* Private methods */ @@ -317,6 +449,41 @@ sort_layers (gpointer a, gpointer b) return 0; } +static gint +objects_start_compare (GESTrackObject * a, GESTrackObject * b) +{ + if (a->start == b->start) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; + } + if (a->start < b->start) + return -1; + if (a->start > b->start) + return 1; + return 0; +} + +static inline void +sort_track_objects (GESTimeline * timeline) +{ + g_sequence_sort (timeline->priv->tracksources, + (GCompareDataFunc) objects_start_compare, NULL); +} + +static gint +compare_uint64 (guint64 * a, guint64 * b, gpointer user_data) +{ + if (*a > *b) + return 1; + else if (*a == *b) + return 0; + else + return -1; +} + static gint custom_find_track (TrackPrivate * tr_priv, GESTrack * track) { @@ -325,6 +492,871 @@ custom_find_track (TrackPrivate * tr_priv, GESTrack * track) return -1; } +/* Look for the pointer passed as @value */ +static GSequenceIter * +lookup_pointer_uint (GSequence * seq, guint64 * value) +{ + GSequenceIter *iter, *tmpiter; + guint64 *found, *tmpval; + + iter = g_sequence_lookup (seq, value, + (GCompareDataFunc) compare_uint64, NULL); + + found = g_sequence_get (iter); + + /* We have the same pointer so we are all fine */ + if (found == value) + return iter; + + if (!g_sequence_iter_is_end (iter)) { + /* Looking frontward for the good pointer */ + tmpiter = iter; + while ((tmpiter = g_sequence_iter_next (tmpiter))) { + if (g_sequence_iter_is_end (tmpiter)) + break; + + tmpval = g_sequence_get (tmpiter); + if (tmpval == value) + return tmpiter; + else if (*tmpval != *value) + break; + } + } + + if (!g_sequence_iter_is_begin (iter)) { + /* Looking backward for the good pointer */ + tmpiter = iter; + while ((tmpiter = g_sequence_iter_prev (tmpiter))) { + tmpval = g_sequence_get (tmpiter); + if (tmpval == value) + return tmpiter; + else if (*tmpval != *value || g_sequence_iter_is_begin (tmpiter)) + break; + } + } + + GST_ERROR ("Missing timecode %p %" GST_TIME_FORMAT + " this should never happen", value, GST_TIME_ARGS (*value)); + + return NULL; +} + +static inline void +sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj) +{ + GSequenceIter *iter; + + GESTimelinePrivate *priv = timeline->priv; + guint64 *end = g_hash_table_lookup (priv->by_end, obj); + + iter = lookup_pointer_uint (priv->starts_ends, end); + *end = obj->start + obj->duration; + + g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); +} + +static inline void +sort_starts_ends_start (GESTimeline * timeline, GESTrackObject * obj) +{ + GSequenceIter *iter; + + GESTimelinePrivate *priv = timeline->priv; + guint64 *start = g_hash_table_lookup (priv->by_start, obj); + + iter = lookup_pointer_uint (priv->starts_ends, start); + *start = obj->start; + + g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); +} + +static inline void +resort_all_starts_ends (GESTimeline * timeline) +{ + GSequenceIter *iter; + GESTrackObject *tckobj; + guint64 *start, *end; + + GESTimelinePrivate *priv = timeline->priv; + + for (iter = g_sequence_get_begin_iter (priv->tracksources); + !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { + tckobj = GES_TRACK_OBJECT (g_sequence_get (iter)); + + start = g_hash_table_lookup (priv->by_start, tckobj); + end = g_hash_table_lookup (priv->by_end, tckobj); + + *start = tckobj->start; + *end = tckobj->start + tckobj->duration; + } + + g_sequence_sort (priv->starts_ends, (GCompareDataFunc) compare_uint64, NULL); +} + +static inline void +sort_all (GESTimeline * timeline) +{ + sort_track_objects (timeline); + resort_all_starts_ends (timeline); +} + +/* Timeline edition functions */ +static inline void +init_movecontext (MoveContext * mv_ctx) +{ + mv_ctx->moving_tckobjs = NULL; + mv_ctx->moving_tlobjs = g_hash_table_new (g_direct_hash, g_direct_equal); + mv_ctx->max_trim_pos = G_MAXUINT64; + mv_ctx->min_move_layer = G_MAXUINT; + mv_ctx->max_layer_prio = 0; + mv_ctx->last_snaped1 = NULL; + mv_ctx->last_snaped2 = NULL; + mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE; +} + +static inline void +clean_movecontext (MoveContext * mv_ctx) +{ + g_list_free (mv_ctx->moving_tckobjs); + g_hash_table_unref (mv_ctx->moving_tlobjs); + init_movecontext (mv_ctx); +} + +static void +stop_tracking_for_snapping (GESTimeline * timeline, GESTrackObject * tckobj) +{ + guint64 *start, *end; + GESTimelinePrivate *priv = timeline->priv; + GSequenceIter *iter_start, *iter_end, *tckobj_iter; + + + start = g_hash_table_lookup (priv->by_start, tckobj); + end = g_hash_table_lookup (priv->by_end, tckobj); + + iter_start = lookup_pointer_uint (priv->starts_ends, start); + iter_end = lookup_pointer_uint (priv->starts_ends, end); + tckobj_iter = g_sequence_lookup (timeline->priv->tracksources, tckobj, + (GCompareDataFunc) objects_start_compare, NULL); + + g_hash_table_remove (priv->by_start, tckobj); + g_hash_table_remove (priv->by_end, tckobj); + g_hash_table_remove (priv->by_object, end); + g_hash_table_remove (priv->by_object, start); + + g_sequence_remove (iter_start); + g_sequence_remove (iter_end); + g_sequence_remove (tckobj_iter); + +} + +static void +start_tracking_track_obj (GESTimeline * timeline, GESTrackObject * tckobj) +{ + guint64 *pstart, *pend; + GESTimelinePrivate *priv = timeline->priv; + + pstart = g_malloc (sizeof (guint64)); + pend = g_malloc (sizeof (guint64)); + *pstart = tckobj->start; + *pend = *pstart + tckobj->duration; + + g_sequence_insert_sorted (priv->starts_ends, pstart, + (GCompareDataFunc) compare_uint64, NULL); + g_sequence_insert_sorted (priv->starts_ends, pend, + (GCompareDataFunc) compare_uint64, NULL); + g_sequence_insert_sorted (priv->tracksources, g_object_ref (tckobj), + (GCompareDataFunc) objects_start_compare, NULL); + + g_hash_table_insert (priv->by_start, tckobj, pstart); + g_hash_table_insert (priv->by_object, pstart, tckobj); + g_hash_table_insert (priv->by_end, tckobj, pend); + g_hash_table_insert (priv->by_object, pend, tckobj); + + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static inline void +ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1, + guint64 * timecode) +{ + GESTrackObject *obj2; + MoveContext *mv_ctx = &timeline->priv->movecontext; + + if (timecode == NULL) { + if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) { + g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, + mv_ctx->last_snaped1, mv_ctx->last_snaped2, mv_ctx->last_snap_ts); + + /* We then need to recalculate the moving context */ + timeline->priv->movecontext.needs_move_ctx = TRUE; + } + + return; + } + + obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode); + + if (mv_ctx->last_snap_ts != *timecode) { + g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, + mv_ctx->last_snaped1, mv_ctx->last_snaped2, mv_ctx->last_snap_ts); + + /* We want the snap start signal to be emited anyway */ + mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE; + } + + if (GST_CLOCK_TIME_IS_VALID (mv_ctx->last_snap_ts) == FALSE) { + + mv_ctx->last_snaped1 = obj1; + mv_ctx->last_snaped2 = obj2; + mv_ctx->last_snap_ts = *timecode; + + g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0, + obj1, obj2, *timecode); + + } +} + +static guint64 * +ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj, + guint64 * current, guint64 timecode, gboolean emit) +{ + GESTimelinePrivate *priv = timeline->priv; + GSequenceIter *iter, *prev_iter, *nxt_iter; + GESTrackObject *tmp_tckobj; + GESTimelineObject *tmp_tlobj, *tlobj; + + guint64 snap_distance = timeline->priv->snapping_distance; + + guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 = + G_MAXUINT64; + + /* Avoid useless calculations */ + if (snap_distance == 0) + return NULL; + + tlobj = ges_track_object_get_timeline_object (trackobj); + + iter = g_sequence_search (priv->starts_ends, &timecode, + (GCompareDataFunc) compare_uint64, NULL); + + /* Getting the next/previous values, and use the closest one if any "respects" + * the snap_distance value */ + nxt_iter = iter; + while (!g_sequence_iter_is_end (nxt_iter)) { + next_tc = g_sequence_get (iter); + tmp_tckobj = g_hash_table_lookup (timeline->priv->by_object, next_tc); + tmp_tlobj = ges_track_object_get_timeline_object (tmp_tckobj); + + off = timecode > *next_tc ? timecode - *next_tc : *next_tc - timecode; + if (next_tc != current && off <= snap_distance && tlobj != tmp_tlobj) { + + ret = next_tc; + break; + } + + nxt_iter = g_sequence_iter_next (nxt_iter); + } + + prev_iter = g_sequence_iter_prev (iter); + while (!g_sequence_iter_is_begin (prev_iter)) { + prev_tc = g_sequence_get (prev_iter); + tmp_tckobj = g_hash_table_lookup (timeline->priv->by_object, prev_tc); + tmp_tlobj = ges_track_object_get_timeline_object (tmp_tckobj); + + off1 = timecode > *prev_tc ? timecode - *prev_tc : *prev_tc - timecode; + if (prev_tc != current && off1 < off && off1 <= snap_distance && + tlobj != tmp_tlobj) { + ret = prev_tc; + + break; + } + + prev_iter = g_sequence_iter_prev (prev_iter); + } + + /* We emit the snapping signal only if we snapped with a different value + * than the current one */ + if (emit) { + ges_timeline_emit_snappig (timeline, trackobj, ret); + GST_DEBUG_OBJECT (timeline, "Snaping at %" GST_TIME_FORMAT, + GST_TIME_ARGS (*ret)); + } + + return ret; +} + +static inline GESTimelineObject * +add_moving_timeline_object (MoveContext * mv_ctx, GESTrackObject * tckobj) +{ + GESTimelineObject *tlobj; + GESTimelineLayer *layer; + guint layer_prio; + + tlobj = ges_track_object_get_timeline_object (tckobj); + + /* Avoid recalculating */ + if (!g_hash_table_lookup (mv_ctx->moving_tlobjs, tlobj)) { + layer = ges_timeline_object_get_layer (tlobj); + if (layer == NULL) { + GST_WARNING_OBJECT (tlobj, "Not in any layer, can not move" + " between layers"); + + } else { + + g_hash_table_insert (mv_ctx->moving_tlobjs, tlobj, tlobj); + + layer_prio = ges_timeline_layer_get_priority (layer); + 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); + + g_object_unref (layer); + } + } + + return tlobj; +} + +static gboolean +ges_move_context_set_objects (GESTimeline * timeline, GESTrackObject * obj, + GESEdge edge) +{ + GSequenceIter *iter, *tckobj_iter; + guint64 start, end, tmpend; + GESTrackObject *tmptckobj; + + MoveContext *mv_ctx = &timeline->priv->movecontext; + + tckobj_iter = g_sequence_lookup (timeline->priv->tracksources, obj, + (GCompareDataFunc) objects_start_compare, NULL); + + switch (edge) { + case GES_EDGE_START: + /* set it properly int the context of "trimming" */ + mv_ctx->max_trim_pos = 0; + start = obj->start; + + if (g_sequence_iter_is_begin (tckobj_iter)) + break; + + /* Look for the objects */ + for (iter = g_sequence_iter_prev (tckobj_iter); + iter && !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_prev (iter)) { + + tmptckobj = GES_TRACK_OBJECT (g_sequence_get (iter)); + tmpend = tmptckobj->start + tmptckobj->duration; + + if (tmpend <= start) { + mv_ctx->max_trim_pos = MAX (mv_ctx->max_trim_pos, tmptckobj->start); + mv_ctx->moving_tckobjs = + g_list_prepend (mv_ctx->moving_tckobjs, tmptckobj); + } + + if (g_sequence_iter_is_begin (iter)) + break; + } + break; + + case GES_EDGE_END: + case GES_EDGE_NONE: /* In this case only works for ripple */ + end = ges_track_object_get_start (obj) + + ges_track_object_get_duration (obj); + + mv_ctx->max_trim_pos = G_MAXUINT64; + + /* Look for folowing objects */ + for (iter = g_sequence_iter_next (tckobj_iter); + iter && !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) { + tmptckobj = GES_TRACK_OBJECT (g_sequence_get (iter)); + + if (tmptckobj->start >= end) { + tmpend = tmptckobj->start + tmptckobj->duration; + mv_ctx->max_trim_pos = MIN (mv_ctx->max_trim_pos, tmpend); + mv_ctx->moving_tckobjs = + g_list_prepend (mv_ctx->moving_tckobjs, tmptckobj); + } + } + break; + default: + GST_DEBUG ("Edge type %d no supported", edge); + return FALSE; + } + + return TRUE; +} + +static gboolean +ges_timeline_set_moving_context (GESTimeline * timeline, GESTrackObject * obj, + GESEditMode mode, GESEdge edge, GList * layers) +{ + MoveContext *mv_ctx = &timeline->priv->movecontext; + GESTimelineObject *tlobj = ges_track_object_get_timeline_object (obj); + + /* Still in the same mv_ctx */ + if ((mv_ctx->obj == tlobj && mv_ctx->mode == mode && + mv_ctx->edge == edge && !mv_ctx->needs_move_ctx)) { + + GST_DEBUG ("Keeping the same moving mv_ctx"); + return TRUE; + } + + GST_DEBUG_OBJECT (tlobj, + "Changing context:\nold: obj: %p, mode: %d, edge: %d \n" + "new: obj: %p, mode: %d, edge: %d ! Has changed %i", mv_ctx->obj, + mv_ctx->mode, mv_ctx->edge, tlobj, mode, edge, mv_ctx->needs_move_ctx); + + clean_movecontext (mv_ctx); + mv_ctx->edge = edge; + mv_ctx->mode = mode; + mv_ctx->obj = tlobj; + mv_ctx->needs_move_ctx = FALSE; + + switch (mode) { + case GES_EDIT_MODE_RIPPLE: + case GES_EDIT_MODE_ROLL: + if (!(ges_move_context_set_objects (timeline, obj, edge))) + return FALSE; + default: + break; + } + + /* And we add the main object to the moving_tlobjs set */ + add_moving_timeline_object (&timeline->priv->movecontext, obj); + + + return TRUE; +} + +gboolean +ges_timeline_trim_object_simple (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position, gboolean snapping) +{ + guint64 nstart, start, inpoint, duration, max_duration, *snapped, *cur; + gboolean ret = TRUE; + gint64 real_dur; + + GST_DEBUG_OBJECT (obj, "Trimming to %" GST_TIME_FORMAT " %s snaping, edge %i", + GST_TIME_ARGS (position), snapping ? "Is" : "Not", edge); + + start = ges_track_object_get_start (obj); + g_object_get (obj, "max-duration", &max_duration, NULL); + + switch (edge) { + case GES_EDGE_START: + inpoint = obj->inpoint; + duration = obj->duration; + + if (snapping) { + cur = g_hash_table_lookup (timeline->priv->by_start, obj); + + snapped = ges_timeline_snap_position (timeline, obj, cur, position, + TRUE); + if (snapped) + position = *snapped; + } + + nstart = position; + + /* Calculate new values */ + position = MAX (start > inpoint ? start - inpoint : 0, position); + position = MIN (position, start + duration); + inpoint = MAX (0, inpoint + position - start); + + real_dur = start + duration - nstart; + /* FIXME: Why CLAMP (0, real_dur, max_duration) doesn't work? */ + duration = MAX (0, real_dur); + duration = MIN (duration, max_duration - obj->inpoint); + + ges_track_object_set_start (obj, nstart); + ges_track_object_set_duration (obj, duration); + ges_track_object_set_inpoint (obj, inpoint); + break; + case GES_EDGE_END: + { + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); + if (snapped) + position = *snapped; + + /* Calculate new values */ + real_dur = position - start; + duration = MAX (0, real_dur); + duration = MIN (duration, max_duration - obj->inpoint); + + ges_track_object_set_duration (obj, duration); + break; + } + default: + GST_WARNING ("Can not trim with %i GESEdge", edge); + return FALSE; + } + + return ret; +} + +gboolean +timeline_ripple_object (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position) +{ + GList *tmp, *moved_tlobjs = NULL; + GESTrackObject *tckobj; + GESTimelineObject *tlobj; + guint64 duration, new_start, *snapped, *cur; + gint64 offset; + + MoveContext *mv_ctx = &timeline->priv->movecontext; + + mv_ctx->ignore_needs_ctx = TRUE; + + if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE, + edge, layers)) + goto error; + + switch (edge) { + case GES_EDGE_NONE: + GST_DEBUG ("Simply rippling"); + + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); + if (snapped) + position = *snapped; + + offset = position - obj->start; + + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + tckobj = GES_TRACK_OBJECT (tmp->data); + new_start = tckobj->start + offset; + + tlobj = add_moving_timeline_object (mv_ctx, tckobj); + + if (ges_track_object_is_locked (tckobj) == TRUE) { + + /* Make sure not to move 2 times the same TimelineObject */ + if (g_list_find (moved_tlobjs, tlobj) == NULL) { + ges_track_object_set_start (tckobj, new_start); + moved_tlobjs = g_list_prepend (moved_tlobjs, tlobj); + } + + } else { + ges_track_object_set_start (tckobj, new_start); + } + } + g_list_free (moved_tlobjs); + ges_track_object_set_start (obj, position); + + break; + case GES_EDGE_END: + GST_DEBUG ("Rippling end"); + + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); + if (snapped) + position = *snapped; + + duration = obj->duration; + + ges_track_object_set_duration (obj, position - obj->start); + + offset = obj->duration - duration; + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + tckobj = GES_TRACK_OBJECT (tmp->data); + new_start = tckobj->start + offset; + + tlobj = add_moving_timeline_object (mv_ctx, tckobj); + + if (ges_track_object_is_locked (tckobj) == TRUE) { + + /* Make sure not to move 2 times the same TimelineObject */ + if (g_list_find (moved_tlobjs, tlobj) == NULL) { + ges_track_object_set_start (tckobj, new_start); + moved_tlobjs = g_list_prepend (moved_tlobjs, tlobj); + } + + } else { + ges_track_object_set_start (tckobj, new_start); + } + } + + g_list_free (moved_tlobjs); + GST_DEBUG ("Done Rippling end"); + break; + case GES_EDGE_START: + GST_WARNING ("Ripple start doesn't exist!"); + + break; + default: + GST_DEBUG ("Can not ripple edge: %i", edge); + + break; + } + + mv_ctx->ignore_needs_ctx = FALSE; + + return TRUE; + +error: + mv_ctx->ignore_needs_ctx = FALSE; + + return FALSE; +} + +gboolean +timeline_slide_object (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position) +{ + + /* FIXME implement me! */ + GST_WARNING ("Slide mode editing not implemented yet"); + + return FALSE; +} + +gboolean +timeline_trim_object (GESTimeline * timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position) +{ + gboolean ret = FALSE; + MoveContext *mv_ctx = &timeline->priv->movecontext; + + mv_ctx->ignore_needs_ctx = TRUE; + + if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM, + edge, layers)) + goto end; + + ret = + ges_timeline_trim_object_simple (timeline, object, layers, edge, position, + TRUE); + +end: + mv_ctx->ignore_needs_ctx = FALSE; + + return ret; +} + +gboolean +timeline_roll_object (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position) +{ + MoveContext *mv_ctx = &timeline->priv->movecontext; + guint64 start, duration, end, tmpstart, tmpduration, tmpend, *snapped, *cur; + gboolean ret = TRUE; + GList *tmp; + + mv_ctx->ignore_needs_ctx = TRUE; + + GST_DEBUG_OBJECT (obj, "Rolling object to %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_ROLL, + edge, layers)) + goto error; + + start = ges_track_object_get_start (obj); + duration = ges_track_object_get_duration (obj); + end = start + duration; + + switch (edge) { + case GES_EDGE_START: + + /* Avoid negative durations */ + if (position < mv_ctx->max_trim_pos || position > end) + goto error; + + cur = g_hash_table_lookup (timeline->priv->by_start, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); + if (snapped) + position = *snapped; + + ret &= + ges_timeline_trim_object_simple (timeline, obj, layers, + GES_EDGE_START, position, FALSE); + + /* In the case we reached max_duration we just make sure to roll + * everything to the real new position */ + position = obj->start; + + /* Send back changes to the neighbourhood */ + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + GESTrackObject *tmptckobj = GES_TRACK_OBJECT (tmp->data); + + tmpstart = ges_track_object_get_start (tmptckobj); + tmpduration = ges_track_object_get_duration (tmptckobj); + tmpend = tmpstart + tmpduration; + + /* Check that the object should be resized at this position + * even if an error accurs, we keep doing our job */ + if (tmpend == start) { + ret &= ges_timeline_trim_object_simple (timeline, tmptckobj, NULL, + GES_EDGE_END, position, FALSE); + break; + } + } + break; + case GES_EDGE_END: + + /* Avoid negative durations */ + if (position > mv_ctx->max_trim_pos || position < start) + goto error; + + end = obj->start + obj->duration; + + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE); + if (snapped) + position = *snapped; + + ret &= ges_timeline_trim_object_simple (timeline, obj, NULL, GES_EDGE_END, + position, FALSE); + + /* In the case we reached max_duration we just make sure to roll + * everything to the real new position */ + position = obj->start + obj->duration; + + /* Send back changes to the neighbourhood */ + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + GESTrackObject *tmptckobj = GES_TRACK_OBJECT (tmp->data); + + tmpstart = ges_track_object_get_start (tmptckobj); + tmpduration = ges_track_object_get_duration (tmptckobj); + tmpend = tmpstart + tmpduration; + + /* Check that the object should be resized at this position + * even if an error accure, we keep doing our job */ + if (end == tmpstart) { + ret &= ges_timeline_trim_object_simple (timeline, tmptckobj, NULL, + GES_EDGE_START, position, FALSE); + } + } + break; + default: + GST_DEBUG ("Edge type %i not handled here", edge); + break; + } + + mv_ctx->ignore_needs_ctx = FALSE; + + return ret; + +error: + mv_ctx->ignore_needs_ctx = FALSE; + + GST_DEBUG_OBJECT (obj, "Could not roll edge %d to %" GST_TIME_FORMAT, + edge, GST_TIME_ARGS (position)); + + return FALSE; +} + +gboolean +timeline_move_object (GESTimeline * timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position) +{ + if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_RIPPLE, + edge, layers)) { + GST_DEBUG_OBJECT (object, "Could not move to %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + return FALSE; + } + + return ges_timeline_move_object_simple (timeline, object, layers, edge, + position); +} + +gboolean +ges_timeline_move_object_simple (GESTimeline * timeline, + GESTrackObject * object, GList * layers, GESEdge edge, guint64 position) +{ + guint64 *snap_end, *snap_st, *cur, off1, off2, end; + + GST_DEBUG_OBJECT (timeline, "Moving to %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + end = position + object->duration; + cur = g_hash_table_lookup (timeline->priv->by_end, object); + snap_end = ges_timeline_snap_position (timeline, object, cur, end, FALSE); + if (snap_end) + off1 = end > *snap_end ? end - *snap_end : *snap_end - end; + else + off1 = G_MAXUINT64; + + cur = g_hash_table_lookup (timeline->priv->by_start, object); + snap_st = ges_timeline_snap_position (timeline, object, cur, position, FALSE); + if (snap_st) + off2 = position > *snap_st ? position - *snap_st : *snap_st - position; + else + off2 = G_MAXUINT64; + + /* In the case we could snap on both sides, we snap on the end */ + if (snap_end && off1 <= off2) { + position = position + *snap_end - end; + ges_timeline_emit_snappig (timeline, object, snap_end); + GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + } else if (snap_st) { + position = position + *snap_st - position; + ges_timeline_emit_snappig (timeline, object, snap_st); + GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + } else + ges_timeline_emit_snappig (timeline, object, NULL); + + + ges_track_object_set_start (object, position); + + return TRUE; +} + +gboolean +timeline_context_to_layer (GESTimeline * timeline, gint offset) +{ + gboolean ret = TRUE; + MoveContext *mv_ctx = &timeline->priv->movecontext; + + + + /* Layer's priority is always positive */ + if (offset != 0 && (offset > 0 || mv_ctx->min_move_layer >= -offset)) { + GHashTableIter iter; + GESTimelineObject *key, *value; + GESTimelineLayer *new_layer, *layer; + guint prio; + + mv_ctx->ignore_needs_ctx = TRUE; + + GST_DEBUG ("Moving %d object, offset %d", + g_hash_table_size (mv_ctx->moving_tlobjs), offset); + + g_hash_table_iter_init (&iter, mv_ctx->moving_tlobjs); + while (g_hash_table_iter_next (&iter, (gpointer *) & key, + (gpointer *) & value)) { + layer = ges_timeline_object_get_layer (value); + prio = ges_timeline_layer_get_priority (layer); + + /* We know that the layer exists as we created it */ + new_layer = GES_TIMELINE_LAYER (g_list_nth_data (timeline->priv->layers, + prio + offset)); + + if (new_layer == NULL) { + do { + new_layer = ges_timeline_append_layer (timeline); + } while (ges_timeline_layer_get_priority (new_layer) < prio + offset); + } + + ret &= ges_timeline_object_move_to_layer (key, new_layer); + + g_object_unref (layer); + } + + /* Readjust min_move_layer */ + mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset; + + mv_ctx->ignore_needs_ctx = FALSE; + } + + return ret; +} + static void add_object_to_track (GESTimelineObject * object, GESTrack * track) { @@ -349,7 +1381,6 @@ add_object_to_tracks (GESTimeline * timeline, GESTimelineObject * object) } } - static void do_async_start (GESTimeline * timeline) { @@ -511,6 +1542,8 @@ layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object, if (ges_timeline_object_is_moving_from_layer (object)) { GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing" " anything on it", object); + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; return; } @@ -605,6 +1638,72 @@ layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object, GST_DEBUG ("Done"); } +static void +trackobj_start_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + sort_track_objects (timeline); + sort_starts_ends_start (timeline, child); + sort_starts_ends_end (timeline, child); + + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static void +trackobj_duration_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + sort_starts_ends_end (timeline, child); + + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static void +trackobj_inpoint_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static void +track_object_added_cb (GESTrack * track, GESTrackObject * object, + GESTimeline * timeline) +{ + /* We only work with sources */ + if (GES_IS_TRACK_SOURCE (object)) { + start_tracking_track_obj (timeline, object); + + g_signal_connect (GES_TRACK_OBJECT (object), "notify::start", + G_CALLBACK (trackobj_start_changed_cb), timeline); + g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration", + G_CALLBACK (trackobj_duration_changed_cb), timeline); + g_signal_connect (GES_TRACK_OBJECT (object), "notify::in-point", + G_CALLBACK (trackobj_inpoint_changed_cb), timeline); + } +} + +static void +track_object_removed_cb (GESTrack * track, GESTrackObject * object, + GESTimeline * timeline) +{ + /* We only work with sources */ + if (GES_IS_TRACK_SOURCE (object)) { + g_signal_handlers_disconnect_by_func (object, trackobj_start_changed_cb, + NULL); + g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb, + NULL); + g_signal_handlers_disconnect_by_func (object, trackobj_inpoint_changed_cb, + NULL); + + /* Make sure to reinitialise the moving context next time */ + timeline->priv->movecontext.needs_move_ctx = TRUE; + stop_tracking_for_snapping (timeline, object); + } +} + static void track_duration_cb (GstElement * track, GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) @@ -615,7 +1714,9 @@ track_duration_cb (GstElement * track, for (tmp = timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) { TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; g_object_get (tr_priv->track, "duration", &duration, NULL); - GST_DEBUG ("track duration : %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); + + GST_DEBUG_OBJECT (track, "track duration : %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); max_duration = MAX (duration, max_duration); } @@ -810,7 +1911,7 @@ fail: * ges_timeline_append_layer: * @timeline: a #GESTimeline * - * Append a newly creater #GESTimelineLayer to @timeline + * Append a newly created #GESTimelineLayer to @timeline * Note that you do not own any reference to the returned layer. * * Returns: (transfer none): The newly created #GESTimelineLayer, or the last (empty) @@ -1069,6 +2170,17 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) /* ensure that each existing timeline object has the opportunity to create a * track object for this track*/ + /* We connect to the duration change notify, so we can update + * our duration accordingly */ + g_signal_connect (G_OBJECT (track), "notify::duration", + G_CALLBACK (track_duration_cb), timeline); + + /* We connect to the object for the timeline editing mode management */ + g_signal_connect (G_OBJECT (track), "track-object-added", + G_CALLBACK (track_object_added_cb), timeline); + g_signal_connect (G_OBJECT (track), "track-object-removed", + G_CALLBACK (track_object_removed_cb), timeline); + for (tmp = priv->layers; tmp; tmp = tmp->next) { GList *objects, *obj; objects = ges_timeline_layer_get_objects (tmp->data); @@ -1081,10 +2193,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) g_list_free (objects); } - /* We connect to the duration change notify, so we can update - * our duration accordingly */ - g_signal_connect (G_OBJECT (track), "notify::duration", - G_CALLBACK (track_duration_cb), timeline); track_duration_cb (GST_ELEMENT (track), NULL, timeline); return TRUE; @@ -1141,6 +2249,9 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track) g_signal_handlers_disconnect_by_func (track, pad_removed_cb, tr_priv); g_signal_handlers_disconnect_by_func (track, track_duration_cb, tr_priv->track); + g_signal_handlers_disconnect_by_func (track, track_object_added_cb, timeline); + g_signal_handlers_disconnect_by_func (track, track_object_removed_cb, + timeline); /* Signal track removal to all layers/objects */ g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track); @@ -1282,5 +2393,8 @@ ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled) res = FALSE; } + /* Make sure we reset the context */ + timeline->priv->movecontext.needs_move_ctx = TRUE; + return res; } diff --git a/ges/ges-track-object.c b/ges/ges-track-object.c index 713ec4d9c8..c4d397239f 100644 --- a/ges/ges-track-object.c +++ b/ges/ges-track-object.c @@ -308,8 +308,8 @@ ges_track_object_class_init (GESTrackObjectClass * klass) */ g_object_class_install_property (object_class, PROP_MAX_DURATION, g_param_spec_uint64 ("max-duration", "Maximum duration", - "The duration of the object", 0, G_MAXUINT64, G_MAXUINT64, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + "The duration of the object", GST_CLOCK_TIME_NONE, G_MAXUINT64, + G_MAXUINT64, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); /** * GESTrackObject::deep-notify: @@ -337,17 +337,18 @@ ges_track_object_class_init (GESTrackObjectClass * klass) static void ges_track_object_init (GESTrackObject * self) { - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GESTrackObjectPrivate *priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GES_TYPE_TRACK_OBJECT, GESTrackObjectPrivate); /* Sane default values */ - self->priv->pending_start = 0; - self->priv->pending_inpoint = 0; - self->priv->pending_duration = GST_SECOND; - self->priv->pending_priority = 1; - self->priv->pending_active = TRUE; - self->priv->locked = TRUE; - self->priv->properties_hashtable = NULL; + priv->pending_start = 0; + priv->pending_inpoint = 0; + priv->pending_duration = GST_SECOND; + priv->pending_priority = 1; + priv->pending_active = TRUE; + priv->locked = TRUE; + priv->properties_hashtable = NULL; + priv->maxduration = GST_CLOCK_TIME_NONE; } static inline gboolean @@ -376,6 +377,8 @@ ges_track_object_set_start_internal (GESTrackObject * object, guint64 start) void ges_track_object_set_start (GESTrackObject * object, guint64 start) { + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + if (ges_track_object_set_start_internal (object, start)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_START]); @@ -412,6 +415,8 @@ ges_track_object_set_inpoint_internal (GESTrackObject * object, guint64 inpoint) void ges_track_object_set_inpoint (GESTrackObject * object, guint64 inpoint) { + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + if (ges_track_object_set_inpoint_internal (object, inpoint)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_INPOINT]); @@ -424,17 +429,24 @@ static inline gboolean ges_track_object_set_duration_internal (GESTrackObject * object, guint64 duration) { + GESTrackObjectPrivate *priv = object->priv; + GST_DEBUG ("object:%p, duration:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (duration)); - if (object->priv->gnlobject != NULL) { + if (GST_CLOCK_TIME_IS_VALID (priv->maxduration) && + duration > object->inpoint + priv->maxduration) + duration = priv->maxduration - object->inpoint; + + if (priv->gnlobject != NULL) { if (G_UNLIKELY (duration == object->duration)) return FALSE; - g_object_set (object->priv->gnlobject, "duration", duration, + g_object_set (priv->gnlobject, "duration", duration, "media-duration", duration, NULL); } else - object->priv->pending_duration = duration; + priv->pending_duration = duration; + return TRUE; } @@ -449,6 +461,8 @@ ges_track_object_set_duration_internal (GESTrackObject * object, void ges_track_object_set_duration (GESTrackObject * object, guint64 duration) { + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + if (ges_track_object_set_duration_internal (object, duration)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_DURATION]); @@ -511,6 +525,8 @@ ges_track_object_set_priority (GESTrackObject * object, guint32 priority) gboolean ges_track_object_set_active (GESTrackObject * object, gboolean active) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); + GST_DEBUG ("object:%p, active:%d", object, active); if (object->priv->gnlobject != NULL) { @@ -631,7 +647,7 @@ gnlobject_duration_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, g_object_get (gnlobject, "duration", &duration, NULL); - GST_DEBUG ("gnlobject duration : %" GST_TIME_FORMAT " current : %" + GST_DEBUG_OBJECT (gnlobject, "duration : %" GST_TIME_FORMAT " current : %" GST_TIME_FORMAT, GST_TIME_ARGS (duration), GST_TIME_ARGS (obj->duration)); if (duration != obj->duration) { @@ -765,20 +781,21 @@ ensure_gnl_object (GESTrackObject * object) GST_DEBUG ("Calling virtual method"); - /* call the create_gnl_object virtual method */ - gnlobject = class->create_gnl_object (object); - - if (G_UNLIKELY (gnlobject == NULL)) { - GST_ERROR - ("'create_gnl_object' implementation returned TRUE but no GnlObject is available"); - goto done; - } - - object->priv->gnlobject = gnlobject; - /* 2. Fill in the GnlObject */ - if (gnlobject) { - GST_DEBUG ("Got a valid GnlObject, now filling it in"); + if (object->priv->gnlobject == NULL) { + + /* call the create_gnl_object virtual method */ + gnlobject = class->create_gnl_object (object); + + if (G_UNLIKELY (gnlobject == NULL)) { + GST_ERROR + ("'create_gnl_object' implementation returned TRUE but no GnlObject is available"); + goto done; + } + + GST_DEBUG_OBJECT (object, "Got a valid GnlObject, now filling it in"); + + object->priv->gnlobject = gnlobject; if (object->priv->timelineobj) res = ges_timeline_object_fill_track_object (object->priv->timelineobj, @@ -802,7 +819,6 @@ ensure_gnl_object (GESTrackObject * object) /* Set some properties on the GnlObject */ g_object_set (object->priv->gnlobject, - "caps", ges_track_get_caps (object->priv->track), "duration", object->priv->pending_duration, "media-duration", object->priv->pending_duration, "start", object->priv->pending_start, @@ -810,6 +826,10 @@ ensure_gnl_object (GESTrackObject * object) "priority", object->priv->pending_priority, "active", object->priv->pending_active, NULL); + if (object->priv->track != NULL) + g_object_set (object->priv->gnlobject, + "caps", ges_track_get_caps (object->priv->track), NULL); + /* We feed up the props_hashtable if possible */ if (class->get_props_hastable) { props_hash = class->get_props_hastable (object); @@ -841,8 +861,16 @@ ges_track_object_set_track (GESTrackObject * object, GESTrack * track) object->priv->track = track; - if (object->priv->track) - return ensure_gnl_object (object); + if (object->priv->track) { + /* If we already have a gnlobject, we just set its caps properly */ + if (object->priv->gnlobject) { + g_object_set (object->priv->gnlobject, + "caps", ges_track_get_caps (object->priv->track), NULL); + return TRUE; + } else { + return ensure_gnl_object (object); + } + } return TRUE; } @@ -969,6 +997,8 @@ ges_track_object_set_locked (GESTrackObject * object, gboolean locked) gboolean ges_track_object_is_locked (GESTrackObject * object) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); + return object->priv->locked; } @@ -978,13 +1008,16 @@ ges_track_object_is_locked (GESTrackObject * object) * * Get the position of the object in the container #GESTrack. * - * Returns: the start position (in #GstClockTime) + * Returns: the start position (in #GstClockTime) or #GST_CLOCK_TIME_NONE + * if something went wrong. * * Since: 0.10.2 */ guint64 ges_track_object_get_start (GESTrackObject * object) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), GST_CLOCK_TIME_NONE); + if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_start; else @@ -997,13 +1030,16 @@ ges_track_object_get_start (GESTrackObject * object) * * Get the offset within the contents of this #GESTrackObject * - * Returns: the in-point (in #GstClockTime) + * Returns: the in-point (in #GstClockTime) or #GST_CLOCK_TIME_NONE + * if something went wrong. * * Since: 0.10.2 */ guint64 ges_track_object_get_inpoint (GESTrackObject * object) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), GST_CLOCK_TIME_NONE); + if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_inpoint; else @@ -1017,13 +1053,16 @@ ges_track_object_get_inpoint (GESTrackObject * object) * Get the duration which will be used in the container #GESTrack * starting from the 'in-point' * - * Returns: the duration (in #GstClockTime) + * Returns: the duration (in #GstClockTime) or #GST_CLOCK_TIME_NONE + * if something went wrong. * * Since: 0.10.2 */ guint64 ges_track_object_get_duration (GESTrackObject * object) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), GST_CLOCK_TIME_NONE); + if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_duration; else @@ -1036,13 +1075,15 @@ ges_track_object_get_duration (GESTrackObject * object) * * Get the priority of the object withing the containing #GESTrack. * - * Returns: the priority of @object + * Returns: the priority of @object or -1 if something went wrong * * Since: 0.10.2 */ guint32 ges_track_object_get_priority (GESTrackObject * object) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), -1); + if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_priority; else @@ -1063,6 +1104,8 @@ ges_track_object_get_priority (GESTrackObject * object) gboolean ges_track_object_is_active (GESTrackObject * object) { + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); + if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_active; else @@ -1100,7 +1143,11 @@ ges_track_object_lookup_child (GESTrackObject * object, const gchar * prop_name, gpointer key, value; gchar **names, *name, *classename; gboolean res; - GESTrackObjectPrivate *priv = object->priv; + GESTrackObjectPrivate *priv; + + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); + + priv = object->priv; classename = NULL; res = FALSE; @@ -1147,8 +1194,11 @@ ges_track_object_set_child_property_by_pspec (GESTrackObject * object, GParamSpec * pspec, GValue * value) { GstElement *element; + GESTrackObjectPrivate *priv; - GESTrackObjectPrivate *priv = object->priv; + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + + priv = object->priv; if (!priv->properties_hashtable) goto prop_hash_not_set; @@ -1198,7 +1248,7 @@ ges_track_object_set_child_property_valist (GESTrackObject * object, gchar *error = NULL; GValue value = { 0, }; - g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); name = first_property_name; @@ -1264,6 +1314,8 @@ ges_track_object_set_child_property (GESTrackObject * object, { va_list var_args; + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + va_start (var_args, first_property_name); ges_track_object_set_child_property_valist (object, first_property_name, var_args); @@ -1372,6 +1424,8 @@ ges_track_object_get_child_property (GESTrackObject * object, { va_list var_args; + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + va_start (var_args, first_property_name); ges_track_object_get_child_property_valist (object, first_property_name, var_args); @@ -1393,8 +1447,11 @@ ges_track_object_get_child_property_by_pspec (GESTrackObject * object, GParamSpec * pspec, GValue * value) { GstElement *element; + GESTrackObjectPrivate *priv; - GESTrackObjectPrivate *priv = object->priv; + g_return_if_fail (GES_IS_TRACK_OBJECT (object)); + + priv = object->priv; if (!priv->properties_hashtable) goto prop_hash_not_set; @@ -1485,3 +1542,134 @@ ges_track_object_set_max_duration (GESTrackObject * object, guint64 maxduration) object->priv->maxduration = maxduration; } + +/** + * ges_track_object_copy: + * @object: The #GESTrackObject to copy + * @deep: whether we want to create the gnlobject and copy it properties + * + * Copies @object + * + * Returns: The newly create #GESTrackObject, copied from @object + * + * Since: 0.10.XX + */ +GESTrackObject * +ges_track_object_copy (GESTrackObject * object, gboolean deep) +{ + GESTrackObject *ret = NULL; + GParameter *params; + GParamSpec **specs; + guint n, n_specs, n_params; + GValue val = { 0 }; + + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), NULL); + + specs = + g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &n_specs); + params = g_new0 (GParameter, n_specs); + n_params = 0; + + for (n = 0; n < n_specs; ++n) { + if (g_strcmp0 (specs[n]->name, "parent") && + (specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE) { + params[n_params].name = g_intern_string (specs[n]->name); + g_value_init (¶ms[n_params].value, specs[n]->value_type); + g_object_get_property (G_OBJECT (object), specs[n]->name, + ¶ms[n_params].value); + ++n_params; + } + } + + ret = g_object_newv (G_TYPE_FROM_INSTANCE (object), n_params, params); + g_free (specs); + g_free (params); + specs = NULL; + params = NULL; + + if (deep == FALSE) + return ret; + + ensure_gnl_object (ret); + specs = ges_track_object_list_children_properties (object, &n_specs); + for (n = 0; n < n_specs; ++n) { + g_value_init (&val, specs[n]->value_type); + g_object_get_property (G_OBJECT (object), specs[n]->name, &val); + ges_track_object_set_child_property_by_pspec (ret, specs[n], &val); + g_value_unset (&val); + } + + g_free (specs); + g_free (params); + + return ret; +} + +/** + * ges_track_object_edit: + * @object: the #GESTrackObject to edit + * @layers: (element-type GESTimelineLayer): The layers you want the edit to + * happen in, %NULL means that the edition is done in all the + * #GESTimelineLayers contained in the current timeline. + * FIXME: This is not implemented yet. + * @mode: The #GESEditMode in which the editition will happen. + * @edge: The #GESEdge the edit should happen on. + * @position: The position at which to edit @object (in nanosecond) + * + * Edit @object in the different exisiting #GESEditMode modes. In the case of + * slide, and roll, you need to specify a #GESEdge + * + * Returns: %TRUE if the object as been edited properly, %FALSE if an error + * occured + * + * Since: 0.10.XX + */ +gboolean +ges_track_object_edit (GESTrackObject * object, + GList * layers, GESEditMode mode, GESEdge edge, guint64 position) +{ + GESTrack *track = ges_track_object_get_track (object); + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); + + if (G_UNLIKELY (!track)) { + GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in" + "any Track yet.", mode); + return FALSE; + } else if (position < 0) { + GST_DEBUG_OBJECT (object, "Trying to move before 0, not moving"); + return FALSE; + } + + timeline = GES_TIMELINE (ges_track_get_timeline (track)); + + if (G_UNLIKELY (!timeline)) { + GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in" + "track %p no in any timeline yet.", mode, track); + return FALSE; + } + + switch (mode) { + case GES_EDIT_MODE_NORMAL: + timeline_move_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_TRIM: + timeline_trim_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_RIPPLE: + timeline_ripple_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_ROLL: + timeline_roll_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_SLIDE: + timeline_slide_object (timeline, object, layers, edge, position); + break; + default: + GST_ERROR ("Unkown edit mode: %d", mode); + return FALSE; + } + + return TRUE; +} diff --git a/ges/ges-track-object.h b/ges/ges-track-object.h index b7efa93aa3..307a9a9ce8 100644 --- a/ges/ges-track-object.h +++ b/ges/ges-track-object.h @@ -226,6 +226,13 @@ void ges_track_object_set_child_property (GESTrackObject * object, const gchar * first_property_name, ...) G_GNUC_NULL_TERMINATED; +GESTrackObject * ges_track_object_copy (GESTrackObject * object, + gboolean deep); + +gboolean +ges_track_object_edit (GESTrackObject * object, + GList *layers, GESEditMode mode, + GESEdge edge, guint64 position); G_END_DECLS #endif /* _GES_TRACK_OBJECT */ diff --git a/ges/ges-track.c b/ges/ges-track.c index 163d263b4e..0ab6464d30 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -455,14 +455,6 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object) return FALSE; } - /* At this point, the track object shouldn't have any gnlobject since - * it hasn't been added to a track yet. - * FIXME : This check seems a bit obsolete */ - if (G_UNLIKELY (ges_track_object_get_gnlobject (object) != NULL)) { - GST_ERROR ("TrackObject already controls a gnlobject !"); - return FALSE; - } - if (G_UNLIKELY (!ges_track_object_set_track (object, track))) { GST_ERROR ("Couldn't properly add the object to the Track"); return FALSE; diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 33a6010fd0..81f973ea95 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -22,6 +22,7 @@ check_PROGRAMS = \ ges/filesource \ ges/simplelayer \ ges/timelineobject \ + ges/timelineedition \ ges/titles\ ges/transition \ ges/overlays\ diff --git a/tests/check/ges/basic.c b/tests/check/ges/basic.c index 98271cac42..358d313347 100644 --- a/tests/check/ges/basic.c +++ b/tests/check/ges/basic.c @@ -115,19 +115,21 @@ GST_START_TEST (test_ges_scenario) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (source)); fail_unless (trackobjects != NULL); trackobject = GES_TRACK_OBJECT (trackobjects->data); - /* There are 3 references: + /* There are 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 4); for (tmp = trackobjects; tmp; tmp = tmp->next) { g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the track */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); GST_DEBUG ("Remove the TimelineObject from the layer"); @@ -252,19 +254,21 @@ GST_START_TEST (test_ges_timeline_add_layer) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s1)); fail_unless (trackobjects != NULL); trackobject = GES_TRACK_OBJECT (trackobjects->data); - /* There are 3 references: + /* There are 4 references: * 1 by the timelineobject * 1 by the trackobject + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 4); for (tmp = trackobjects; tmp; tmp = tmp->next) { g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the trackobject */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s2)); @@ -274,10 +278,11 @@ GST_START_TEST (test_ges_timeline_add_layer) g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the trackobject */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (trackobject), "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (trackobject), "trackobject", 3); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s3)); @@ -287,10 +292,11 @@ GST_START_TEST (test_ges_timeline_add_layer) g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the trackobject */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); /* theoretically this is all we need to do to ensure cleanup */ g_object_unref (timeline); @@ -369,11 +375,12 @@ GST_START_TEST (test_ges_timeline_add_layer_first) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s1)); fail_unless (trackobjects != NULL); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* Each object has 3 references: + /* Each object has 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by _get_track_object() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); @@ -382,11 +389,12 @@ GST_START_TEST (test_ges_timeline_add_layer_first) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s2)); fail_unless (trackobjects != NULL); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* Each object has 3 references: + /* Each object has 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by _get_track_object() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); @@ -395,11 +403,12 @@ GST_START_TEST (test_ges_timeline_add_layer_first) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s3)); fail_unless (trackobjects != NULL); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* Each object has 3 references: + /* Each object has 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by _get_track_object() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); @@ -482,67 +491,73 @@ GST_START_TEST (test_ges_timeline_remove_track) fail_unless (trackobjects != NULL); t1 = GES_TRACK_OBJECT ((trackobjects)->data); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_object_ref (t1); g_list_free (trackobjects); - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelinobject * 1 by the track + * 1 by the timeline * 1 added by ourselves above (g_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 4); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s2)); fail_unless (trackobjects != NULL); t2 = GES_TRACK_OBJECT (trackobjects->data); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_object_ref (t2); g_list_free (trackobjects); - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelinobject * 1 by the track + * 1 by the timeline * 1 added by ourselves above (g_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t2, "t2", 3); + ASSERT_OBJECT_REFCOUNT (t2, "t2", 4); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s3)); fail_unless (trackobjects != NULL); t3 = GES_TRACK_OBJECT (trackobjects->data); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_object_ref (t3); g_list_free (trackobjects); - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelinobject * 1 by the track + * 1 by the timeline * 1 added by ourselves above (g_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t3, "t3", 3); + ASSERT_OBJECT_REFCOUNT (t3, "t3", 4); /* remove the track and check that the track objects have been released */ fail_unless (ges_timeline_remove_track (timeline, track)); - ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 1); - ASSERT_OBJECT_REFCOUNT (t2, "trackobject", 1); - ASSERT_OBJECT_REFCOUNT (t3, "trackobject", 1); + ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (t2, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (t3, "trackobject", 2); g_object_unref (t1); g_object_unref (t2); diff --git a/tests/check/ges/timelineedition.c b/tests/check/ges/timelineedition.c new file mode 100644 index 0000000000..9e1e9c01ca --- /dev/null +++ b/tests/check/ges/timelineedition.c @@ -0,0 +1,975 @@ +/* GStreamer Editing Services + * + * Copyright (C) <2012> 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. + */ + +#include +#include + +static gboolean +my_fill_track_func (GESTimelineObject * object, + GESTrackObject * trobject, GstElement * gnlobj, gpointer user_data) +{ + GstElement *src; + + GST_DEBUG ("timelineobj:%p, trackobjec:%p, gnlobj:%p", + object, trobject, gnlobj); + + /* Let's just put a fakesource in for the time being */ + src = gst_element_factory_make ("fakesrc", NULL); + /* If this fails... that means that there already was something + * in it */ + fail_unless (gst_bin_add (GST_BIN (gnlobj), src)); + + return TRUE; +} + +static inline GESTimelineObject * +create_custom_tlobj (void) +{ + return + GES_TIMELINE_OBJECT (ges_custom_timeline_source_new (my_fill_track_func, + NULL)); +} + +#define CHECK_OBJECT_PROPS(obj, start, inpoint, duration) {\ + assert_equals_uint64 (ges_track_object_get_start (obj), start);\ + assert_equals_uint64 (ges_track_object_get_inpoint (obj), inpoint);\ + assert_equals_uint64 (ges_track_object_get_duration (obj), duration);\ +} + +GST_START_TEST (test_basic_timeline_edition) +{ + GESTrack *track; + GESTimeline *timeline; + GESTrackObject *tckobj, *tckobj1, *tckobj2; + GESTimelineObject *obj, *obj1, *obj2; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_CUSTOM, GST_CAPS_ANY); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + fail_unless (ges_timeline_add_track (timeline, track)); + + obj = create_custom_tlobj (); + obj1 = create_custom_tlobj (); + obj2 = create_custom_tlobj (); + + + fail_unless (obj && obj1 && obj2); + + /** + * Our timeline + * + * inpoints 0------- 0-------- 0----------- + * | obj | | obj1 | | obj2 | + * time 0------- 10 --------20 50---------60 + */ + g_object_set (obj, "start", (guint64) 0, "duration", (guint64) 10, + "in-point", (guint64) 0, NULL); + g_object_set (obj1, "start", (guint64) 10, "duration", (guint64) 10, + "in-point", (guint64) 0, NULL); + g_object_set (obj2, "start", (guint64) 50, "duration", (guint64) 60, + "in-point", (guint64) 0, NULL); + + tckobj = ges_timeline_object_create_track_object (obj, track); + fail_unless (tckobj != NULL); + fail_unless (ges_timeline_object_add_track_object (obj, tckobj)); + fail_unless (ges_track_add_object (track, tckobj)); + assert_equals_uint64 (ges_track_object_get_duration (tckobj), 10); + + tckobj1 = ges_timeline_object_create_track_object (obj1, track); + fail_unless (tckobj1 != NULL); + fail_unless (ges_timeline_object_add_track_object (obj1, tckobj1)); + fail_unless (ges_track_add_object (track, tckobj1)); + assert_equals_uint64 (ges_track_object_get_duration (tckobj1), 10); + + tckobj2 = ges_timeline_object_create_track_object (obj2, track); + fail_unless (ges_timeline_object_add_track_object (obj2, tckobj2)); + fail_unless (tckobj2 != NULL); + fail_unless (ges_track_add_object (track, tckobj2)); + assert_equals_uint64 (ges_track_object_get_duration (tckobj2), 60); + + /** + * Simple rippling obj to: 10 + * + * New timeline: + * ------------ + * + * inpoints 0------- 0-------- 0----------- + * | obj | | obj1 | | obj2 | + * time 10------- 20 --------30 60---------120 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 10) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 10, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 60, 0, 60); + + + /* FIXME find a way to check that we are using the same MovingContext + * inside the GESTrack */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 40) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 10, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 40, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 80, 0, 60); + + /** + * Rippling obj1 back to: 20 (getting to the exact same timeline as before + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 20) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 10, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 60, 0, 60); + + /** + * Simple move obj to: 27 and obj2 to 35 + * + * New timeline: + * ------------ + * 0------------ + * inpoints 0-------|--- obj 0--|---------- + * | obj1 27 -|-----|-37 obj2 | + * time 20-----------30 35-------------120 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 27) == TRUE); + fail_unless (ges_timeline_object_edit (obj2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 35) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 27, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + + /** + * Trim start obj to: 32 and obj2 to 35 + * + * New timeline: + * ------------ + * 5-------- + * inpoints 0----------- | obj 0--|---------- + * | obj1 | 32----|-37 obj2 | + * time 20-----------30 35-------------120 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 32) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 5); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + + /* Ripple end obj to 42 + * New timeline: + * ------------ + * 5-------- + * inpoints 0----------- | obj 0--|---------- + * | obj1 | 32----|-42 obj2 | + * time 20-----------30 35-------------120 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 42) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + + /** + * New timeline: + * ------------ + * inpoints 0------- 5-------- 0----------- + * | obj1 | | obj1 || obj2 | + * time 20-------30 32--------52 ---------112 + */ + fail_unless (ges_timeline_object_edit (obj2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 42) == TRUE); + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 52) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 20); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 52, 0, 60); + + /** + * New timeline: + * ------------ + * inpoints 0------- 5-------- 0------------ + * | obj1 | | obj || obj2 | + * time 20-------40 42--------62 ---------122 + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 40) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 42, 5, 20); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 20); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /** + * New timeline: + * ------------ + * inpoints 0------- 0-------- 0----------- + * | obj1 || obj || obj2 | + * time 20------ 25 ------ 62 ---------122 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 40) == TRUE); + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 25) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Make sure that not doing anything when not able to roll */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 65) == TRUE); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 65) == TRUE, 0); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + g_object_unref (timeline); + g_object_unref (obj); + g_object_unref (obj1); + g_object_unref (obj2); +} + +GST_END_TEST; + +GST_START_TEST (test_snapping) +{ + GESTrack *track; + GESTimeline *timeline; + GESTrackObject *tckobj, *tckobj1, *tckobj2; + GESTimelineObject *obj, *obj1, *obj2; + GESTimelineLayer *layer; + GList *tckobjs; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_CUSTOM, GST_CAPS_ANY); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + fail_unless (ges_timeline_add_track (timeline, track)); + + obj = create_custom_tlobj (); + obj1 = create_custom_tlobj (); + obj2 = create_custom_tlobj (); + + fail_unless (obj && obj1 && obj2); + + /** + * Our timeline + * ------------ + * inpoints 0------- 0-------- 0----------- + * | obj1 || obj || obj2 | + * time 20------ 25 ------ 62 ---------122 + */ + g_object_set (obj, "start", (guint64) 25, "duration", (guint64) 37, + "in-point", (guint64) 0, NULL); + g_object_set (obj1, "start", (guint64) 20, "duration", (guint64) 15, + "in-point", (guint64) 0, NULL); + g_object_set (obj2, "start", (guint64) 62, "duration", (guint64) 60, + "in-point", (guint64) 0, NULL); + + fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL); + assert_equals_int (ges_timeline_layer_get_priority (layer), 0); + + + fail_unless (ges_timeline_layer_add_object (layer, obj)); + fail_unless ((tckobjs = ges_timeline_object_get_track_objects (obj)) != NULL); + fail_unless ((tckobj = GES_TRACK_OBJECT (tckobjs->data)) != NULL); + fail_unless (ges_track_object_get_track (tckobj) == track); + assert_equals_uint64 (ges_track_object_get_duration (tckobj), 37); + g_list_free_full (tckobjs, g_object_unref); + + /* We have 3 references to tckobj from: + * track + timeline + obj */ + ASSERT_OBJECT_REFCOUNT (tckobj, "First tckobj", 3); + /* We have 1 ref to obj1: + * + layer */ + ASSERT_OBJECT_REFCOUNT (obj, "First tlobj", 1); + + fail_unless (ges_timeline_layer_add_object (layer, obj1)); + fail_unless ((tckobjs = + ges_timeline_object_get_track_objects (obj1)) != NULL); + fail_unless ((tckobj1 = GES_TRACK_OBJECT (tckobjs->data)) != NULL); + fail_unless (ges_track_object_get_track (tckobj1) == track); + assert_equals_uint64 (ges_track_object_get_duration (tckobj1), 15); + g_list_free_full (tckobjs, g_object_unref); + + /* Same ref logic */ + ASSERT_OBJECT_REFCOUNT (tckobj1, "First tckobj", 3); + ASSERT_OBJECT_REFCOUNT (obj1, "First tlobj", 1); + + fail_unless (ges_timeline_layer_add_object (layer, obj2)); + fail_unless ((tckobjs = + ges_timeline_object_get_track_objects (obj2)) != NULL); + fail_unless ((tckobj2 = GES_TRACK_OBJECT (tckobjs->data)) != NULL); + fail_unless (ges_track_object_get_track (tckobj2) == track); + assert_equals_uint64 (ges_track_object_get_duration (tckobj2), 60); + g_list_free_full (tckobjs, g_object_unref); + + /* Same ref logic */ + ASSERT_OBJECT_REFCOUNT (tckobj2, "First tckobj", 3); + ASSERT_OBJECT_REFCOUNT (obj2, "First tlobj", 1); + + /* Snaping to edge, so no move */ + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 27) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Snaping to edge, so no move */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 27) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /** + * New timeline: + * ------------ + * 0----------- 0------------- + * inpoints 0-------|-- obj || obj2 | + * | obj1 25-|------- 62 -----------122 + * time 20----------30 + */ + g_object_set (timeline, "snapping-distance", (guint64) 0, NULL); + ges_timeline_object_set_duration (obj1, 10); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /** + * New timeline(the "layers" are just to help reading diagram, nothing else): + * ------------ + * 0---------- + * | obj | + * 25---------62 + * inpoints 0----------------------- 10-------- + * | obj1 || obj2 | + * time 20---------------------- 72 --------122 + */ + /* Rolling involves only neighbour that are currently snapping */ + fail_unless (ges_timeline_object_roll_end (obj1, 62)); + fail_unless (ges_timeline_object_roll_end (obj1, 72) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 52); + CHECK_OBJECT_PROPS (tckobj2, 72, 10, 50); + + /** + * 0---------- + * | obj | + * 25---------62 + * inpoints 5--------------- 10-------- + * | obj1 || obj2 | + * time 25------------- 72 --------122 + */ + g_object_set (timeline, "snapping-distance", (guint64) 4, NULL); + fail_unless (ges_timeline_object_trim_start (obj1, 28) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 25, 5, 47); + CHECK_OBJECT_PROPS (tckobj2, 72, 10, 50); + + /** + * 0---------- + * | obj | + * 25---------62 + * inpoints 5---------- 0--------- + * | obj1 || obj2 | + * time 25-------- 62 --------122 + */ + fail_unless (ges_timeline_object_roll_start (obj2, 59) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 25, 5, 37); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /** + * inpoints 0----------5---------- 0---------- + * | obj || obj1 || obj2 | + * time 25---------62-------- 99 --------170 + */ + fail_unless (ges_timeline_object_ripple (obj1, 58) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 62, 5, 37); + CHECK_OBJECT_PROPS (tckobj2, 99, 0, 60); + + /** + * inpoints 0----------5---------- 0---------- + * | obj || obj1 | | obj2 | + * time 25---------62-------- 99 110--------170 + */ + ges_timeline_object_set_start (obj2, 110); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 62, 5, 37); + CHECK_OBJECT_PROPS (tckobj2, 110, 0, 60); + + /** + * inpoints 0----------5 5 --------- 0---------- + * | obj | | obj1 || obj2 | + * time 25---------62 73---------110--------170 + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 72) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 73, 5, 37); + CHECK_OBJECT_PROPS (tckobj2, 110, 0, 60); + + /** + * inpoints 0----------5---------- 0---------- + * | obj || obj1 | | obj2 | + * time 25---------62-------- 99 110--------170 + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 58) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 62, 5, 37); + CHECK_OBJECT_PROPS (tckobj2, 110, 0, 60); + + + /** + * inpoints 0----------5---------- 0---------- + * | obj || obj1 || obj2 | + * time 25---------62--------110--------170 + */ + g_object_set (obj1, "duration", 46, NULL); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 62, 5, 48); + CHECK_OBJECT_PROPS (tckobj2, 110, 0, 60); + + /** + * inpoints 5----------- 0--------- 0---------- + * | obj1 || obj2 || obj | + * time 62---------110--------170--------207 + */ + g_object_set (obj, "start", 168, NULL); + CHECK_OBJECT_PROPS (tckobj, 170, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 62, 5, 48); + CHECK_OBJECT_PROPS (tckobj2, 110, 0, 60); + + /* Check we didn't lose/screwed any references */ + ASSERT_OBJECT_REFCOUNT (tckobj, "First tckobj", 3); + ASSERT_OBJECT_REFCOUNT (tckobj1, "Second tckobj", 3); + ASSERT_OBJECT_REFCOUNT (tckobj2, "Third tckobj", 3); + ASSERT_OBJECT_REFCOUNT (obj, "First tlobj", 1); + ASSERT_OBJECT_REFCOUNT (obj1, "Second tlobj", 1); + ASSERT_OBJECT_REFCOUNT (obj2, "Third tlobj", 1); + + g_object_unref (timeline); + + /* Check we destroyed everything */ + fail_if (G_IS_OBJECT (tckobj)); + fail_if (G_IS_OBJECT (tckobj1)); + fail_if (G_IS_OBJECT (tckobj2)); + fail_if (G_IS_OBJECT (obj)); + fail_if (G_IS_OBJECT (obj1)); + fail_if (G_IS_OBJECT (obj2)); + fail_if (G_IS_OBJECT (layer)); +} + +GST_END_TEST; + +GST_START_TEST (test_timeline_edition_mode) +{ + guint i; + GESTrack *track; + GESTimeline *timeline; + GESTrackObject *tckobj, *tckobj1, *tckobj2; + GESTimelineObject *obj, *obj1, *obj2; + GESTimelineLayer *layer, *layer1, *layer2; + GList *tckobjs, *layers, *tmp; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_CUSTOM, GST_CAPS_ANY); + fail_unless (track != NULL); + + timeline = ges_timeline_new (); + fail_unless (timeline != NULL); + + fail_unless (ges_timeline_add_track (timeline, track)); + + obj = create_custom_tlobj (); + obj1 = create_custom_tlobj (); + obj2 = create_custom_tlobj (); + + fail_unless (obj && obj1 && obj2); + + /** + * Our timeline + * + * 0------- + * layer: | obj | + * 0-------10 + * + * 0-------- 0----------- + * layer1: | obj1 | | obj2 | + * 10--------20 50---------60 + */ + g_object_set (obj, "start", (guint64) 0, "duration", (guint64) 10, + "in-point", (guint64) 0, NULL); + g_object_set (obj1, "start", (guint64) 10, "duration", (guint64) 10, + "in-point", (guint64) 0, NULL); + g_object_set (obj2, "start", (guint64) 50, "duration", (guint64) 60, + "in-point", (guint64) 0, NULL); + + fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL); + assert_equals_int (ges_timeline_layer_get_priority (layer), 0); + + + fail_unless (ges_timeline_layer_add_object (layer, obj)); + fail_unless ((tckobjs = ges_timeline_object_get_track_objects (obj)) != NULL); + fail_unless ((tckobj = GES_TRACK_OBJECT (tckobjs->data)) != NULL); + fail_unless (ges_track_object_get_track (tckobj) == track); + assert_equals_uint64 (ges_track_object_get_duration (tckobj), 10); + g_list_free_full (tckobjs, g_object_unref); + + /* Add a new layer and add objects to it */ + fail_unless ((layer1 = ges_timeline_append_layer (timeline)) != NULL); + fail_unless (layer != layer1); + assert_equals_int (ges_timeline_layer_get_priority (layer1), 1); + + fail_unless (ges_timeline_layer_add_object (layer1, obj1)); + fail_unless ((tckobjs = + ges_timeline_object_get_track_objects (obj1)) != NULL); + fail_unless ((tckobj1 = GES_TRACK_OBJECT (tckobjs->data)) != NULL); + fail_unless (ges_track_object_get_track (tckobj1) == track); + assert_equals_uint64 (ges_track_object_get_duration (tckobj1), 10); + g_list_free_full (tckobjs, g_object_unref); + + fail_unless (ges_timeline_layer_add_object (layer1, obj2)); + fail_unless ((tckobjs = + ges_timeline_object_get_track_objects (obj2)) != NULL); + fail_unless ((tckobj2 = GES_TRACK_OBJECT (tckobjs->data)) != NULL); + fail_unless (ges_track_object_get_track (tckobj2) == track); + assert_equals_uint64 (ges_track_object_get_duration (tckobj2), 60); + g_list_free_full (tckobjs, g_object_unref); + + /** + * Simple rippling obj to: 10 + * + * New timeline: + * ------------ + * + * inpoints 0------- + * | obj | + * time 10-------20 + * + * 0-------- 0----------- + * | obj1 | | obj2 | + * 20--------30 60--------120 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 10) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 10, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 60, 0, 60); + + + /* FIXME find a way to check that we are using the same MovingContext + * inside the GESTimeline */ + fail_unless (ges_timeline_object_edit (obj1, NULL, 3, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 40) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 10, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 40, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 80, 0, 60); + layer2 = ges_timeline_object_get_layer (obj1); + assert_equals_int (ges_timeline_layer_get_priority (layer2), 3); + /* obj2 should have moved layer too */ + fail_unless (ges_timeline_object_get_layer (obj2) == layer2); + /* We got 2 reference to the same object, unref them */ + g_object_unref (layer2); + g_object_unref (layer2); + + /** + * Rippling obj1 back to: 20 (getting to the exact same timeline as before + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, 1, GES_EDIT_MODE_RIPPLE, + GES_EDGE_NONE, 20) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 10, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 60, 0, 60); + layer2 = ges_timeline_object_get_layer (obj1); + assert_equals_int (ges_timeline_layer_get_priority (layer2), 1); + /* obj2 should have moved layer too */ + fail_unless (ges_timeline_object_get_layer (obj2) == layer2); + /* We got 2 reference to the same object, unref them */ + g_object_unref (layer2); + g_object_unref (layer2); + + /** + * Simple move obj to 27 and obj2 to 35 + * + * New timeline: + * ------------ + * + * inpoints 0------- + * | obj | + * time 27-------37 + * + * 0-------- 0----------- + * | obj1 | | obj2 | + * 20--------30 35---------95 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 27) == TRUE); + fail_unless (ges_timeline_object_edit (obj2, NULL, -1, GES_EDIT_MODE_NORMAL, + GES_EDGE_NONE, 35) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 27, 0, 10); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + + /** + * Simple trimming start obj to: 32 + * + * New timeline: + * ------------ + * + * 5------- + * layer 0: | obj | + * 32-------37 + * + * 0-------- 0----------- + * layer 1 | obj1 | | obj2 | + * 20--------30 35---------95 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 32) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 5); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + + /* Ripple end obj to 35 and move to layer 2 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 1: | obj1 | | obj2 | + * 20--------30 35---------95 + * + * 5------ + * layer 2: | obj | + * 32------35 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, 2, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 35) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 3); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + layer = ges_timeline_object_get_layer (obj); + assert_equals_int (ges_timeline_layer_get_priority (layer), 2); + g_object_unref (layer); + + /* Roll end obj to 50 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 1: | obj1 | | obj2 | + * 20--------30 50---------95 + * + * 5------ + * layer 2: | obj | + * 32------50 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, 2, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 50) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 18); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 50, 15, 45); + layer = ges_timeline_object_get_layer (obj); + assert_equals_int (ges_timeline_layer_get_priority (layer), 2); + g_object_unref (layer); + + /* Some more intensive roll testing */ + for (i = 0; i < 20; i++) { + gint32 random = g_random_int_range (35, 94); + guint64 tck3_inpoint = random - 35; + + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, random) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, random - 32); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, random, tck3_inpoint, 95 - random); + } + + /* Roll end obj back to 35 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 1: | obj1 | | obj2 | + * 20--------30 35---------95 + * + * 5------ + * layer 2: | obj | + * 32------35 + */ + fail_unless (ges_timeline_object_edit (obj, NULL, 2, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 35) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 3); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 35, 0, 60); + layer = ges_timeline_object_get_layer (obj); + assert_equals_int (ges_timeline_layer_get_priority (layer), 2); + g_object_unref (layer); + + /* Ripple obj end to 52 + * New timeline: + * ------------ + * + * 0-------- 0---------- + * layer 1: | obj1 | | obj2 | + * 20-------30 52---------112 + * + * 5------ + * layer 2: | obj | + * 32------52 + * + */ + /* Can not move to the first layer as obj2 should move to a layer with priority < 0 */ + fail_unless (ges_timeline_object_edit (obj, NULL, 0, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 52) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 32, 5, 20); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 52, 0, 60) + layer = ges_timeline_object_get_layer (obj); + assert_equals_int (ges_timeline_layer_get_priority (layer), 2); + g_object_unref (layer); + + + /* Little check that we have 4 layers in the timeline */ + layers = ges_timeline_get_layers (timeline); + assert_equals_int (g_list_length (layers), 4); + + /* Some refcount checkings */ + /* We have a reference to each layer in layers */ + for (tmp = layers; tmp; tmp = tmp->next) + ASSERT_OBJECT_REFCOUNT (layer, "Layer", 2); + g_list_free_full (layers, g_object_unref); + + /* We have 3 references: + * track + timeline + obj + */ + ASSERT_OBJECT_REFCOUNT (tckobj, "First tckobj", 3); + ASSERT_OBJECT_REFCOUNT (tckobj1, "Second tckobj", 3); + ASSERT_OBJECT_REFCOUNT (tckobj2, "Third tckobj", 3); + /* We have 1 ref: + * + layer */ + ASSERT_OBJECT_REFCOUNT (obj, "First tlobj", 1); + ASSERT_OBJECT_REFCOUNT (obj1, "Second tlobj", 1); + ASSERT_OBJECT_REFCOUNT (obj2, "Third tlobj", 1); + + /* Ripple obj end to 52 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 0: | obj1 | | obj2 | + * 20-------40 62----------112 + * + * 5------ + * layer 1: | obj | + * 42------60 + * + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, 0, GES_EDIT_MODE_RIPPLE, + GES_EDGE_END, 40) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 42, 5, 20); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 20); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Check that movement between layer has been done properly */ + layer1 = ges_timeline_object_get_layer (obj); + layer = ges_timeline_object_get_layer (obj1); + assert_equals_int (ges_timeline_layer_get_priority (layer1), 1); + assert_equals_int (ges_timeline_layer_get_priority (layer), 0); + fail_unless (ges_timeline_object_get_layer (obj2) == layer); + g_object_unref (layer1); + /* We have 2 references to @layer that we do not need anymore */ ; + g_object_unref (layer); + g_object_unref (layer); + + /* Trim obj start to 40 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 0: | obj1 | | obj2 | + * 20-------40 62---------112 + * + * 0------ + * layer 1: | obj | + * 40------62 + * + */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 40) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 40, 3, 22); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 20); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Roll obj end to 25 + * New timeline: + * ------------ + * + * 0-------- 0----------- + * layer 0: | obj1 | | obj2 | + * 20-------25 62---------112 + * + * 0------ + * layer 1: | obj | + * 25------62 + * + */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 25) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Make sure that not doing anything when not able to roll */ + fail_unless (ges_timeline_object_edit (obj, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 65) == TRUE); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 65) == TRUE, 0); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Snaping to edge, so no move */ + g_object_set (timeline, "snapping-distance", (guint64) 3, NULL); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 27) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /* Snaping to edge, so no move */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 27) == TRUE); + + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 5); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /** + * New timeline: + * ------------ + * 0----------- 0------------- + * inpoints 0-------|-- obj || obj2 | + * | obj1 25-|------- 62 -----------122 + * time 20----------30 + */ + g_object_set (timeline, "snapping-distance", (guint64) 0, NULL); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_END, 30) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 10); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + + /** + * New timeline + * ------------ + * 0---------- + * | obj | + * 25---------62 + * inpoints 0----------------------- 10-------- + * | obj1 || obj2 | + * time 20---------------------- 72 --------122 + */ + /* Rolling involves only neighbours that are currently snapping */ + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 62) == TRUE); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_END, 72) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 20, 0, 52); + CHECK_OBJECT_PROPS (tckobj2, 72, 10, 50); + + /* Test Snapping */ + /** + * 0---------- + * | obj | + * 25---------62 + * inpoints 5--------------- 10-------- + * | obj1 || obj2 | + * time 25------------- 72 --------122 + */ + g_object_set (timeline, "snapping-distance", (guint64) 4, NULL); + fail_unless (ges_timeline_object_edit (obj1, NULL, -1, GES_EDIT_MODE_TRIM, + GES_EDGE_START, 28) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 25, 5, 47); + CHECK_OBJECT_PROPS (tckobj2, 72, 10, 50); + + /** + * 0---------- + * | obj | + * 25---------62 + * inpoints 5---------- 0--------- + * | obj1 || obj2 | + * time 25-------- 62 --------122 + */ + fail_unless (ges_timeline_object_edit (obj2, NULL, -1, GES_EDIT_MODE_ROLL, + GES_EDGE_START, 59) == TRUE); + CHECK_OBJECT_PROPS (tckobj, 25, 0, 37); + CHECK_OBJECT_PROPS (tckobj1, 25, 5, 37); + CHECK_OBJECT_PROPS (tckobj2, 62, 0, 60); + +} + +GST_END_TEST; + +static Suite * +ges_suite (void) +{ + Suite *s = suite_create ("ges-timeline-edition"); + TCase *tc_chain = tcase_create ("timeline-edition"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_basic_timeline_edition); + tcase_add_test (tc_chain, test_snapping); + tcase_add_test (tc_chain, test_timeline_edition_mode); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = ges_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/ges/timelineobject.c b/tests/check/ges/timelineobject.c index 969de96686..629df936a8 100644 --- a/tests/check/ges/timelineobject.c +++ b/tests/check/ges/timelineobject.c @@ -195,6 +195,83 @@ GST_START_TEST (test_object_properties_unlocked) } GST_END_TEST; + +GST_START_TEST (test_split_object) +{ + GESTrack *track; + GESTrackObject *trackobject, *splittckobj; + GESTimelineObject *object, *splitobj; + GList *splittckobjs; + + ges_init (); + + track = ges_track_new (GES_TRACK_TYPE_CUSTOM, GST_CAPS_ANY); + fail_unless (track != NULL); + + object = + (GESTimelineObject *) ges_custom_timeline_source_new (my_fill_track_func, + NULL); + fail_unless (object != NULL); + + /* Set some properties */ + g_object_set (object, "start", (guint64) 42, "duration", (guint64) 50, + "in-point", (guint64) 12, NULL); + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 42); + assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (object), 50); + assert_equals_uint64 (GES_TIMELINE_OBJECT_INPOINT (object), 12); + + trackobject = ges_timeline_object_create_track_object (object, track); + ges_timeline_object_add_track_object (object, trackobject); + fail_unless (trackobject != NULL); + fail_unless (ges_track_object_set_track (trackobject, track)); + + /* Check that trackobject has the same properties */ + assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 42); + assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject), 50); + assert_equals_uint64 (GES_TRACK_OBJECT_INPOINT (trackobject), 12); + + /* And let's also check that it propagated correctly to GNonLin */ + gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 50, 12, + 50, 0, TRUE); + + splitobj = ges_timeline_object_split (object, 67); + fail_unless (GES_IS_TIMELINE_OBJECT (splitobj)); + + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 42); + assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (object), 25); + assert_equals_uint64 (GES_TIMELINE_OBJECT_INPOINT (object), 12); + + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (splitobj), 67); + assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (splitobj), 25); + assert_equals_uint64 (GES_TIMELINE_OBJECT_INPOINT (splitobj), 37); + + splittckobjs = ges_timeline_object_get_track_objects (splitobj); + fail_unless_equals_int (g_list_length (splittckobjs), 1); + + splittckobj = GES_TRACK_OBJECT (splittckobjs->data); + fail_unless (GES_IS_TRACK_OBJECT (splittckobj)); + assert_equals_uint64 (GES_TRACK_OBJECT_START (splittckobj), 67); + assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (splittckobj), 25); + assert_equals_uint64 (GES_TRACK_OBJECT_INPOINT (splittckobj), 37); + + fail_unless (splittckobj != trackobject); + fail_unless (splitobj != object); + + /* We own the only ref */ + ASSERT_OBJECT_REFCOUNT (splitobj, "splitobj", 1); + /* 1 ref for the TimelineObject, 1 ref for the Track and 1 in splittckobjs */ + ASSERT_OBJECT_REFCOUNT (splittckobj, "splittckobj", 3); + + g_object_unref (track); + g_object_unref (splitobj); + g_object_unref (object); + + ASSERT_OBJECT_REFCOUNT (splittckobj, "splittckobj", 1); + g_list_free_full (splittckobjs, g_object_unref); +} + +GST_END_TEST; + static Suite * ges_suite (void) { @@ -205,6 +282,7 @@ ges_suite (void) tcase_add_test (tc_chain, test_object_properties); tcase_add_test (tc_chain, test_object_properties_unlocked); + tcase_add_test (tc_chain, test_split_object); return s; }