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: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
This commit is contained in:
Henry Wilkes 2020-05-20 21:20:10 +01:00
parent 738524662b
commit 9e18e03939
9 changed files with 737 additions and 152 deletions

View file

@ -1081,8 +1081,7 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self,
if (priv->state != STATE_LOADING_CLIPS) { if (priv->state != STATE_LOADING_CLIPS) {
GST_DEBUG_OBJECT (self, "Not loading control bindings in %s state.", GST_DEBUG_OBJECT (self, "Not loading control bindings in %s state.",
loading_state_name (priv->state)); loading_state_name (priv->state));
g_slist_free_full (timed_values, g_free); goto done;
return;
} }
if (track_id[0] != '-' && priv->current_clip) if (track_id[0] != '-' && priv->current_clip)
@ -1092,23 +1091,28 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self,
if (element == NULL) { if (element == NULL) {
GST_WARNING ("No current track element to which we can append a binding"); GST_WARNING ("No current track element to which we can append a binding");
return; goto done;
} }
if (!g_strcmp0 (source_type, "interpolation")) { if (!g_strcmp0 (source_type, "interpolation")) {
GstControlSource *source; GstControlSource *source;
source = gst_interpolation_control_source_new (); source = gst_interpolation_control_source_new ();
/* add first before setting values to avoid clamping */
ges_track_element_set_control_source (element, source, ges_track_element_set_control_source (element, source,
property_name, binding_type); property_name, binding_type);
g_object_set (source, "mode", mode, NULL); g_object_set (source, "mode", mode, NULL);
gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE
(source), timed_values); (source), timed_values);
g_slist_free_full (timed_values, g_free);
gst_object_unref (source);
} else } else
GST_WARNING ("This interpolation type is not supported\n"); GST_WARNING ("This interpolation type is not supported\n");
done:
g_slist_free_full (timed_values, g_free);
} }
void void

View file

@ -167,6 +167,7 @@ struct _GESClipPrivate
GstClockTime duration_limit; GstClockTime duration_limit;
gboolean prevent_duration_limit_update; gboolean prevent_duration_limit_update;
gboolean prevent_children_outpoint_update;
gboolean allow_any_remove; gboolean allow_any_remove;
@ -446,6 +447,19 @@ _calculate_duration_limit (GESClip * self, GList * child_data)
return limit; 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 static void
_update_duration_limit (GESClip * self) _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 active = ges_track_element_is_active (child);
gboolean is_core = _IS_CORE_CHILD (child); gboolean is_core = _IS_CORE_CHILD (child);
gboolean prev_prevent = self->priv->prevent_duration_limit_update; 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 /* We want to ensure that each active non-core element has a
* corresponding active core element in the same track */ * 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->setting_active = TRUE;
self->priv->prevent_duration_limit_update = 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 core, make all the non-core elements in-active
* If we are non-core, make the core element active (should only be one) */ * 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->setting_active = FALSE;
self->priv->prevent_duration_limit_update = prev_prevent; 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; GESTrackElement *core;
gboolean active; gboolean active;
gboolean prev_prevent = self->priv->prevent_duration_limit_update; 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) if (self->priv->allow_any_track || _IS_CORE_CHILD (child) || !track)
return; return;
@ -1050,6 +1068,7 @@ _update_active_for_track (GESClip * self, GESTrackElement * child)
self->priv->setting_active = TRUE; self->priv->setting_active = TRUE;
self->priv->prevent_duration_limit_update = TRUE; self->priv->prevent_duration_limit_update = TRUE;
self->priv->prevent_children_outpoint_update = TRUE;
if (!ges_track_element_set_active (child, FALSE)) if (!ges_track_element_set_active (child, FALSE))
GST_ERROR_OBJECT (self, "Failed to de-activate child %" GES_FORMAT, 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->setting_active = FALSE;
self->priv->prevent_duration_limit_update = prev_prevent; 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, _child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
GESClip * self) GESClip * self)
{ {
gboolean update = FALSE; gboolean update_limit = FALSE;
gboolean update_outpoint = FALSE;
const gchar *name = pspec->name; const gchar *name = pspec->name;
if (_IS_PROP ("track")) { if (_IS_PROP ("track")) {
update = TRUE; update_limit = TRUE;
update_outpoint = TRUE;
_update_active_for_track (self, GES_TRACK_ELEMENT (child)); _update_active_for_track (self, GES_TRACK_ELEMENT (child));
} else if (_IS_PROP ("active")) { } else if (_IS_PROP ("active")) {
update = TRUE; update_limit = TRUE;
update_outpoint = TRUE;
_child_active_changed (self, GES_TRACK_ELEMENT (child)); _child_active_changed (self, GES_TRACK_ELEMENT (child));
} else if (_IS_PROP ("priority")) { } else if (_IS_PROP ("priority")) {
update = TRUE; update_limit = TRUE;
update_outpoint = TRUE;
_child_priority_changed (GES_CONTAINER (self), child); _child_priority_changed (GES_CONTAINER (self), child);
} else if (_IS_PROP ("in-point")) { } 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")) { } else if (_IS_PROP ("max-duration")) {
update = TRUE; update_limit = TRUE;
_child_max_duration_changed (GES_CONTAINER (self), child); _child_max_duration_changed (GES_CONTAINER (self), child);
} else if (_IS_PROP ("has-internal-source")) { } else if (_IS_PROP ("has-internal-source")) {
_child_has_internal_source_changed (self, child); _child_has_internal_source_changed (self, child);
} }
if (update) if (update_limit)
_update_duration_limit (self); _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) { if (time_prop) {
g_free (time_prop); g_free (time_prop);
_update_duration_limit (self); _update_duration_limit (self);
_update_children_outpoints (self);
} }
} }
@ -1346,6 +1374,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
GList *tmp; GList *tmp;
guint32 min_prio, max_prio, min_child_prio = G_MAXUINT32; guint32 min_prio, max_prio, min_child_prio = G_MAXUINT32;
gboolean prev_prevent = priv->prevent_duration_limit_update; gboolean prev_prevent = priv->prevent_duration_limit_update;
gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
GESContainer *container = GES_CONTAINER (element); GESContainer *container = GES_CONTAINER (element);
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) 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 */ /* offsets will remain constant for the children */
priv->prevent_resort = TRUE; priv->prevent_resort = TRUE;
priv->prevent_duration_limit_update = TRUE; priv->prevent_duration_limit_update = TRUE;
priv->prevent_children_outpoint_update = TRUE;
priv->setting_priority = TRUE; priv->setting_priority = TRUE;
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) { for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = tmp->data; GESTimelineElement *child = tmp->data;
@ -1378,6 +1408,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
priv->prevent_resort = FALSE; priv->prevent_resort = FALSE;
priv->setting_priority = FALSE; priv->setting_priority = FALSE;
priv->prevent_duration_limit_update = prev_prevent; priv->prevent_duration_limit_update = prev_prevent;
priv->prevent_children_outpoint_update = prev_prevent_outpoint;
return TRUE; return TRUE;
} }
@ -1480,6 +1511,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
GESClipPrivate *priv = self->priv; GESClipPrivate *priv = self->priv;
GESAsset *asset, *creator_asset; GESAsset *asset, *creator_asset;
gboolean prev_prevent = priv->prevent_duration_limit_update; gboolean prev_prevent = priv->prevent_duration_limit_update;
gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
GList *tmp; GList *tmp;
GError *error = NULL; GError *error = NULL;
@ -1664,6 +1696,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
priv->prevent_resort = TRUE; priv->prevent_resort = TRUE;
priv->setting_priority = TRUE; priv->setting_priority = TRUE;
priv->prevent_duration_limit_update = TRUE; priv->prevent_duration_limit_update = TRUE;
priv->prevent_children_outpoint_update = TRUE;
/* increase the priority of anything with a lower priority */ /* increase the priority of anything with a lower priority */
for (tmp = container->children; tmp; tmp = tmp->next) { for (tmp = container->children; tmp; tmp = tmp->next) {
@ -1676,6 +1709,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
priv->prevent_resort = FALSE; priv->prevent_resort = FALSE;
priv->setting_priority = FALSE; priv->setting_priority = FALSE;
priv->prevent_duration_limit_update = prev_prevent; 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 /* no need to call _ges_container_sort_children (container) since
* there is no change to the ordering yet (this happens after the * there is no change to the ordering yet (this happens after the
* child is actually added) */ * child is actually added) */
@ -1776,12 +1810,14 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
if (_IS_TOP_EFFECT (element)) { if (_IS_TOP_EFFECT (element)) {
GList *tmp; GList *tmp;
gboolean prev_prevent = priv->prevent_duration_limit_update; gboolean prev_prevent = priv->prevent_duration_limit_update;
gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
GST_DEBUG_OBJECT (container, "Resyncing effects priority."); GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
/* changing priorities, so preventing a re-sort */ /* changing priorities, so preventing a re-sort */
priv->prevent_resort = TRUE; priv->prevent_resort = TRUE;
priv->setting_priority = TRUE; priv->setting_priority = TRUE;
priv->prevent_duration_limit_update = TRUE; priv->prevent_duration_limit_update = TRUE;
priv->prevent_children_outpoint_update = TRUE;
for (tmp = container->children; tmp; tmp = tmp->next) { for (tmp = container->children; tmp; tmp = tmp->next) {
guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data); guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data);
if (sibling_prio > element->priority) if (sibling_prio > element->priority)
@ -1792,6 +1828,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
priv->prevent_resort = FALSE; priv->prevent_resort = FALSE;
priv->setting_priority = FALSE; priv->setting_priority = FALSE;
priv->prevent_duration_limit_update = prev_prevent; 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 /* no need to re-sort the children since the rest keep the same
* relative priorities */ * relative priorities */
/* height may have changed */ /* height may have changed */
@ -1817,6 +1854,7 @@ _child_added (GESContainer * container, GESTimelineElement * element)
_update_max_duration (container); _update_max_duration (container);
_update_duration_limit (self); _update_duration_limit (self);
_update_children_outpoints (self);
} }
static void static void
@ -1835,6 +1873,8 @@ _child_removed (GESContainer * container, GESTimelineElement * element)
_update_max_duration (container); _update_max_duration (container);
_update_duration_limit (self); _update_duration_limit (self);
_update_children_outpoints (self);
ges_track_element_update_outpoint (GES_TRACK_ELEMENT (element));
} }
static void static void
@ -1862,6 +1902,10 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip); GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip);
gboolean prev_prevent_from = from_clip->priv->prevent_duration_limit_update; 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_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 */ /* We need to bump the refcount to avoid the object to be destroyed */
gst_object_ref (child); gst_object_ref (child);
@ -1871,6 +1915,8 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
from_clip->priv->prevent_duration_limit_update = TRUE; from_clip->priv->prevent_duration_limit_update = TRUE;
to_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; from_clip->priv->allow_any_remove = TRUE;
ges_container_remove (GES_CONTAINER (from_clip), 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; from_clip->priv->prevent_duration_limit_update = prev_prevent_from;
to_clip->priv->prevent_duration_limit_update = prev_prevent_to; 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); gst_object_unref (child);
} }
@ -2115,12 +2164,14 @@ ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
{ {
GList *tmp; GList *tmp;
gboolean prev_prevent = clip->priv->prevent_duration_limit_update; gboolean prev_prevent = clip->priv->prevent_duration_limit_update;
gboolean prev_prevent_outpoint = clip->priv->prevent_children_outpoint_update;
if (track == NULL) if (track == NULL)
return; return;
/* allow us to remove in any order */ /* allow us to remove in any order */
clip->priv->allow_any_track = TRUE; clip->priv->allow_any_track = TRUE;
clip->priv->prevent_duration_limit_update = 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) { for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *child = tmp->data; 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->allow_any_track = FALSE;
clip->priv->prevent_duration_limit_update = prev_prevent; clip->priv->prevent_duration_limit_update = prev_prevent;
clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
_update_duration_limit (clip); _update_duration_limit (clip);
_update_children_outpoints (clip);
} }
static GESTrackElement * static GESTrackElement *
@ -3193,8 +3246,7 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
GList *tmp, *transitions = NULL; GList *tmp, *transitions = NULL;
GESClip *new_object; GESClip *new_object;
gboolean no_core = FALSE; gboolean no_core = FALSE;
GstClockTime start, inpoint, duration, old_duration, new_duration, GstClockTime start, duration, old_duration, new_duration, new_inpoint;
new_inpoint;
GESTimelineElement *element; GESTimelineElement *element;
GESTimeline *timeline; GESTimeline *timeline;
GHashTable *track_for_copy; GHashTable *track_for_copy;
@ -3210,7 +3262,6 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
duration = element->duration; duration = element->duration;
start = element->start; start = element->start;
inpoint = element->inpoint;
if (position >= start + duration || position <= start) { if (position >= start + duration || position <= start) {
GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT 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 */ /* Create the new Clip */
new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE)); new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE));
new_object->priv->prevent_duration_limit_update = TRUE; new_object->priv->prevent_duration_limit_update = TRUE;
new_object->priv->prevent_children_outpoint_update = TRUE;
GST_DEBUG_OBJECT (new_object, "New 'splitted' clip"); GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
/* Set new timing properties on the 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); GESTrack *track = ges_track_element_get_track (orig);
GESAutoTransition *trans; GESAutoTransition *trans;
/* FIXME: is position - start + inpoint always the correct splitting copy = ges_clip_copy_track_element_into (new_object, orig, new_inpoint);
* 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);
if (!copy) if (!copy)
continue; continue;
@ -3347,7 +3395,9 @@ ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
g_list_free_full (transitions, gst_object_unref); g_list_free_full (transitions, gst_object_unref);
new_object->priv->prevent_duration_limit_update = FALSE; new_object->priv->prevent_duration_limit_update = FALSE;
new_object->priv->prevent_children_outpoint_update = FALSE;
_update_duration_limit (new_object); _update_duration_limit (new_object);
_update_children_outpoints (new_object);
return new_object; return new_object;
} }

View file

@ -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, G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element,
GESTrackElement *new_element, GESTrackElement *new_element,
guint64 position); 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 G_GNUC_INTERNAL void
ges_track_element_set_creator_asset (GESTrackElement * self, ges_track_element_set_creator_asset (GESTrackElement * self,

View file

@ -1861,6 +1861,12 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits)
/* freeze the auto-transitions whilst we edit */ /* freeze the auto-transitions whilst we edit */
ges_timeline_freeze_auto_transitions (root->data, TRUE); 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); g_hash_table_iter_init (&iter, edits);
while (g_hash_table_iter_next (&iter, &key, &value)) { while (g_hash_table_iter_next (&iter, &key, &value)) {
GESTimelineElement *element = key; GESTimelineElement *element = key;
@ -1868,6 +1874,13 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits)
if (!perform_element_edit (element, edit_data)) if (!perform_element_edit (element, edit_data))
no_errors = FALSE; 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 */ /* allow the transitions to update if they can */
ges_timeline_freeze_auto_transitions (root->data, FALSE); ges_timeline_freeze_auto_transitions (root->data, FALSE);

View file

@ -40,6 +40,16 @@
* tracks and take responsibility for updating them. The only track * tracks and take responsibility for updating them. The only track
* elements that are not automatically created by clips, but a user is * elements that are not automatically created by clips, but a user is
* likely to want to create, are #GESEffect-s. * 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 #ifdef HAVE_CONFIG_H
#include "config.h" #include "config.h"
@ -62,13 +72,15 @@ struct _GESTrackElementPrivate
gboolean has_internal_source_forbidden; gboolean has_internal_source_forbidden;
gboolean has_internal_source; gboolean has_internal_source;
gboolean locked; /* If TRUE, then moves in sync with its controlling
* GESClip */
gboolean layer_active; gboolean layer_active;
GHashTable *bindings_hashtable; /* We need this if we want to be able to serialize GHashTable *bindings_hashtable; /* We need this if we want to be able to serialize
and deserialize keyframes */ and deserialize keyframes */
GESAsset *creator_asset; GESAsset *creator_asset;
GstClockTime outpoint;
gboolean freeze_control_sources;
gboolean auto_clamp_control_sources;
}; };
enum enum
@ -78,6 +90,7 @@ enum
PROP_TRACK_TYPE, PROP_TRACK_TYPE,
PROP_TRACK, PROP_TRACK,
PROP_HAS_INTERNAL_SOURCE, PROP_HAS_INTERNAL_SOURCE,
PROP_AUTO_CLAMP_CONTROL_SOURCES,
PROP_LAST PROP_LAST
}; };
@ -120,10 +133,6 @@ static gboolean _set_max_duration (GESTimelineElement * element,
static gboolean _set_priority (GESTimelineElement * element, guint32 priority); static gboolean _set_priority (GESTimelineElement * element, guint32 priority);
GESTrackType _get_track_types (GESTimelineElement * object); GESTrackType _get_track_types (GESTimelineElement * object);
static void
_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint,
GstClockTime duration);
static gboolean static gboolean
_lookup_child (GESTrackElement * object, _lookup_child (GESTrackElement * object,
const gchar * prop_name, GstElement ** element, GParamSpec ** pspec) 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, g_value_set_boolean (value,
ges_track_element_has_internal_source (track_element)); ges_track_element_has_internal_source (track_element));
break; break;
case PROP_AUTO_CLAMP_CONTROL_SOURCES:
g_value_set_boolean (value,
ges_track_element_get_auto_clamp_control_sources (track_element));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 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, ges_track_element_set_has_internal_source (track_element,
g_value_get_boolean (value)); g_value_get_boolean (value));
break; break;
case PROP_AUTO_CLAMP_CONTROL_SOURCES:
ges_track_element_set_auto_clamp_control_sources (track_element,
g_value_get_boolean (value));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 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, g_object_class_install_property (object_class, PROP_HAS_INTERNAL_SOURCE,
properties[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: * GESTrackElement::control-binding-added:
* @track_element: A #GESTrackElement * @track_element: A #GESTrackElement
@ -483,6 +522,9 @@ ges_track_element_init (GESTrackElement * self)
* because it calls g_object_new_with_properties. * because it calls g_object_new_with_properties.
*/ */
self->priv->has_internal_source = TRUE; self->priv->has_internal_source = TRUE;
self->priv->outpoint = GST_CLOCK_TIME_NONE;
self->priv->auto_clamp_control_sources = TRUE;
} }
static gfloat static gfloat
@ -504,6 +546,7 @@ interpolate_values_for_position (GstTimedValue * first_value,
diff = second_value->value - first_value->value; diff = second_value->value - first_value->value;
interval = second_value->timestamp - first_value->timestamp; interval = second_value->timestamp - first_value->timestamp;
/* FIXME: properly support non-linear timed control sources */
if (position > first_value->timestamp) if (position > first_value->timestamp)
value_at_pos = value_at_pos =
first_value->value + ((float) (position - first_value->value + ((float) (position -
@ -520,98 +563,116 @@ interpolate_values_for_position (GstTimedValue * first_value,
} }
static void static void
_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint, _update_control_source (GstTimedValueControlSource * source, gboolean absolute,
GstClockTime duration) GstClockTime inpoint, GstClockTime outpoint)
{ {
GParamSpec **specs; GList *values, *tmp;
guint n, n_specs; GstTimedValue *last, *first, *prev = NULL, *next = NULL;
GstControlBinding *binding; gfloat value_at_pos;
GstTimedValueControlSource *source;
GESTrackElement *self = GES_TRACK_ELEMENT (element);
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) { values = gst_timed_value_control_source_get_all (source);
GList *values, *tmp;
gboolean absolute;
GstTimedValue *last, *first, *prev = NULL, *next = NULL;
gfloat value_at_pos;
binding = ges_track_element_get_control_binding (self, specs[n]->name); if (g_list_length (values) == 0)
return;
if (!binding) first = values->data;
continue;
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 (next->timestamp == inpoint) {
if (duration == 0) { /* just leave this value in place */
gst_timed_value_control_source_unset_all (GST_TIMED_VALUE_CONTROL_SOURCE first = NULL;
(source)); break;
continue;
} }
if (next->timestamp > inpoint)
break;
}
g_list_free (values);
values = if (first) {
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);
value_at_pos = value_at_pos =
interpolate_values_for_position (first, next, inpoint, absolute); interpolate_values_for_position (first, next, inpoint, absolute);
gst_timed_value_control_source_unset (source, first->timestamp); gst_timed_value_control_source_unset (source, first->timestamp);
gst_timed_value_control_source_set (source, inpoint, value_at_pos); 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 static gboolean
@ -626,6 +687,45 @@ _set_start (GESTimelineElement * element, GstClockTime start)
return TRUE; 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 static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint) _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); 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; return TRUE;
} }
@ -663,16 +764,11 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTrackElement *object = GES_TRACK_ELEMENT (element);
GESTrackElementPrivate *priv = object->priv; GESTrackElementPrivate *priv = object->priv;
g_return_val_if_fail (object->priv->nleobject, FALSE); g_return_val_if_fail (priv->nleobject, FALSE);
if (GST_CLOCK_TIME_IS_VALID (_MAXDURATION (element)) &&
duration > _INPOINT (object) + _MAXDURATION (element))
duration = _MAXDURATION (element) - _INPOINT (object);
g_object_set (priv->nleobject, "duration", duration, NULL); g_object_set (priv->nleobject, "duration", duration, NULL);
_update_control_bindings (element, ges_timeline_element_get_inpoint (element), ges_track_element_update_outpoint_full (object, element->inpoint, duration);
duration);
return TRUE; return TRUE;
} }
@ -1626,15 +1722,16 @@ ges_track_element_copy_bindings (GESTrackElement * element,
if (!binding) if (!binding)
continue; 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)) { if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
GST_FIXME_OBJECT (element, GST_FIXME_OBJECT (element,
"Implement support for control source type: %s", "Implement support for control source type: %s",
G_OBJECT_TYPE_NAME (source)); G_OBJECT_TYPE_NAME (source));
gst_object_unref (source);
continue; continue;
} }
g_object_get (binding, "absolute", &absolute, NULL);
g_object_get (source, "mode", &mode, NULL); g_object_get (source, "mode", &mode, NULL);
new_source = new_source =
@ -1656,6 +1753,9 @@ ges_track_element_copy_bindings (GESTrackElement * element,
else else
ges_track_element_set_control_source (new_element, ges_track_element_set_control_source (new_element,
GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct"); GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct");
gst_object_unref (source);
gst_object_unref (new_source);
} }
g_free (specs); g_free (specs);
@ -1765,9 +1865,9 @@ ges_track_element_set_control_source (GESTrackElement * object,
GstControlSource * source, GstControlSource * source,
const gchar * property_name, const gchar * binding_type) const gchar * property_name, const gchar * binding_type)
{ {
gboolean ret = FALSE;
GESTrackElementPrivate *priv; GESTrackElementPrivate *priv;
GstElement *element; GstElement *element;
GParamSpec *pspec;
GstControlBinding *binding; GstControlBinding *binding;
gboolean direct, direct_absolute; gboolean direct, direct_absolute;
@ -1780,7 +1880,7 @@ ges_track_element_set_control_source (GESTrackElement * object,
return FALSE; 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"); GST_WARNING ("You need to provide a valid and controllable property name");
return FALSE; return FALSE;
} }
@ -1789,39 +1889,55 @@ ges_track_element_set_control_source (GESTrackElement * object,
direct = !g_strcmp0 (binding_type, "direct"); direct = !g_strcmp0 (binding_type, "direct");
direct_absolute = !g_strcmp0 (binding_type, "direct-absolute"); direct_absolute = !g_strcmp0 (binding_type, "direct-absolute");
if (direct || direct_absolute) { if (!direct && !direct_absolute) {
/* First remove existing binding */ GST_WARNING_OBJECT (object, "Binding type must be in "
if (ges_track_element_remove_control_binding (object, property_name)) { "[direct, direct-absolute]");
GST_LOG ("Removed old binding for property %s", property_name); goto done;
}
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;
} }
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; 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: * ges_track_element_is_core:

View file

@ -161,6 +161,16 @@ ges_track_element_set_control_source (GESTrackElement *object,
const gchar *property_name, const gchar *property_name,
const gchar *binding_type); 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_API GstControlBinding *
ges_track_element_get_control_binding (GESTrackElement *object, ges_track_element_get_control_binding (GESTrackElement *object,
const gchar *property_name); const gchar *property_name);

View file

@ -1511,6 +1511,8 @@ _save_keyframes (GString * str, GESTrackElement * trackelement, gint index,
append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth); append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth);
} else } else
GST_DEBUG ("control source not in [interpolation]"); GST_DEBUG ("control source not in [interpolation]");
gst_object_unref (source);
} else } else
GST_DEBUG ("Binding type not in [direct, direct-absolute]"); GST_DEBUG ("Binding type not in [direct, direct-absolute]");
} }

View file

@ -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_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 5 * GST_SECOND);
check_layer (clip, 0); check_layer (clip, 0);
gst_object_unref (timeline); gst_object_unref (timeline);
gst_object_unref (source);
gst_object_unref (splitsource);
ges_deinit (); ges_deinit ();
} }
@ -303,6 +306,9 @@ GST_START_TEST (test_split_direct_absolute_bindings)
check_layer (clip, 0); check_layer (clip, 0);
gst_object_unref (timeline); gst_object_unref (timeline);
gst_object_unref (source);
gst_object_unref (splitsource);
ges_deinit (); ges_deinit ();
} }
@ -4573,11 +4579,12 @@ _new_timed_value (GstClockTime time, gdouble val)
GstControlBinding *binding = ges_track_element_get_control_binding ( \ GstControlBinding *binding = ges_track_element_get_control_binding ( \
GES_TRACK_ELEMENT (element), prop_name); \ GES_TRACK_ELEMENT (element), prop_name); \
fail_unless (binding, "No control binding found for %s on %s", \ 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, \ g_object_get (G_OBJECT (binding), "control-source", &source, \
"object", &found_object, NULL); \ "object", &found_object, NULL); \
\ \
fail_unless (found_object == child); \ if (child) \
fail_unless (found_object == child); \
g_object_unref (found_object); \ g_object_unref (found_object); \
\ \
fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)); \ 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; \ for (i = 0, tmp1 = timed_vals, tmp2 = found_timed_vals; tmp1 && tmp2; \
tmp1 = tmp1->next, tmp2 = tmp2->next, i++) { \ tmp1 = tmp1->next, tmp2 = tmp2->next, i++) { \
GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \ GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \
fail_unless (val1->timestamp == val2->timestamp && \ gdouble diff = (val1->value > val2->value) ? \
val1->value == val2->value, "The %ith timed value (%lu: %g) " \ val1->value - val2->value : val2->value - val1->value; \
"does not match the found timed value (%lu: %g)", \ fail_unless (val1->timestamp == val2->timestamp && diff < 0.0001, \
i, val1->timestamp, val1->value, val2->timestamp, 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); \
} \ } \
fail_unless (tmp1 == NULL, "Found too few timed values"); \ fail_unless (tmp1 == NULL, "Found too few timed values"); \
fail_unless (tmp2 == NULL, "Found too many timed values"); \ fail_unless (tmp2 == NULL, "Found too many timed values"); \
\ \
g_list_free (found_timed_vals); \ g_list_free (found_timed_vals); \
g_object_get (G_OBJECT (source), "mode", &found_mode, NULL); \ 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); \ 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 */ /* find the track element where the child property comes from */
fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop)); fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop));
_assert_int_val_child_prop (track_el, val, 30, prop, "posx"); _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 */ /* set a control binding */
timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5)); 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; 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) GST_START_TEST (test_unchanged_after_layer_add_failure)
{ {
GList *found; 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_contain);
tcase_add_test (tc_chain, test_children_properties_change); 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_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_unchanged_after_layer_add_failure);
tcase_add_test (tc_chain, test_convert_time); tcase_add_test (tc_chain, test_convert_time);

View file

@ -342,6 +342,8 @@ _add_properties (GESTimeline * timeline)
(source), 5 * GST_SECOND, 0.); (source), 5 * GST_SECOND, 0.);
gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
(source), 10 * GST_SECOND, 1.); (source), 10 * GST_SECOND, 1.);
gst_object_unref (source);
} else if (GES_IS_VIDEO_SOURCE (element)) { } else if (GES_IS_VIDEO_SOURCE (element)) {
/* Adding children properties */ /* Adding children properties */
gint64 posx = 42; gint64 posx = 42;
@ -412,6 +414,7 @@ _check_properties (GESTimeline * timeline)
fail_unless (value->value == 1.); fail_unless (value->value == 1.);
fail_unless (value->timestamp == 10 * GST_SECOND); fail_unless (value->timestamp == 10 * GST_SECOND);
g_list_free (timed_values); g_list_free (timed_values);
gst_object_unref (source);
} }
/* Checking children properties */ /* Checking children properties */
else if (GES_IS_VIDEO_SOURCE (element)) { else if (GES_IS_VIDEO_SOURCE (element)) {