From 9e18e03939836dd5ca07bd166ca27d58d97d841c Mon Sep 17 00:00:00 2001 From: Henry Wilkes Date: Wed, 20 May 2020 21:20:10 +0100 Subject: [PATCH] track-element: use out-point for updating control bindings The out-point, which is an internal time, is used instead of the duration for determining the control binding value at the end of the element. Also, allow the user to switch off the auto-clamping of control sources if they are not desired. And allow them to clamp specific control sources individually. Also, fix a lot of memory leaks related to control sources. In particular, releasing the extra ref gained by source in g_object_get (binding, "control-source", &source, NULL); Part-of: --- ges/ges-base-xml-formatter.c | 14 +- ges/ges-clip.c | 80 ++++-- ges/ges-internal.h | 3 + ges/ges-timeline-tree.c | 13 + ges/ges-track-element.c | 465 +++++++++++++++++++++++++---------- ges/ges-track-element.h | 10 + ges/ges-xml-formatter.c | 2 + tests/check/ges/clip.c | 299 +++++++++++++++++++++- tests/check/ges/project.c | 3 + 9 files changed, 737 insertions(+), 152 deletions(-) diff --git a/ges/ges-base-xml-formatter.c b/ges/ges-base-xml-formatter.c index 7b7338bd89..30d02c2e3b 100644 --- a/ges/ges-base-xml-formatter.c +++ b/ges/ges-base-xml-formatter.c @@ -1081,8 +1081,7 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self, if (priv->state != STATE_LOADING_CLIPS) { GST_DEBUG_OBJECT (self, "Not loading control bindings in %s state.", loading_state_name (priv->state)); - g_slist_free_full (timed_values, g_free); - return; + goto done; } if (track_id[0] != '-' && priv->current_clip) @@ -1092,23 +1091,28 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self, if (element == NULL) { GST_WARNING ("No current track element to which we can append a binding"); - return; + goto done; } if (!g_strcmp0 (source_type, "interpolation")) { GstControlSource *source; source = gst_interpolation_control_source_new (); + + /* add first before setting values to avoid clamping */ ges_track_element_set_control_source (element, source, property_name, binding_type); g_object_set (source, "mode", mode, NULL); - gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE (source), timed_values); - g_slist_free_full (timed_values, g_free); + + gst_object_unref (source); } else GST_WARNING ("This interpolation type is not supported\n"); + +done: + g_slist_free_full (timed_values, g_free); } void diff --git a/ges/ges-clip.c b/ges/ges-clip.c index 33f7c5fa6d..a29790da04 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -167,6 +167,7 @@ struct _GESClipPrivate GstClockTime duration_limit; gboolean prevent_duration_limit_update; + gboolean prevent_children_outpoint_update; gboolean allow_any_remove; @@ -446,6 +447,19 @@ _calculate_duration_limit (GESClip * self, GList * child_data) return limit; } +static void +_update_children_outpoints (GESClip * self) +{ + GList *tmp; + + if (self->priv->prevent_children_outpoint_update) + return; + + for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) { + ges_track_element_update_outpoint (tmp->data); + } +} + static void _update_duration_limit (GESClip * self) { @@ -864,6 +878,7 @@ _child_active_changed (GESClip * self, GESTrackElement * child) gboolean active = ges_track_element_is_active (child); gboolean is_core = _IS_CORE_CHILD (child); gboolean prev_prevent = self->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update; /* We want to ensure that each active non-core element has a * corresponding active core element in the same track */ @@ -872,6 +887,7 @@ _child_active_changed (GESClip * self, GESTrackElement * child) self->priv->setting_active = TRUE; self->priv->prevent_duration_limit_update = TRUE; + self->priv->prevent_children_outpoint_update = TRUE; /* If we are core, make all the non-core elements in-active * If we are non-core, make the core element active (should only be one) */ @@ -895,6 +911,7 @@ _child_active_changed (GESClip * self, GESTrackElement * child) self->priv->setting_active = FALSE; self->priv->prevent_duration_limit_update = prev_prevent; + self->priv->prevent_children_outpoint_update = prev_prevent_outpoint; } /**************************************************** @@ -1025,6 +1042,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child) GESTrackElement *core; gboolean active; gboolean prev_prevent = self->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update; if (self->priv->allow_any_track || _IS_CORE_CHILD (child) || !track) return; @@ -1050,6 +1068,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child) self->priv->setting_active = TRUE; self->priv->prevent_duration_limit_update = TRUE; + self->priv->prevent_children_outpoint_update = TRUE; if (!ges_track_element_set_active (child, FALSE)) GST_ERROR_OBJECT (self, "Failed to de-activate child %" GES_FORMAT, @@ -1057,6 +1076,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child) self->priv->setting_active = FALSE; self->priv->prevent_duration_limit_update = prev_prevent; + self->priv->prevent_children_outpoint_update = prev_prevent_outpoint; } } @@ -1066,29 +1086,36 @@ static void _child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec, GESClip * self) { - gboolean update = FALSE; + gboolean update_limit = FALSE; + gboolean update_outpoint = FALSE; const gchar *name = pspec->name; if (_IS_PROP ("track")) { - update = TRUE; + update_limit = TRUE; + update_outpoint = TRUE; _update_active_for_track (self, GES_TRACK_ELEMENT (child)); } else if (_IS_PROP ("active")) { - update = TRUE; + update_limit = TRUE; + update_outpoint = TRUE; _child_active_changed (self, GES_TRACK_ELEMENT (child)); } else if (_IS_PROP ("priority")) { - update = TRUE; + update_limit = TRUE; + update_outpoint = TRUE; _child_priority_changed (GES_CONTAINER (self), child); } else if (_IS_PROP ("in-point")) { - update = _child_inpoint_changed (self, child); + /* update outpoint already handled by the track element */ + update_limit = _child_inpoint_changed (self, child); } else if (_IS_PROP ("max-duration")) { - update = TRUE; + update_limit = TRUE; _child_max_duration_changed (GES_CONTAINER (self), child); } else if (_IS_PROP ("has-internal-source")) { _child_has_internal_source_changed (self, child); } - if (update) + if (update_limit) _update_duration_limit (self); + if (update_outpoint) + _update_children_outpoints (self); } /**************************************************** @@ -1140,6 +1167,7 @@ _child_time_property_changed_cb (GESTimelineElement * child, if (time_prop) { g_free (time_prop); _update_duration_limit (self); + _update_children_outpoints (self); } } @@ -1346,6 +1374,7 @@ _set_priority (GESTimelineElement * element, guint32 priority) GList *tmp; guint32 min_prio, max_prio, min_child_prio = G_MAXUINT32; gboolean prev_prevent = priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update; GESContainer *container = GES_CONTAINER (element); for (tmp = container->children; tmp; tmp = g_list_next (tmp)) @@ -1358,6 +1387,7 @@ _set_priority (GESTimelineElement * element, guint32 priority) /* offsets will remain constant for the children */ priv->prevent_resort = TRUE; priv->prevent_duration_limit_update = TRUE; + priv->prevent_children_outpoint_update = TRUE; priv->setting_priority = TRUE; for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { GESTimelineElement *child = tmp->data; @@ -1378,6 +1408,7 @@ _set_priority (GESTimelineElement * element, guint32 priority) priv->prevent_resort = FALSE; priv->setting_priority = FALSE; priv->prevent_duration_limit_update = prev_prevent; + priv->prevent_children_outpoint_update = prev_prevent_outpoint; return TRUE; } @@ -1480,6 +1511,7 @@ _add_child (GESContainer * container, GESTimelineElement * element) GESClipPrivate *priv = self->priv; GESAsset *asset, *creator_asset; gboolean prev_prevent = priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update; GList *tmp; GError *error = NULL; @@ -1664,6 +1696,7 @@ _add_child (GESContainer * container, GESTimelineElement * element) priv->prevent_resort = TRUE; priv->setting_priority = TRUE; priv->prevent_duration_limit_update = TRUE; + priv->prevent_children_outpoint_update = TRUE; /* increase the priority of anything with a lower priority */ for (tmp = container->children; tmp; tmp = tmp->next) { @@ -1676,6 +1709,7 @@ _add_child (GESContainer * container, GESTimelineElement * element) priv->prevent_resort = FALSE; priv->setting_priority = FALSE; priv->prevent_duration_limit_update = prev_prevent; + priv->prevent_children_outpoint_update = prev_prevent_outpoint; /* no need to call _ges_container_sort_children (container) since * there is no change to the ordering yet (this happens after the * child is actually added) */ @@ -1776,12 +1810,14 @@ _remove_child (GESContainer * container, GESTimelineElement * element) if (_IS_TOP_EFFECT (element)) { GList *tmp; gboolean prev_prevent = priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update; GST_DEBUG_OBJECT (container, "Resyncing effects priority."); /* changing priorities, so preventing a re-sort */ priv->prevent_resort = TRUE; priv->setting_priority = TRUE; priv->prevent_duration_limit_update = TRUE; + priv->prevent_children_outpoint_update = TRUE; for (tmp = container->children; tmp; tmp = tmp->next) { guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data); if (sibling_prio > element->priority) @@ -1792,6 +1828,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element) priv->prevent_resort = FALSE; priv->setting_priority = FALSE; priv->prevent_duration_limit_update = prev_prevent; + priv->prevent_children_outpoint_update = prev_prevent_outpoint; /* no need to re-sort the children since the rest keep the same * relative priorities */ /* height may have changed */ @@ -1817,6 +1854,7 @@ _child_added (GESContainer * container, GESTimelineElement * element) _update_max_duration (container); _update_duration_limit (self); + _update_children_outpoints (self); } static void @@ -1835,6 +1873,8 @@ _child_removed (GESContainer * container, GESTimelineElement * element) _update_max_duration (container); _update_duration_limit (self); + _update_children_outpoints (self); + ges_track_element_update_outpoint (GES_TRACK_ELEMENT (element)); } static void @@ -1862,6 +1902,10 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip, GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip); gboolean prev_prevent_from = from_clip->priv->prevent_duration_limit_update; gboolean prev_prevent_to = to_clip->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint_from = + from_clip->priv->prevent_children_outpoint_update; + gboolean prev_prevent_outpoint_to = + to_clip->priv->prevent_children_outpoint_update; /* We need to bump the refcount to avoid the object to be destroyed */ gst_object_ref (child); @@ -1871,6 +1915,8 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip, from_clip->priv->prevent_duration_limit_update = TRUE; to_clip->priv->prevent_duration_limit_update = TRUE; + from_clip->priv->prevent_children_outpoint_update = TRUE; + to_clip->priv->prevent_children_outpoint_update = TRUE; from_clip->priv->allow_any_remove = TRUE; ges_container_remove (GES_CONTAINER (from_clip), @@ -1887,6 +1933,9 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip, from_clip->priv->prevent_duration_limit_update = prev_prevent_from; to_clip->priv->prevent_duration_limit_update = prev_prevent_to; + from_clip->priv->prevent_children_outpoint_update = + prev_prevent_outpoint_from; + to_clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint_to; gst_object_unref (child); } @@ -2115,12 +2164,14 @@ ges_clip_empty_from_track (GESClip * clip, GESTrack * track) { GList *tmp; gboolean prev_prevent = clip->priv->prevent_duration_limit_update; + gboolean prev_prevent_outpoint = clip->priv->prevent_children_outpoint_update; if (track == NULL) return; /* allow us to remove in any order */ clip->priv->allow_any_track = TRUE; clip->priv->prevent_duration_limit_update = TRUE; + clip->priv->prevent_children_outpoint_update = TRUE; for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) { GESTrackElement *child = tmp->data; @@ -2132,7 +2183,9 @@ ges_clip_empty_from_track (GESClip * clip, GESTrack * track) } clip->priv->allow_any_track = FALSE; clip->priv->prevent_duration_limit_update = prev_prevent; + clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint; _update_duration_limit (clip); + _update_children_outpoints (clip); } static GESTrackElement * @@ -3193,8 +3246,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) GList *tmp, *transitions = NULL; GESClip *new_object; gboolean no_core = FALSE; - GstClockTime start, inpoint, duration, old_duration, new_duration, - new_inpoint; + GstClockTime start, duration, old_duration, new_duration, new_inpoint; GESTimelineElement *element; GESTimeline *timeline; GHashTable *track_for_copy; @@ -3210,7 +3262,6 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) duration = element->duration; start = element->start; - inpoint = element->inpoint; if (position >= start + duration || position <= start) { GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT @@ -3259,6 +3310,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) /* Create the new Clip */ new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE)); new_object->priv->prevent_duration_limit_update = TRUE; + new_object->priv->prevent_children_outpoint_update = TRUE; GST_DEBUG_OBJECT (new_object, "New 'splitted' clip"); /* Set new timing properties on the Clip */ @@ -3293,11 +3345,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) GESTrack *track = ges_track_element_get_track (orig); GESAutoTransition *trans; - /* FIXME: is position - start + inpoint always the correct splitting - * point for control bindings? What coordinate system are control - * bindings given in? */ - copy = ges_clip_copy_track_element_into (new_object, orig, - position - start + inpoint); + copy = ges_clip_copy_track_element_into (new_object, orig, new_inpoint); if (!copy) continue; @@ -3347,7 +3395,9 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) g_list_free_full (transitions, gst_object_unref); new_object->priv->prevent_duration_limit_update = FALSE; + new_object->priv->prevent_children_outpoint_update = FALSE; _update_duration_limit (new_object); + _update_children_outpoints (new_object); return new_object; } diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 3e8d95040c..0241877685 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -454,6 +454,9 @@ G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element, GESTrackElement *new_element, guint64 position); +G_GNUC_INTERNAL void ges_track_element_freeze_control_sources (GESTrackElement * object, + gboolean freeze); +G_GNUC_INTERNAL void ges_track_element_update_outpoint (GESTrackElement * self); G_GNUC_INTERNAL void ges_track_element_set_creator_asset (GESTrackElement * self, diff --git a/ges/ges-timeline-tree.c b/ges/ges-timeline-tree.c index 67a99d70a9..09f405a52f 100644 --- a/ges/ges-timeline-tree.c +++ b/ges/ges-timeline-tree.c @@ -1861,6 +1861,12 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits) /* freeze the auto-transitions whilst we edit */ ges_timeline_freeze_auto_transitions (root->data, TRUE); + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GES_IS_TRACK_ELEMENT (key)) + ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), TRUE); + } + g_hash_table_iter_init (&iter, edits); while (g_hash_table_iter_next (&iter, &key, &value)) { GESTimelineElement *element = key; @@ -1868,6 +1874,13 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits) if (!perform_element_edit (element, edit_data)) no_errors = FALSE; } + + g_hash_table_iter_init (&iter, edits); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GES_IS_TRACK_ELEMENT (key)) + ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), FALSE); + } + /* allow the transitions to update if they can */ ges_timeline_freeze_auto_transitions (root->data, FALSE); diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index ac4f2386d6..691a829bc7 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -40,6 +40,16 @@ * tracks and take responsibility for updating them. The only track * elements that are not automatically created by clips, but a user is * likely to want to create, are #GESEffect-s. + * + * ## Control Bindings for Children Properties + * + * You can set up control bindings for a track element child property + * using ges_track_element_set_control_source(). A + * #GstTimedValueControlSource should specify the timed values using the + * internal source coordinates (see #GESTimelineElement). By default, + * these will be updated to lie between the #GESTimelineElement:in-point + * and out-point of the element. This can be switched off by setting + * #GESTrackElement:auto-clamp-control-sources to %FALSE. */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -62,13 +72,15 @@ struct _GESTrackElementPrivate gboolean has_internal_source_forbidden; gboolean has_internal_source; - gboolean locked; /* If TRUE, then moves in sync with its controlling - * GESClip */ gboolean layer_active; GHashTable *bindings_hashtable; /* We need this if we want to be able to serialize and deserialize keyframes */ GESAsset *creator_asset; + + GstClockTime outpoint; + gboolean freeze_control_sources; + gboolean auto_clamp_control_sources; }; enum @@ -78,6 +90,7 @@ enum PROP_TRACK_TYPE, PROP_TRACK, PROP_HAS_INTERNAL_SOURCE, + PROP_AUTO_CLAMP_CONTROL_SOURCES, PROP_LAST }; @@ -120,10 +133,6 @@ static gboolean _set_max_duration (GESTimelineElement * element, static gboolean _set_priority (GESTimelineElement * element, guint32 priority); GESTrackType _get_track_types (GESTimelineElement * object); -static void -_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint, - GstClockTime duration); - static gboolean _lookup_child (GESTrackElement * object, const gchar * prop_name, GstElement ** element, GParamSpec ** pspec) @@ -197,6 +206,10 @@ ges_track_element_get_property (GObject * object, guint property_id, g_value_set_boolean (value, ges_track_element_has_internal_source (track_element)); break; + case PROP_AUTO_CLAMP_CONTROL_SOURCES: + g_value_set_boolean (value, + ges_track_element_get_auto_clamp_control_sources (track_element)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -220,6 +233,10 @@ ges_track_element_set_property (GObject * object, guint property_id, ges_track_element_set_has_internal_source (track_element, g_value_get_boolean (value)); break; + case PROP_AUTO_CLAMP_CONTROL_SOURCES: + ges_track_element_set_auto_clamp_control_sources (track_element, + g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -412,6 +429,28 @@ ges_track_element_class_init (GESTrackElementClass * klass) g_object_class_install_property (object_class, PROP_HAS_INTERNAL_SOURCE, properties[PROP_HAS_INTERNAL_SOURCE]); + /** + * GESTrackElement:auto-clamp-control-sources: + * + * Whether the control sources on the element (see + * ges_track_element_set_control_source()) will be automatically + * updated whenever the #GESTimelineElement:in-point or out-point of the + * element change in value. + * + * See ges_track_element_clamp_control_source() for how this is done + * per control source. + * + * Default value: %TRUE + */ + properties[PROP_AUTO_CLAMP_CONTROL_SOURCES] = + g_param_spec_boolean ("auto-clamp-control-sources", + "Auto-Clamp Control Sources", "Whether to automatically update the " + "control sources with a change in in-point or out-point", TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_property (object_class, + PROP_AUTO_CLAMP_CONTROL_SOURCES, + properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]); + /** * GESTrackElement::control-binding-added: * @track_element: A #GESTrackElement @@ -483,6 +522,9 @@ ges_track_element_init (GESTrackElement * self) * because it calls g_object_new_with_properties. */ self->priv->has_internal_source = TRUE; + + self->priv->outpoint = GST_CLOCK_TIME_NONE; + self->priv->auto_clamp_control_sources = TRUE; } static gfloat @@ -504,6 +546,7 @@ interpolate_values_for_position (GstTimedValue * first_value, diff = second_value->value - first_value->value; interval = second_value->timestamp - first_value->timestamp; + /* FIXME: properly support non-linear timed control sources */ if (position > first_value->timestamp) value_at_pos = first_value->value + ((float) (position - @@ -520,98 +563,116 @@ interpolate_values_for_position (GstTimedValue * first_value, } static void -_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint, - GstClockTime duration) +_update_control_source (GstTimedValueControlSource * source, gboolean absolute, + GstClockTime inpoint, GstClockTime outpoint) { - GParamSpec **specs; - guint n, n_specs; - GstControlBinding *binding; - GstTimedValueControlSource *source; - GESTrackElement *self = GES_TRACK_ELEMENT (element); + GList *values, *tmp; + GstTimedValue *last, *first, *prev = NULL, *next = NULL; + gfloat value_at_pos; - specs = ges_track_element_list_children_properties (self, &n_specs); + if (inpoint == outpoint) { + gst_timed_value_control_source_unset_all (source); + return; + } - for (n = 0; n < n_specs; ++n) { - GList *values, *tmp; - gboolean absolute; - GstTimedValue *last, *first, *prev = NULL, *next = NULL; - gfloat value_at_pos; + values = gst_timed_value_control_source_get_all (source); - binding = ges_track_element_get_control_binding (self, specs[n]->name); + if (g_list_length (values) == 0) + return; - if (!binding) - continue; + first = values->data; - g_object_get (binding, "control_source", &source, NULL); + for (tmp = values->next; tmp; tmp = tmp->next) { + next = tmp->data; - g_object_get (binding, "absolute", &absolute, NULL); - if (duration == 0) { - gst_timed_value_control_source_unset_all (GST_TIMED_VALUE_CONTROL_SOURCE - (source)); - continue; + if (next->timestamp == inpoint) { + /* just leave this value in place */ + first = NULL; + break; } + if (next->timestamp > inpoint) + break; + } + g_list_free (values); - values = - gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE - (source)); - - if (g_list_length (values) == 0) - continue; - - first = values->data; - - for (tmp = values->next; tmp; tmp = tmp->next) { - next = tmp->data; - - if (next->timestamp > inpoint) - break; - } - g_list_free (values); - + if (first) { value_at_pos = interpolate_values_for_position (first, next, inpoint, absolute); gst_timed_value_control_source_unset (source, first->timestamp); gst_timed_value_control_source_set (source, inpoint, value_at_pos); - - values = - gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE - (source)); - - if (duration != GST_CLOCK_TIME_NONE) { - last = g_list_last (values)->data; - - for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) { - prev = tmp->data; - - if (prev->timestamp < duration + inpoint) - break; - } - g_list_free (values); - - value_at_pos = - interpolate_values_for_position (prev, last, duration + inpoint, - absolute); - - gst_timed_value_control_source_unset (source, last->timestamp); - gst_timed_value_control_source_set (source, duration + inpoint, - value_at_pos); - values = - gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE - (source)); - } - - for (tmp = values; tmp; tmp = tmp->next) { - GstTimedValue *value = tmp->data; - if (value->timestamp < inpoint) - gst_timed_value_control_source_unset (source, value->timestamp); - else if (duration != GST_CLOCK_TIME_NONE - && value->timestamp > duration + inpoint) - gst_timed_value_control_source_unset (source, value->timestamp); - } - g_list_free (values); } - g_free (specs); + if (GST_CLOCK_TIME_IS_VALID (outpoint)) { + values = gst_timed_value_control_source_get_all (source); + + last = g_list_last (values)->data; + + for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) { + prev = tmp->data; + + if (prev->timestamp == outpoint) { + /* leave this value in place */ + last = NULL; + break; + } + if (prev->timestamp < outpoint) + break; + } + g_list_free (values); + + if (last) { + value_at_pos = + interpolate_values_for_position (prev, last, outpoint, absolute); + + gst_timed_value_control_source_unset (source, last->timestamp); + gst_timed_value_control_source_set (source, outpoint, value_at_pos); + } + } + + values = gst_timed_value_control_source_get_all (source); + + for (tmp = values; tmp; tmp = tmp->next) { + GstTimedValue *value = tmp->data; + if (value->timestamp < inpoint) + gst_timed_value_control_source_unset (source, value->timestamp); + else if (GST_CLOCK_TIME_IS_VALID (outpoint) && value->timestamp > outpoint) + gst_timed_value_control_source_unset (source, value->timestamp); + } + g_list_free (values); +} + +static void +_update_control_bindings (GESTrackElement * self, GstClockTime inpoint, + GstClockTime outpoint) +{ + gchar *name; + GstControlBinding *binding; + GstControlSource *source; + gboolean absolute; + gpointer value, key; + GHashTableIter iter; + + if (self->priv->freeze_control_sources) + return; + + g_hash_table_iter_init (&iter, self->priv->bindings_hashtable); + while (g_hash_table_iter_next (&iter, &key, &value)) { + binding = value; + name = key; + g_object_get (binding, "control-source", &source, "absolute", &absolute, + NULL); + + if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { + GST_INFO_OBJECT (self, "Not updating %s because it does not have a" + " timed value control source", name); + gst_object_unref (source); + continue; + } + + _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute, + inpoint, outpoint); + gst_object_unref (source); + } } static gboolean @@ -626,6 +687,45 @@ _set_start (GESTimelineElement * element, GstClockTime start) return TRUE; } +static void +ges_track_element_update_outpoint_full (GESTrackElement * self, + GstClockTime inpoint, GstClockTime duration) +{ + GstClockTime current_inpoint = _INPOINT (self); + gboolean increase = (inpoint > current_inpoint); + GstClockTime outpoint = GST_CLOCK_TIME_NONE; + GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (self); + GESTrackElementPrivate *priv = self->priv; + + if (GES_IS_CLIP (parent) && ges_track_element_get_track (self) + && ges_track_element_is_active (self) + && GST_CLOCK_TIME_IS_VALID (duration)) { + outpoint = + ges_clip_get_internal_time_from_timeline_time (GES_CLIP (parent), self, + _START (self) + duration, NULL); + + if (!GST_CLOCK_TIME_IS_VALID (outpoint)) + GST_ERROR_OBJECT (self, "Got an invalid out-point"); + else if (increase) + outpoint += (inpoint - current_inpoint); + else + outpoint -= (current_inpoint - inpoint); + } + + if ((priv->outpoint != outpoint || inpoint != current_inpoint) + && self->priv->auto_clamp_control_sources) + _update_control_bindings (self, inpoint, outpoint); + + priv->outpoint = outpoint; +} + +void +ges_track_element_update_outpoint (GESTrackElement * self) +{ + GESTimelineElement *el = GES_TIMELINE_ELEMENT (self); + ges_track_element_update_outpoint_full (self, el->inpoint, el->duration); +} + static gboolean _set_inpoint (GESTimelineElement * element, GstClockTime inpoint) { @@ -652,7 +752,8 @@ _set_inpoint (GESTimelineElement * element, GstClockTime inpoint) } g_object_set (object->priv->nleobject, "inpoint", inpoint, NULL); - _update_control_bindings (element, inpoint, GST_CLOCK_TIME_NONE); + + ges_track_element_update_outpoint_full (object, inpoint, element->duration); return TRUE; } @@ -663,16 +764,11 @@ _set_duration (GESTimelineElement * element, GstClockTime duration) GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTrackElementPrivate *priv = object->priv; - g_return_val_if_fail (object->priv->nleobject, FALSE); - - if (GST_CLOCK_TIME_IS_VALID (_MAXDURATION (element)) && - duration > _INPOINT (object) + _MAXDURATION (element)) - duration = _MAXDURATION (element) - _INPOINT (object); + g_return_val_if_fail (priv->nleobject, FALSE); g_object_set (priv->nleobject, "duration", duration, NULL); - _update_control_bindings (element, ges_timeline_element_get_inpoint (element), - duration); + ges_track_element_update_outpoint_full (object, element->inpoint, duration); return TRUE; } @@ -1626,15 +1722,16 @@ ges_track_element_copy_bindings (GESTrackElement * element, if (!binding) continue; - g_object_get (binding, "control_source", &source, NULL); + g_object_get (binding, "control-source", &source, "absolute", &absolute, + NULL); if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { GST_FIXME_OBJECT (element, "Implement support for control source type: %s", G_OBJECT_TYPE_NAME (source)); + gst_object_unref (source); continue; } - g_object_get (binding, "absolute", &absolute, NULL); g_object_get (source, "mode", &mode, NULL); new_source = @@ -1656,6 +1753,9 @@ ges_track_element_copy_bindings (GESTrackElement * element, else ges_track_element_set_control_source (new_element, GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct"); + + gst_object_unref (source); + gst_object_unref (new_source); } g_free (specs); @@ -1765,9 +1865,9 @@ ges_track_element_set_control_source (GESTrackElement * object, GstControlSource * source, const gchar * property_name, const gchar * binding_type) { + gboolean ret = FALSE; GESTrackElementPrivate *priv; GstElement *element; - GParamSpec *pspec; GstControlBinding *binding; gboolean direct, direct_absolute; @@ -1780,7 +1880,7 @@ ges_track_element_set_control_source (GESTrackElement * object, return FALSE; } - if (!ges_track_element_lookup_child (object, property_name, &element, &pspec)) { + if (!ges_track_element_lookup_child (object, property_name, &element, NULL)) { GST_WARNING ("You need to provide a valid and controllable property name"); return FALSE; } @@ -1789,39 +1889,55 @@ ges_track_element_set_control_source (GESTrackElement * object, direct = !g_strcmp0 (binding_type, "direct"); direct_absolute = !g_strcmp0 (binding_type, "direct-absolute"); - if (direct || direct_absolute) { - /* First remove existing binding */ - if (ges_track_element_remove_control_binding (object, property_name)) { - GST_LOG ("Removed old binding for property %s", property_name); - } - - if (direct_absolute) - binding = - gst_direct_control_binding_new_absolute (GST_OBJECT (element), - property_name, source); - else - binding = - gst_direct_control_binding_new (GST_OBJECT (element), property_name, - source); - - gst_object_add_control_binding (GST_OBJECT (element), binding); - /* FIXME: maybe we should force the - * "ChildTypeName:property-name" - * format convention for child property names in bindings_hashtable. - * Currently the table may also contain - * "property-name" - * as keys. - */ - g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name), - binding); - g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED], - 0, binding); - return TRUE; + if (!direct && !direct_absolute) { + GST_WARNING_OBJECT (object, "Binding type must be in " + "[direct, direct-absolute]"); + goto done; } - GST_WARNING ("Binding type must be in [direct]"); + /* First remove existing binding */ + if (ges_track_element_remove_control_binding (object, property_name)) + GST_LOG_OBJECT (object, "Removed old binding for property %s", + property_name); - return FALSE; + if (direct_absolute) + binding = gst_direct_control_binding_new_absolute (GST_OBJECT (element), + property_name, source); + else + binding = gst_direct_control_binding_new (GST_OBJECT (element), + property_name, source); + + gst_object_add_control_binding (GST_OBJECT (element), binding); + /* FIXME: maybe we should force the + * "ChildTypeName:property-name" + * format convention for child property names in bindings_hashtable. + * Currently the table may also contain + * "property-name" + * as keys. + */ + g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name), + binding); + + if (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source) + && priv->auto_clamp_control_sources) { + /* Make sure we have the control source used by the binding */ + g_object_get (binding, "control-source", &source, NULL); + + _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), + direct_absolute, _INPOINT (object), priv->outpoint); + + gst_object_unref (source); + } + + g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED], + 0, binding); + + ret = TRUE; + +done: + gst_object_unref (element); + + return ret; } /** @@ -1857,6 +1973,105 @@ ges_track_element_get_control_binding (GESTrackElement * object, return binding; } +/** + * ges_track_element_clamp_control_source: + * @object: A #GESTrackElement + * @property_name: The name of the child property to clamp the control + * source of + * + * Clamp the #GstTimedValueControlSource for the specified child property + * to lie between the #GESTimelineElement:in-point and out-point of the + * element. The out-point is the #GES_TIMELINE_ELEMENT_END of the element + * translated from the timeline coordinates to the internal source + * coordinates of the element. + * + * If the property does not have a #GstTimedValueControlSource set by + * ges_track_element_set_control_source(), nothing happens. Otherwise, if + * a timed value for the control source lies before the in-point of the + * element, or after its out-point, then it will be removed. At the + * in-point and out-point times, a new interpolated value will be placed. + */ +void +ges_track_element_clamp_control_source (GESTrackElement * object, + const gchar * property_name) +{ + GstControlBinding *binding; + GstControlSource *source; + gboolean absolute; + + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + binding = ges_track_element_get_control_binding (object, property_name); + + if (!binding) + return; + + g_object_get (binding, "control-source", &source, "absolute", &absolute, + NULL); + + if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) { + gst_object_unref (source); + return; + } + + _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute, + _INPOINT (object), object->priv->outpoint); + gst_object_unref (source); +} + +/** + * ges_track_element_set_auto_clamp_control_sources: + * @object: A #GESTrackElement + * @auto_clamp: Whether to automatically clamp the control sources for the + * child properties of @object + * + * Sets #GESTrackElement:auto-clamp-control-sources. If set to %TRUE, this + * will immediately clamp all the control sources. + */ +void +ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object, + gboolean auto_clamp) +{ + g_return_if_fail (GES_IS_TRACK_ELEMENT (object)); + + if (auto_clamp == object->priv->auto_clamp_control_sources) + return; + + object->priv->auto_clamp_control_sources = auto_clamp; + if (auto_clamp) + _update_control_bindings (object, _INPOINT (object), + object->priv->outpoint); + + g_object_notify_by_pspec (G_OBJECT (object), + properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]); +} + +/** + * ges_track_element_get_auto_clamp_control_sources: + * @object: A #GESTrackElement + * + * Gets #GESTrackElement:auto-clamp-control-sources. + * + * Returns: Whether the control sources for the child properties of + * @object are automatically clamped. + */ +gboolean +ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object) +{ + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + + return object->priv->auto_clamp_control_sources; +} + +void +ges_track_element_freeze_control_sources (GESTrackElement * object, + gboolean freeze) +{ + object->priv->freeze_control_sources = freeze; + if (!freeze && object->priv->auto_clamp_control_sources) + _update_control_bindings (object, _INPOINT (object), + object->priv->outpoint); +} /** * ges_track_element_is_core: diff --git a/ges/ges-track-element.h b/ges/ges-track-element.h index ccf797ebf9..4e3e2d8e94 100644 --- a/ges/ges-track-element.h +++ b/ges/ges-track-element.h @@ -161,6 +161,16 @@ ges_track_element_set_control_source (GESTrackElement *object, const gchar *property_name, const gchar *binding_type); +GES_API void +ges_track_element_clamp_control_source (GESTrackElement * object, + const gchar * property_name); + +GES_API void +ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object, + gboolean auto_clamp); +GES_API gboolean +ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object); + GES_API GstControlBinding * ges_track_element_get_control_binding (GESTrackElement *object, const gchar *property_name); diff --git a/ges/ges-xml-formatter.c b/ges/ges-xml-formatter.c index b44a176ef0..6082640d25 100644 --- a/ges/ges-xml-formatter.c +++ b/ges/ges-xml-formatter.c @@ -1511,6 +1511,8 @@ _save_keyframes (GString * str, GESTrackElement * trackelement, gint index, append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth); } else GST_DEBUG ("control source not in [interpolation]"); + + gst_object_unref (source); } else GST_DEBUG ("Binding type not in [direct, direct-absolute]"); } diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index 156e1f9ff6..9e034b164a 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -203,7 +203,10 @@ GST_START_TEST (test_split_direct_bindings) CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 5 * GST_SECOND); check_layer (clip, 0); + gst_object_unref (timeline); + gst_object_unref (source); + gst_object_unref (splitsource); ges_deinit (); } @@ -303,6 +306,9 @@ GST_START_TEST (test_split_direct_absolute_bindings) check_layer (clip, 0); gst_object_unref (timeline); + gst_object_unref (source); + gst_object_unref (splitsource); + ges_deinit (); } @@ -4573,11 +4579,12 @@ _new_timed_value (GstClockTime time, gdouble val) GstControlBinding *binding = ges_track_element_get_control_binding ( \ GES_TRACK_ELEMENT (element), prop_name); \ fail_unless (binding, "No control binding found for %s on %s", \ - prop_name, element->name); \ + prop_name, GES_TIMELINE_ELEMENT_NAME (element)); \ g_object_get (G_OBJECT (binding), "control-source", &source, \ "object", &found_object, NULL); \ \ - fail_unless (found_object == child); \ + if (child) \ + fail_unless (found_object == child); \ g_object_unref (found_object); \ \ fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)); \ @@ -4587,17 +4594,19 @@ _new_timed_value (GstClockTime time, gdouble val) for (i = 0, tmp1 = timed_vals, tmp2 = found_timed_vals; tmp1 && tmp2; \ tmp1 = tmp1->next, tmp2 = tmp2->next, i++) { \ GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \ - fail_unless (val1->timestamp == val2->timestamp && \ - val1->value == val2->value, "The %ith timed value (%lu: %g) " \ - "does not match the found timed value (%lu: %g)", \ - i, val1->timestamp, val1->value, val2->timestamp, val2->value); \ + gdouble diff = (val1->value > val2->value) ? \ + val1->value - val2->value : val2->value - val1->value; \ + fail_unless (val1->timestamp == val2->timestamp && diff < 0.0001, \ + "The %ith timed value (%lu: %g) does not match the found timed " \ + "value (%lu: %g)", i, val1->timestamp, val1->value, \ + val2->timestamp, val2->value); \ } \ fail_unless (tmp1 == NULL, "Found too few timed values"); \ fail_unless (tmp2 == NULL, "Found too many timed values"); \ \ g_list_free (found_timed_vals); \ g_object_get (G_OBJECT (source), "mode", &found_mode, NULL); \ - fail_unless (found_mode == GST_INTERPOLATION_MODE_CUBIC); \ + fail_unless (found_mode == mode); \ g_object_unref (source); \ } @@ -4641,6 +4650,8 @@ GST_START_TEST (test_copy_paste_children_properties) /* find the track element where the child property comes from */ fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop)); _assert_int_val_child_prop (track_el, val, 30, prop, "posx"); + ges_track_element_set_auto_clamp_control_sources (GES_TRACK_ELEMENT + (track_el), FALSE); /* set a control binding */ timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5)); @@ -4709,6 +4720,279 @@ GST_START_TEST (test_copy_paste_children_properties) GST_END_TEST; +#define _THREE_TIMED_VALS(timed_vals, tm1, val1, tm2, val2, tm3, val3) \ + if (timed_vals) \ + g_slist_free_full (timed_vals, g_free); \ + timed_vals = g_slist_prepend (NULL, _new_timed_value (tm3, val3)); \ + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm2, val2)); \ + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1)); + +#define _TWO_TIMED_VALS(timed_vals, tm1, val1, tm2, val2) \ + if (timed_vals) \ + g_slist_free_full (timed_vals, g_free); \ + timed_vals = g_slist_prepend (NULL, _new_timed_value (tm2, val2)); \ + timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1)); + +#define _assert_control_source(obj, prop, vals) \ + _assert_binding (obj, prop, NULL, vals, GST_INTERPOLATION_MODE_LINEAR); + +GST_START_TEST (test_children_property_bindings_with_rate_effects) +{ + GESTimeline *timeline; + GESTrack *track; + GESLayer *layer; + GESClip *clip; + GESTrackElement *video_source, *rate0, *rate1, *overlay; + GstControlSource *ctrl_source; + GSList *video_source_vals = NULL, *overlay_vals = NULL; + GValue value = G_VALUE_INIT; + GstControlBinding *binding; + + ges_init (); + + g_value_init (&value, G_TYPE_DOUBLE); + + timeline = ges_timeline_new (); + track = GES_TRACK (ges_video_track_new ()); + fail_unless (ges_timeline_add_track (timeline, track)); + + layer = ges_timeline_append_layer (timeline); + + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_duration (clip, 4); + assert_set_start (clip, 20); + assert_set_inpoint (clip, 3); + + fail_unless (ges_layer_add_clip (layer, clip)); + + video_source = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE); + fail_unless (video_source); + gst_object_unref (video_source); + + rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=0.5")); + rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=4.0")); + overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay, TRUE); + assert_set_inpoint (overlay, 9); + + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), -1, + NULL)); + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (overlay), 0, + NULL)); + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate1), 0, + NULL)); + + fail_unless (ges_track_element_get_auto_clamp_control_sources (video_source)); + fail_unless (ges_track_element_get_auto_clamp_control_sources (overlay)); + + /* source's alpha property */ + _THREE_TIMED_VALS (video_source_vals, 1, 0.7, 7, 1.0, 15, 0.2); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_LINEAR, NULL); + fail_unless (gst_timed_value_control_source_set_from_list + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), video_source_vals)); + + fail_unless (ges_track_element_set_control_source (video_source, ctrl_source, + "alpha", "direct")); + gst_object_unref (ctrl_source); + + /* values have been clamped between its in-point:3 and its + * out-point:11 (4ns in timeline is 8ns in source) */ + _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* overlay's xpos property */ + _THREE_TIMED_VALS (overlay_vals, 9, 12, 17, 16, 25, 8); + + ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ()); + g_object_set (G_OBJECT (ctrl_source), "mode", + GST_INTERPOLATION_MODE_LINEAR, NULL); + fail_unless (gst_timed_value_control_source_set_from_list + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), overlay_vals)); + + fail_unless (ges_track_element_set_control_source (overlay, ctrl_source, + "xpos", "direct-absolute")); + gst_object_unref (ctrl_source); + + /* unchanged since values are at the edges already + * in-point:9 out-point:25 (4ns in timeline is 16ns in source) */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* setting the in-point changes the in-point and out-point */ + /* increase in-point */ + assert_set_inpoint (video_source, 5); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* decrease in-point */ + assert_set_inpoint (overlay, 7); + + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* when trimming start, out-point should stay the same */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 19, NULL)); + + /* in-point of video_source now 3 */ + _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* in-point of video_source now 3 */ + _THREE_TIMED_VALS (overlay_vals, 3, 9, 17, 16, 23, 10); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* trim forwards */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 20, NULL)); + + /* in-point of video_source now 5 again */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* in-point of overlay now 7 again */ + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* trim end */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 25, NULL)); + + /* out-point of video_source now 15 */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 15, 0.2); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* out-point of overlay now 27 */ + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 27, 6); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* trim backwards */ + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 23, NULL)); + + /* out-point of video_source now 11 */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* in-point of overlay now 19 */ + _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 19, 14); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* changing the rate changes the out-point */ + _assert_set_rate (rate0, "rate", 1.0, value); + + /* out-point of video_source now 17 */ + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* change back */ + _assert_set_rate (rate0, "rate", 0.5, value); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* make inactive */ + fail_unless (ges_track_element_set_active (rate0, FALSE)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* make active again */ + fail_unless (ges_track_element_set_active (rate0, TRUE)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay, which is after rate0 */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* change order */ + fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (overlay), + 2)); + + /* video source unchanged */ + _assert_control_source (video_source, "alpha", video_source_vals); + + /* new out-point is 13 + * new value is interpolated between the previous value + * (at time 7, value 11) and the *final* value (at time 19, value 14) + * Not the middle value at time 17, value 16! */ + _TWO_TIMED_VALS (overlay_vals, 7, 11, 13, 12.5); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* removing time effect changes out-point */ + gst_object_ref (rate0); + fail_unless (ges_clip_remove_top_effect (clip, GES_BASE_EFFECT (rate0), + NULL)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0); + _assert_control_source (video_source, "alpha", video_source_vals); + + _TWO_TIMED_VALS (overlay_vals, 7, 11, 19, 14); + _assert_control_source (overlay, "xpos", overlay_vals); + + /* adding also changes it */ + fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), 2, + NULL)); + + _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* unchanged for overlay */ + _assert_control_source (overlay, "xpos", overlay_vals); + + /* new value will use the value already set at in-point if possible */ + + assert_set_inpoint (video_source, 7); + + _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 13, 0.4); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* same with out-point for overlay */ + binding = ges_track_element_get_control_binding (overlay, "xpos"); + fail_unless (binding); + g_object_get (binding, "control-source", &ctrl_source, NULL); + + fail_unless (gst_timed_value_control_source_set + (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 11, 5)); + gst_object_unref (ctrl_source); + _THREE_TIMED_VALS (overlay_vals, 7, 11, 11, 5, 19, 14); + + _assert_control_source (overlay, "xpos", overlay_vals); + + fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip), + -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 21, NULL)); + + _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 9, 0.8); + _assert_control_source (video_source, "alpha", video_source_vals); + + /* overlay uses existing value, rather than an interpolation */ + _TWO_TIMED_VALS (overlay_vals, 7, 11, 11, 5); + _assert_control_source (overlay, "xpos", overlay_vals); + + g_slist_free_full (video_source_vals, g_free); + g_slist_free_full (overlay_vals, g_free); + + gst_object_unref (timeline); + g_value_unset (&value); + + ges_deinit (); +} + +GST_END_TEST; + GST_START_TEST (test_unchanged_after_layer_add_failure) { GList *found; @@ -5257,6 +5541,7 @@ ges_suite (void) tcase_add_test (tc_chain, test_children_properties_contain); tcase_add_test (tc_chain, test_children_properties_change); tcase_add_test (tc_chain, test_copy_paste_children_properties); + tcase_add_test (tc_chain, test_children_property_bindings_with_rate_effects); tcase_add_test (tc_chain, test_unchanged_after_layer_add_failure); tcase_add_test (tc_chain, test_convert_time); diff --git a/tests/check/ges/project.c b/tests/check/ges/project.c index 3c544df158..d5d7c73234 100644 --- a/tests/check/ges/project.c +++ b/tests/check/ges/project.c @@ -342,6 +342,8 @@ _add_properties (GESTimeline * timeline) (source), 5 * GST_SECOND, 0.); gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE (source), 10 * GST_SECOND, 1.); + + gst_object_unref (source); } else if (GES_IS_VIDEO_SOURCE (element)) { /* Adding children properties */ gint64 posx = 42; @@ -412,6 +414,7 @@ _check_properties (GESTimeline * timeline) fail_unless (value->value == 1.); fail_unless (value->timestamp == 10 * GST_SECOND); g_list_free (timed_values); + gst_object_unref (source); } /* Checking children properties */ else if (GES_IS_VIDEO_SOURCE (element)) {