From 571120dcfbefc02c01f9ddb0be7c2debbcacad88 Mon Sep 17 00:00:00 2001 From: Henry Wilkes Date: Fri, 15 May 2020 14:28:09 +0100 Subject: [PATCH] effect: Add support for time effects Allow the user to register a child property of a base effect as a time property. This can be used by GES to correctly calculate the duration-limit of a clip when it has time effects on it. The existing ges_effect_class_register_rate_property is now used to automatically register such time effects for rate effects. Part-of: --- ges/ges-base-effect-clip.c | 22 +- ges/ges-base-effect.c | 374 +++++++++++++++++++++++- ges/ges-base-effect.h | 35 +++ ges/ges-clip.c | 208 +++++++++++-- ges/ges-effect.c | 125 +++++++- ges/ges-internal.h | 26 ++ ges/ges-timeline-element.c | 61 +++- ges/ges-timeline-element.h | 22 +- ges/ges-track-element.c | 16 +- tests/check/ges/clip.c | 577 +++++++++++++++++++++++++++++++++++++ 10 files changed, 1408 insertions(+), 58 deletions(-) diff --git a/ges/ges-base-effect-clip.c b/ges/ges-base-effect-clip.c index 118832c53e..952abf5a19 100644 --- a/ges/ges-base-effect-clip.c +++ b/ges/ges-base-effect-clip.c @@ -31,6 +31,9 @@ * non-core elements. These additional effects are applied to the output * of the core effects of the clip that they share a #GESTrack with. See * #GESClip for how to add and move these effects from the clip. + * + * Note that you cannot add time effects to #GESBaseEffectClip, neither + * as core children, nor as additional effects. */ /* FIXME: properly handle the priority of the children. How should we sort @@ -51,15 +54,32 @@ struct _GESBaseEffectClipPrivate G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffectClip, ges_base_effect_clip, GES_TYPE_OPERATION_CLIP); +static gboolean +ges_base_effect_clip_add_child (GESContainer * container, + GESTimelineElement * element) +{ + if (GES_IS_TIME_EFFECT (element)) { + GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child " + "because it is a time effect", GES_ARGS (element)); + return FALSE; + } + + return + GES_CONTAINER_CLASS (ges_base_effect_clip_parent_class)->add_child + (container, element); +} + static void ges_base_effect_clip_class_init (GESBaseEffectClipClass * klass) { + GESContainerClass *container_class = GES_CONTAINER_CLASS (klass); + GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE; + container_class->add_child = ges_base_effect_clip_add_child; } static void ges_base_effect_clip_init (GESBaseEffectClip * self) { self->priv = ges_base_effect_clip_get_instance_private (self); - } diff --git a/ges/ges-base-effect.c b/ges/ges-base-effect.c index b604833c81..19c9e4bf35 100644 --- a/ges/ges-base-effect.c +++ b/ges/ges-base-effect.c @@ -22,6 +22,56 @@ * @title: GESBaseEffect * @short_description: adds an effect to a stream in a GESSourceClip or a * GESLayer + * + * A #GESBaseEffect is some operation that applies an effect to the data + * it receives. + * + * ## Time Effects + * + * Some operations will change the timing of the stream data they receive + * in some way. In particular, the #GstElement that they wrap could alter + * the times of the segment they receive in a #GST_EVENT_SEGMENT event, + * or the times of a seek they receive in a #GST_EVENT_SEEK event. Such + * operations would be considered time effects since they translate the + * times they receive on their source to different times at their sink, + * and vis versa. This introduces two sets of time coordinates for the + * event: (internal) sink coordinates and (internal) source coordinates, + * where segment times are translated from the sink coordinates to the + * source coordinates, and seek times are translated from the source + * coordinates to the sink coordinates. + * + * If you use such an effect in GES, you will need to inform GES of the + * properties that control the timing with + * ges_base_effect_register_time_property(), and the effect's timing + * behaviour using ges_base_effect_set_time_translation_funcs(). + * + * Note that a time effect should not have its + * #GESTrackElement:has-internal-source set to %TRUE. + * + * In addition, note that GES only *fully* supports time effects whose + * mapping from the source to sink coordinates (those applied to seeks) + * obeys: + * + * + Maps the time `0` to `0`. So initial time-shifting effects are + * excluded. + * + Is monotonically increasing. So reversing effects, and effects that + * jump backwards in the stream are excluded. + * + Can handle a reasonable #GstClockTime, relative to the project. So + * this would exclude a time effect with an extremely large speed-up + * that would cause the converted #GstClockTime seeks to overflow. + * + Is 'continuously reversible'. This essentially means that for every + * time in the sink coordinates, we can, to 'good enough' accuracy, + * calculate the corresponding time in the source coordinates. Moreover, + * this should correspond to how segment times are translated from + * sink to source. + * + Only depends on the registered time properties, rather than the + * state of the #GstElement or the data it receives. This would exclude, + * say, an effect that would speedup if there is more red in the image + * it receives. + * + * Note that a constant-rate-change effect that is not extremely fast or + * slow would satisfy these conditions. For such effects, you may wish to + * use ges_effect_class_register_rate_property(). */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -34,17 +84,81 @@ #include "ges-track-element.h" #include "ges-base-effect.h" +typedef struct _TimePropertyData +{ + gchar *property_name; + GObject *child; + GParamSpec *pspec; +} TimePropertyData; + +static void +_time_property_data_free (gpointer data_p) +{ + TimePropertyData *data = data_p; + g_free (data->property_name); + gst_object_unref (data->child); + g_param_spec_unref (data->pspec); + g_free (data); +} + struct _GESBaseEffectPrivate { - void *nothing; + GList *time_properties; + GESBaseEffectTimeTranslationFunc source_to_sink; + GESBaseEffectTimeTranslationFunc sink_to_source; + gpointer translation_data; + GDestroyNotify destroy_translation_data; }; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffect, ges_base_effect, GES_TYPE_OPERATION); +static gboolean +ges_base_effect_set_child_property_full (GESTimelineElement * element, + GObject * child, GParamSpec * pspec, const GValue * value, GError ** error) +{ + GESClip *parent = GES_IS_CLIP (element->parent) ? + GES_CLIP (element->parent) : NULL; + + if (parent && !ges_clip_can_set_time_property_of_child (parent, + GES_TRACK_ELEMENT (element), child, pspec, value, error)) { + GST_INFO_OBJECT (element, "Cannot set time property '%s::%s' " + "because the parent clip %" GES_FORMAT " would not allow it", + G_OBJECT_TYPE_NAME (child), pspec->name, GES_ARGS (parent)); + return FALSE; + } + + return + GES_TIMELINE_ELEMENT_CLASS + (ges_base_effect_parent_class)->set_child_property_full (element, child, + pspec, value, error); +} + +static void +ges_base_effect_dispose (GObject * object) +{ + GESBaseEffectPrivate *priv = GES_BASE_EFFECT (object)->priv; + + g_list_free_full (priv->time_properties, _time_property_data_free); + priv->time_properties = NULL; + if (priv->destroy_translation_data) + priv->destroy_translation_data (priv->translation_data); + priv->destroy_translation_data = NULL; + priv->source_to_sink = NULL; + priv->sink_to_source = NULL; + + G_OBJECT_CLASS (ges_base_effect_parent_class)->dispose (object); +} + static void ges_base_effect_class_init (GESBaseEffectClass * klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass); + + object_class->dispose = ges_base_effect_dispose; + element_class->set_child_property_full = + ges_base_effect_set_child_property_full; } static void @@ -52,3 +166,261 @@ ges_base_effect_init (GESBaseEffect * self) { self->priv = ges_base_effect_get_instance_private (self); } + +static void +_child_property_removed (GESTimelineElement * element, GObject * child, + GParamSpec * pspec, gpointer user_data) +{ + GList *tmp; + GESBaseEffectPrivate *priv = GES_BASE_EFFECT (element)->priv; + + for (tmp = priv->time_properties; tmp; tmp = tmp->next) { + TimePropertyData *data = tmp->data; + if (data->child == child && data->pspec == pspec) { + priv->time_properties = g_list_remove (priv->time_properties, data); + _time_property_data_free (data); + return; + } + } +} + +/** + * ges_base_effect_register_time_property: + * @effect: A #GESBaseEffect + * @child_property_name: The name of the child property to register as + * a time property + * + * Register a child property of the effect as a property that, when set, + * can change the timing of its input data. The child property should be + * specified as in ges_timeline_element_lookup_child(). + * + * You should also set the corresponding time translation using + * ges_base_effect_set_time_translation_funcs(). + * + * Note that @effect must not be part of a clip, nor can it have + * #GESTrackElement:has-internal-source set to %TRUE. + * + * Returns: %TRUE if the child property was found and newly registered. + */ +gboolean +ges_base_effect_register_time_property (GESBaseEffect * effect, + const gchar * child_property_name) +{ + GESTimelineElement *element; + GESTrackElement *el; + GParamSpec *pspec; + GObject *child; + GList *tmp; + TimePropertyData *data; + + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + el = GES_TRACK_ELEMENT (effect); + element = GES_TIMELINE_ELEMENT (el); + + g_return_val_if_fail (element->parent == NULL, FALSE); + g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE, + FALSE); + + if (!ges_timeline_element_lookup_child (element, child_property_name, + &child, &pspec)) + return FALSE; + + for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) { + data = tmp->data; + if (data->child == child && data->pspec == pspec) { + GST_WARNING_OBJECT (effect, "Already registered the time effect for %s", + child_property_name); + g_object_unref (child); + g_param_spec_unref (pspec); + return FALSE; + } + } + + ges_track_element_set_has_internal_source_is_forbidden (el); + + data = g_new0 (TimePropertyData, 1); + data->child = child; + data->pspec = pspec; + data->property_name = g_strdup (child_property_name); + + effect->priv->time_properties = + g_list_prepend (effect->priv->time_properties, data); + + g_signal_handlers_disconnect_by_func (effect, _child_property_removed, NULL); + g_signal_connect (effect, "child-property-removed", + G_CALLBACK (_child_property_removed), NULL); + + return TRUE; +} + +/** + * ges_base_effect_set_time_translation_funcs: + * @effect: A #GESBaseEffect + * @source_to_sink_func: (nullable) (scope notified): The function to use + * for querying how a time is translated from the source coordinates to + * the sink coordinates of @effect + * @sink_to_source_func: (nullable) (scope notified): The function to use + * for querying how a time is translated from the sink coordinates to the + * source coordinates of @effect + * @user_data: (closure): Data to pass to both @source_to_sink_func and + * @sink_to_source_func + * @destroy: (destroy user_data) (nullable): Method to call to destroy + * @user_data, or %NULL + * + * Set the time translation query functions for the time effect. If an + * effect is a time effect, it will have two sets of coordinates: one + * at its sink and one at its source. The given functions should be able + * to translate between these two sets of coordinates. More specifically, + * @source_to_sink_func should *emulate* how the corresponding #GstElement + * would translate the #GstSegment @time field, and @sink_to_source_func + * should emulate how the corresponding #GstElement would translate the + * seek query @start and @stop values, as used in gst_element_seek(). As + * such, @sink_to_source_func should act as an approximate reverse of + * @source_to_sink_func. + * + * Note, these functions will be passed a table of time properties, as + * registered in ges_base_effect_register_time_property(), and their + * values. The functions should emulate what the translation *would* be + * *if* the time properties were set to the given values. They should not + * use the currently set values. + * + * Note that @effect must not be part of a clip, nor can it have + * #GESTrackElement:has-internal-source set to %TRUE. + * + * Returns: %TRUE if the translation functions were set. + */ +gboolean +ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect, + GESBaseEffectTimeTranslationFunc source_to_sink_func, + GESBaseEffectTimeTranslationFunc sink_to_source_func, + gpointer user_data, GDestroyNotify destroy) +{ + GESTimelineElement *element; + GESTrackElement *el; + GESBaseEffectPrivate *priv; + + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + + element = GES_TIMELINE_ELEMENT (effect); + el = GES_TRACK_ELEMENT (element); + + g_return_val_if_fail (element->parent == NULL, FALSE); + g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE, + FALSE); + + ges_track_element_set_has_internal_source_is_forbidden (el); + + priv = effect->priv; + if (priv->destroy_translation_data) + priv->destroy_translation_data (priv->translation_data); + + priv->translation_data = user_data; + priv->destroy_translation_data = destroy; + priv->source_to_sink = source_to_sink_func; + priv->sink_to_source = sink_to_source_func; + + return TRUE; +} + +/** + * ges_base_effect_is_time_effect: + * @effect: A #GESBaseEffect + * + * Get whether the effect is considered a time effect or not. An effect + * with registered time properties or set translation functions is + * considered a time effect. + * + * Returns: %TRUE if @effect is considered a time effect. + */ +gboolean +ges_base_effect_is_time_effect (GESBaseEffect * effect) +{ + GESBaseEffectPrivate *priv; + g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE); + + priv = effect->priv; + if (priv->time_properties || priv->source_to_sink || priv->sink_to_source) + return TRUE; + return FALSE; +} + +gchar * +ges_base_effect_get_time_property_name (GESBaseEffect * effect, + GObject * child, GParamSpec * pspec) +{ + GList *tmp; + for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) { + TimePropertyData *data = tmp->data; + if (data->pspec == pspec && data->child == child) + return g_strdup (data->property_name); + } + return NULL; +} + +static void +_gvalue_free (gpointer data) +{ + GValue *val = data; + g_value_unset (val); + g_free (val); +} + +GHashTable * +ges_base_effect_get_time_property_values (GESBaseEffect * effect) +{ + GList *tmp; + GHashTable *ret = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _gvalue_free); + + for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) { + TimePropertyData *data = tmp->data; + GValue *value = g_new0 (GValue, 1); + + /* FIXME: once we move to GLib 2.60, g_object_get_property() will + * automatically initialize the type */ + g_value_init (value, data->pspec->value_type); + g_object_get_property (data->child, data->pspec->name, value); + + g_hash_table_insert (ret, g_strdup (data->property_name), value); + } + + return ret; +} + +GstClockTime +ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect, + GstClockTime time, GHashTable * time_property_values) +{ + GESBaseEffectPrivate *priv = effect->priv; + + if (!GST_CLOCK_TIME_IS_VALID (time)) + return GST_CLOCK_TIME_NONE; + + if (priv->source_to_sink) + return priv->source_to_sink (effect, time, time_property_values, + priv->translation_data); + + if (time_property_values && g_hash_table_size (time_property_values)) + GST_ERROR_OBJECT (effect, "The time effect is missing its source to " + "sink translation function"); + return time; +} + +GstClockTime +ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect, + GstClockTime time, GHashTable * time_property_values) +{ + GESBaseEffectPrivate *priv = effect->priv; + + if (!GST_CLOCK_TIME_IS_VALID (time)) + return GST_CLOCK_TIME_NONE; + + if (priv->sink_to_source) + return effect->priv->sink_to_source (effect, time, time_property_values, + priv->translation_data); + + if (time_property_values && g_hash_table_size (time_property_values)) + GST_ERROR_OBJECT (effect, "The time effect is missing its sink to " + "source translation function"); + return time; +} diff --git a/ges/ges-base-effect.h b/ges/ges-base-effect.h index 6f3d6389ea..71cf5da4a3 100644 --- a/ges/ges-base-effect.h +++ b/ges/ges-base-effect.h @@ -50,9 +50,44 @@ struct _GESBaseEffectClass { /*< private > */ GESOperationClass parent_class; + /* Padding for API extension */ gpointer _ges_reserved[GES_PADDING]; }; +/** + * GESBaseEffectTimeTranslationFunc: + * @effect: The #GESBaseEffect that is doing the time translation + * @time: The #GstClockTime to translation + * @time_property_values: (element-type gchar* GValue*): A table of child + * property name/value pairs + * @user_data: Data passed to ges_base_effect_set_time_translation_funcs() + * + * A function for querying how an effect would translate a time if it had + * the given child property values set. The keys for @time_properties will + * be the same string that was passed to + * ges_base_effect_register_time_property(), the values will be #GValue* + * values of the corresponding child properties. You should always use the + * values given in @time_properties before using the currently set values. + * + * Returns: The translated time. + */ +typedef GstClockTime (*GESBaseEffectTimeTranslationFunc) (GESBaseEffect * effect, + GstClockTime time, + GHashTable * time_property_values, + gpointer user_data); + +GES_API gboolean +ges_base_effect_register_time_property (GESBaseEffect * effect, + const gchar * child_property_name); +GES_API gboolean +ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect, + GESBaseEffectTimeTranslationFunc source_to_sink_func, + GESBaseEffectTimeTranslationFunc sink_to_source_func, + gpointer user_data, + GDestroyNotify destroy); +GES_API gboolean +ges_base_effect_is_time_effect (GESBaseEffect * effect); + G_END_DECLS diff --git a/ges/ges-clip.c b/ges/ges-clip.c index 9cd79f59ac..baf42b4466 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -224,6 +224,7 @@ typedef struct _DurationLimitData GstClockTime max_duration; GstClockTime inpoint; gboolean active; + GHashTable *time_property_values; } DurationLimitData; static DurationLimitData * @@ -239,6 +240,10 @@ _duration_limit_data_new (GESTrackElement * child) data->priority = _PRIORITY (child); data->active = ges_track_element_is_active (child); + if (GES_IS_TIME_EFFECT (child)) + data->time_property_values = + ges_base_effect_get_time_property_values (GES_BASE_EFFECT (child)); + return data; } @@ -248,6 +253,8 @@ _duration_limit_data_free (gpointer data_p) DurationLimitData *data = data_p; gst_clear_object (&data->track); gst_clear_object (&data->child); + if (data->time_property_values) + g_hash_table_unref (data->time_property_values); g_free (data); } @@ -288,9 +295,9 @@ _cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p) return 1; /* if higher priority (numerically lower) place later */ if (a->priority < b->priority) - return -1; - else if (a->priority > b->priority) return 1; + else if (a->priority > b->priority) + return -1; return 0; } @@ -298,43 +305,129 @@ _cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p) ((data->active && GST_CLOCK_TIME_IS_VALID (data->max_duration)) ? \ data->max_duration - data->inpoint : GST_CLOCK_TIME_NONE) +static GstClockTime +_calculate_track_duration_limit (GESClip * self, GList * start, GList * end) +{ + GList *tmp; + DurationLimitData *data = start->data; + GstClockTime track_limit; + + /* convert source-duration to timeline-duration + * E.g. consider the following stack + * + * *=============================* + * | source | + * | in-point = 5 | + * | max-duration = 20 | + * *=============================* + * 5 10 15 20 (internal coordinates) + * + * duration-limit = 15 because max-duration - in-point = 15 + * + * 0 5 10 15 + * *=============================* + * | time-effect | | sink_to_source + * | rate = 0.5 | v / 0.5 + * *=============================* + * 0 10 20 30 + * + * duration-limit = 30 because rate effect can make it last longer + * + * 13 23 33 (internal coordinates) + * *===================* + * |effect-with-source | + * | in-point = 13 | + * | max-duration = 33 | + * *===================* + * 13 23 33 (internal coordinates) + * + * duration-limit = 20 because effect-with-source cannot cover 30 + * + * 0 10 20 + * *===================* + * | time-effect | | sink_to_source + * | rate = 2.0 | v / 2.0 + * *===================* + * 0 5 10 + * + * duration-limit = 10 because rate effect uses up twice as much + * + * -----------------------------------------------timeline + */ + + while (!_IS_CORE_CHILD (data->child)) { + GST_WARNING_OBJECT (self, "Child %" GES_FORMAT " has a lower " + "priority than the core child in the same track. Ignoring.", + GES_ARGS (data->child)); + + start = start->next; + if (start == end) { + GST_ERROR_OBJECT (self, "Track %" GST_PTR_FORMAT " is missing a " + "core child", data->track); + return GST_CLOCK_TIME_NONE; + } + data = start->data; + } + + track_limit = _INTERNAL_LIMIT (data); + + for (tmp = start->next; tmp != end; tmp = tmp->next) { + data = tmp->data; + + if (GES_IS_TIME_EFFECT (data->child)) { + GESBaseEffect *effect = GES_BASE_EFFECT (data->child); + if (data->inpoint) + GST_ERROR_OBJECT (self, "Did not expect an in-point to be set " + "for the time effect %" GES_FORMAT, GES_ARGS (effect)); + if (GST_CLOCK_TIME_IS_VALID (data->max_duration)) + GST_ERROR_OBJECT (self, "Did not expect a max-duration to be set " + "for the time effect %" GES_FORMAT, GES_ARGS (effect)); + + if (data->active) { + /* for the time effect, the minimum time it will receive is 0 + * (it should map 0 -> 0), and the maximum time will be track_limit */ + track_limit = ges_base_effect_translate_sink_to_source_time (effect, + track_limit, data->time_property_values); + } + } else { + GstClockTime el_limit = _INTERNAL_LIMIT (data); + track_limit = _MIN_CLOCK_TIME (track_limit, el_limit); + } + } + + GST_LOG_OBJECT (self, "Track duration-limit for track %" GST_PTR_FORMAT + " is %" GST_TIME_FORMAT, data->track, GST_TIME_ARGS (track_limit)); + + return track_limit; +} + /* transfer-full of child_data */ static GstClockTime _calculate_duration_limit (GESClip * self, GList * child_data) { GstClockTime limit = GST_CLOCK_TIME_NONE; - GList *tmp; + GList *start, *end; child_data = g_list_sort (child_data, _cmp_by_track_then_priority); - tmp = child_data; + start = child_data; - while (tmp) { + while (start) { /* we have the first element in the track, of the lowest priority, and * work our way up from here */ - DurationLimitData *data = tmp->data; - GESTrack *track = data->track; - if (track) { - GstClockTime track_limit = _INTERNAL_LIMIT (data); + GESTrack *track = ((DurationLimitData *) (start->data))->track; - for (tmp = tmp->next; tmp; tmp = tmp->next) { - data = tmp->data; - if (data->track != track) - break; - track_limit = _MIN_CLOCK_TIME (track_limit, _INTERNAL_LIMIT (data)); - } + end = start; + do { + end = end->next; + } while (end && ((DurationLimitData *) (end->data))->track == track); - GST_LOG_OBJECT (self, "duration-limit for track %" GST_PTR_FORMAT - " is %" GST_TIME_FORMAT, track, GST_TIME_ARGS (track_limit)); + if (track) { + GstClockTime track_limit = + _calculate_track_duration_limit (self, start, end); limit = _MIN_CLOCK_TIME (limit, track_limit); - } else { - /* children not in a track do not affect the duration-limit */ - for (tmp = tmp->next; tmp; tmp = tmp->next) { - data = tmp->data; - if (data->track) - break; - } } + start = end; } GST_LOG_OBJECT (self, "calculated duration-limit for the clip is %" GST_TIME_FORMAT, GST_TIME_ARGS (limit)); @@ -969,6 +1062,58 @@ _child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec, _update_duration_limit (self); } +/**************************************************** + * time properties * + ****************************************************/ + +gboolean +ges_clip_can_set_time_property_of_child (GESClip * clip, + GESTrackElement * child, GObject * child_prop_object, GParamSpec * pspec, + const GValue * value, GError ** error) +{ + if (_IS_TOP_EFFECT (child)) { + gchar *prop_name = + ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child), + child_prop_object, pspec); + + if (prop_name) { + GList *child_data; + DurationLimitData *data = _duration_limit_data_new (child); + GValue *copy = g_new0 (GValue, 1); + + g_value_init (copy, pspec->value_type); + g_value_copy (value, copy); + + g_hash_table_insert (data->time_property_values, prop_name, copy); + + child_data = _duration_limit_data_list_with_data (clip, data); + + if (!_can_update_duration_limit (clip, child_data, error)) { + gchar *val_str = gst_value_serialize (value); + GST_INFO_OBJECT (clip, "Cannot set the child-property %s of " + "child %" GES_FORMAT " to %s because the duration-limit " + "cannot be adjusted", prop_name, GES_ARGS (child), val_str); + g_free (val_str); + return FALSE; + } + } + } + return TRUE; +} + +static void +_child_time_property_changed_cb (GESTimelineElement * child, + GObject * prop_object, GParamSpec * pspec, GESClip * self) +{ + gchar *time_prop = + ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child), + prop_object, pspec); + if (time_prop) { + g_free (time_prop); + _update_duration_limit (self); + } +} + /***************************************************** * * * GESTimelineElement virtual methods implementation * @@ -1551,6 +1696,10 @@ _child_added (GESContainer * container, GESTimelineElement * element) g_signal_connect (element, "notify", G_CALLBACK (_child_property_changed_cb), self); + if (GES_IS_TIME_EFFECT (element)) + g_signal_connect (element, "deep-notify", + G_CALLBACK (_child_time_property_changed_cb), self); + if (_IS_CORE_CHILD (element)) _update_max_duration (container); @@ -1564,6 +1713,10 @@ _child_removed (GESContainer * container, GESTimelineElement * element) g_signal_handlers_disconnect_by_func (element, _child_property_changed_cb, self); + /* NOTE: we do not test if the effect is a time effect since technically + * it can stop being a time effect, although this would be rare */ + g_signal_handlers_disconnect_by_func (element, + _child_time_property_changed_cb, self); if (_IS_CORE_CHILD (element)) _update_max_duration (container); @@ -2124,9 +2277,10 @@ ges_clip_class_init (GESClipClass * klass) * * The maximum #GESTimelineElement:duration that can be *currently* set * for the clip, taking into account the #GESTimelineElement:in-point, - * #GESTimelineElement:max-duration, GESTrackElement:active, and - * #GESTrackElement:track properties of its children. If there is no - * limit, this will be set to #GST_CLOCK_TIME_NONE. + * #GESTimelineElement:max-duration, #GESTrackElement:active, and + * #GESTrackElement:track properties of its children, as well as any + * time effects. If there is no limit, this will be set to + * #GST_CLOCK_TIME_NONE. * * Note that whilst a clip has no children in any tracks, the limit will * be unknown, and similarly set to #GST_CLOCK_TIME_NONE. diff --git a/ges/ges-effect.c b/ges/ges-effect.c index 4de3aaf10b..7bba621f3a 100644 --- a/ges/ges-effect.c +++ b/ges/ges-effect.c @@ -252,14 +252,83 @@ ghost_compatible_pads (GstElement * bin, GstElement * child, } } +static gdouble +_get_rate_factor (GESBaseEffect * effect, GHashTable * rate_values) +{ + GHashTableIter iter; + gpointer key, val; + gdouble factor = 1.0; + + g_hash_table_iter_init (&iter, rate_values); + while (g_hash_table_iter_next (&iter, &key, &val)) { + GValue *value = val; + gchar *prop_name = key; + gdouble rate = 1.0; + + switch (G_VALUE_TYPE (value)) { + case G_TYPE_DOUBLE: + rate = g_value_get_double (value); + break; + case G_TYPE_FLOAT: + rate = g_value_get_float (value); + break; + default: + GST_ERROR_OBJECT (effect, "Rate property %s has neither a gdouble " + "nor gfloat value", prop_name); + break; + } + factor *= rate; + } + + return factor; +} + +static GstClockTime +_rate_source_to_sink (GESBaseEffect * effect, GstClockTime time, + GHashTable * rate_values, gpointer user_data) +{ + /* multiply by rate factor + * E.g. rate=2.0, then the time 30 at the source would become + * 60 at the sink because we are using up twice as much data in a given + * time */ + gdouble rate_factor = _get_rate_factor (effect, rate_values); + + if (time == 0) + return 0; + if (rate_factor == 0.0) { + GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0"); + return 0; + } + return (GstClockTime) (time * rate_factor); +} + +static GstClockTime +_rate_sink_to_source (GESBaseEffect * effect, GstClockTime time, + GHashTable * rate_values, gpointer user_data) +{ + /* divide by rate factor */ + gdouble rate_factor = _get_rate_factor (effect, rate_values); + + if (time == 0) + return 0; + if (rate_factor == 0.0) { + GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0"); + return GST_CLOCK_TIME_NONE; + } + return (GstClockTime) (time / rate_factor); +} + static GstElement * ges_effect_create_element (GESTrackElement * object) { + GESBaseEffect *base_effect = GES_BASE_EFFECT (object); + GESEffectClass *class; GList *tmp; GstElement *effect; gchar *bin_desc; GstCaps *valid_caps; gint n_src = 0, n_sink = 0; + gboolean is_rate_effect = FALSE; GError *error = NULL; GESEffect *self = GES_EFFECT (object); @@ -308,6 +377,22 @@ ges_effect_create_element (GESTrackElement * object) ges_track_element_add_children_props (object, effect, NULL, blacklisted_factories, NULL); + class = GES_EFFECT_CLASS (g_type_class_peek (GES_TYPE_EFFECT)); + + for (tmp = class->rate_properties; tmp; tmp = tmp->next) { + gchar *prop = tmp->data; + if (ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (object), prop, + NULL, NULL)) { + if (!ges_base_effect_register_time_property (base_effect, prop)) + GST_ERROR_OBJECT (object, "Failed to register rate property %s", prop); + is_rate_effect = TRUE; + } + } + if (is_rate_effect + && !ges_base_effect_set_time_translation_funcs (base_effect, + _rate_source_to_sink, _rate_sink_to_source, NULL, NULL)) + GST_ERROR_OBJECT (object, "Failed to set rate translation functions"); + done: gst_clear_caps (&valid_caps); @@ -352,22 +437,38 @@ ges_effect_new (const gchar * bin_description) /** * ges_effect_class_register_rate_property: * @klass: Instance of the GESEffectClass - * @element_name: Name of the GstElement that changes the rate - * @property_name: Name of the property that changes the rate + * @element_name: The #GstElementFactory name of the element that changes + * the rate + * @property_name: The name of the property that changes the rate + * + * Register an element that can change the rate at which media is playing. + * The property type must be float or double, and must be a factor of the + * rate, i.e. a value of 2.0 must mean that the media plays twice as fast. + * Several properties may be registered for a single element type, + * provided they all contribute to the rate as independent factors. For + * example, this is true for the "GstPitch::rate" and "GstPitch::tempo" + * properties. These are already registered by default in GES, along with + * #videorate:rate for #videorate and #scaletempo:rate for #scaletempo. + * + * If such a rate property becomes a child property of a #GESEffect upon + * its creation (the element is part of its #GESEffect:bin-description), + * it will be automatically registered as a time property (see + * ges_base_effect_register_time_property()) and will have its time + * translation functions set (see + * ges_base_effect_set_time_translation_funcs()) to use the overall rate + * of the rate properties. Note that if an effect contains a rate + * property as well as a non-rate time property, you should ensure to set + * the time translation functions to some other methods using + * ges_base_effect_set_time_translation_funcs(). * - * Register an element that can change the rate at which media is playing. The - * property type must be float or double, and must be a factor of the rate, - * i.e. a value of 2.0 must mean that the media plays twice as fast. For - * example, this is true for the properties 'rate' and 'tempo' of the element - * 'pitch', which is already registered by default. By registering the element, - * timeline duration can be correctly converted into media duration, allowing - * the right segment seeks to be sent to the sources. + * Note, you can obtain a reference to the GESEffectClass using * - * A reference to the GESEffectClass can be obtained as follows: + * ``` * GES_EFFECT_CLASS (g_type_class_ref (GES_TYPE_EFFECT)); + * ``` * - * Returns: whether the rate property was succesfully registered. When this - * method returns false, a warning is emitted with more information. + * Returns: %TRUE if the rate property was successfully registered. When + * this method returns %FALSE, a warning is emitted with more information. */ gboolean ges_effect_class_register_rate_property (GESEffectClass * klass, diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 76eb260645..84911ff5d0 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -87,6 +87,10 @@ GstDebugCategory * _ges_debug (void); #define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT #define GES_ARGS GES_TIMELINE_ELEMENT_ARGS +#define GES_IS_TIME_EFFECT(element) \ + (GES_IS_BASE_EFFECT (element) \ + && ges_base_effect_is_time_effect (GES_BASE_EFFECT (element))) + #define GES_TIMELINE_ELEMENT_SET_BEING_EDITED(element) \ ELEMENT_SET_FLAG ( \ ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \ @@ -412,6 +416,7 @@ G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_child (GESCli G_GNUC_INTERNAL gboolean ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active, GError ** error); G_GNUC_INTERNAL gboolean ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority, GError ** error); G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_time_property_of_child (GESClip * clip, GESTrackElement * child, GObject * prop_object, GParamSpec * pspec, const GValue * value, GError ** error); G_GNUC_INTERNAL void ges_clip_empty_from_track (GESClip * clip, GESTrack * track); /**************************************************** @@ -439,6 +444,9 @@ ges_track_element_set_creator_asset (GESTrackElement * self, G_GNUC_INTERNAL GESAsset * ges_track_element_get_creator_asset (GESTrackElement * self); +G_GNUC_INTERNAL void +ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement * element); + G_GNUC_INTERNAL GstElement* ges_source_create_topbin(const gchar* bin_name, GstElement* sub_element, GPtrArray* elements); G_GNUC_INTERNAL void ges_track_set_caps(GESTrack* track, const GstCaps* caps); @@ -455,6 +463,24 @@ G_GNUC_INTERNAL GESImageSource * ges_image_source_new (gchar *uri); G_GNUC_INTERNAL GESTitleSource * ges_title_source_new (void); G_GNUC_INTERNAL GESVideoTestSource * ges_video_test_source_new (void); +/**************************************************** + * GESBaseEffect * + ****************************************************/ +G_GNUC_INTERNAL gchar * +ges_base_effect_get_time_property_name (GESBaseEffect * effect, + GObject * child, + GParamSpec * pspec); +G_GNUC_INTERNAL GHashTable * +ges_base_effect_get_time_property_values (GESBaseEffect * effect); +G_GNUC_INTERNAL GstClockTime +ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect, + GstClockTime time, + GHashTable * time_property_values); +G_GNUC_INTERNAL GstClockTime +ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect, + GstClockTime time, + GHashTable * time_property_values); + /**************************************************** * GESTimelineElement * ****************************************************/ diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index 491564f8fa..13053552c6 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -174,6 +174,15 @@ _set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child, g_object_set_property (child, pspec->name, value); } +static gboolean +_set_child_property_full (GESTimelineElement * self, GObject * child, + GParamSpec * pspec, const GValue * value, GError ** error) +{ + GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_child_property (self, child, + pspec, (GValue *) value); + return TRUE; +} + static gboolean _lookup_child (GESTimelineElement * self, const gchar * prop_name, GObject ** child, GParamSpec ** pspec) @@ -549,7 +558,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass) /** * GESTimelineElement::child-property-removed: - * @timeline_element: A #GESTtimelineElement + * @timeline_element: A #GESTimelineElement * @prop_object: The child whose property has been unregistered * @prop: The specification for the property that has been unregistered * @@ -582,6 +591,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass) ges_timeline_element_get_children_properties; klass->lookup_child = _lookup_child; klass->set_child_property = _set_child_property; + klass->set_child_property_full = _set_child_property_full; klass->get_natural_framerate = _get_natural_framerate; } @@ -762,8 +772,8 @@ emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data) } static void -child_prop_changed_cb (GObject * child, GParamSpec * arg - G_GNUC_UNUSED, GESTimelineElement * self) +child_prop_changed_cb (GObject * child, GParamSpec * arg, + GESTimelineElement * self) { EmitDeepNotifyInIdleData *data; @@ -786,7 +796,7 @@ child_prop_changed_cb (GObject * child, GParamSpec * arg static gboolean set_child_property_by_pspec (GESTimelineElement * self, - GParamSpec * pspec, const GValue * value) + GParamSpec * pspec, const GValue * value, GError ** error) { GESTimelineElementClass *klass; GESTimelineElement *setter = self; @@ -805,6 +815,10 @@ set_child_property_by_pspec (GESTimelineElement * self, klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); } + if (klass->set_child_property_full) + return klass->set_child_property_full (setter, handler->child, pspec, + value, error); + g_assert (klass->set_child_property); klass->set_child_property (setter, handler->child, pspec, (GValue *) value); @@ -1816,14 +1830,15 @@ ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self, g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); g_return_if_fail (G_IS_PARAM_SPEC (pspec)); - set_child_property_by_pspec (self, pspec, value); + set_child_property_by_pspec (self, pspec, value, NULL); } /** - * ges_timeline_element_set_child_property: + * ges_timeline_element_set_child_property_full: * @self: A #GESTimelineElement * @property_name: The name of the child property to set * @value: The value to set the property to + * @error: (nullable): Return location for an error * * Sets the property of a child of the element. * @@ -1840,24 +1855,22 @@ ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self, * property set to @value. Other children that may have also matched the * property name (and type name) are left unchanged! * - * Note that ges_timeline_element_set_child_properties() may be more - * convenient for C programming. - * * Returns: %TRUE if the property was found and set. */ gboolean -ges_timeline_element_set_child_property (GESTimelineElement * self, - const gchar * property_name, const GValue * value) +ges_timeline_element_set_child_property_full (GESTimelineElement * self, + const gchar * property_name, const GValue * value, GError ** error) { GParamSpec *pspec; GObject *child; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec)) goto not_found; - return set_child_property_by_pspec (self, pspec, value); + return set_child_property_by_pspec (self, pspec, value, error); not_found: { @@ -1867,6 +1880,28 @@ not_found: } } +/** + * ges_timeline_element_set_child_property: + * @self: A #GESTimelineElement + * @property_name: The name of the child property to set + * @value: The value to set the property to + * + * See ges_timeline_element_set_child_property_full(), which also gives an + * error. + * + * Note that ges_timeline_element_set_child_properties() may be more + * convenient for C programming. + * + * Returns: %TRUE if the property was found and set. + */ +gboolean +ges_timeline_element_set_child_property (GESTimelineElement * self, + const gchar * property_name, const GValue * value) +{ + return ges_timeline_element_set_child_property_full (self, property_name, + value, NULL); +} + /** * ges_timeline_element_get_child_property: * @self: A #GESTimelineElement @@ -2001,7 +2036,7 @@ ges_timeline_element_set_child_property_valist (GESTimelineElement * self, if (error) goto cant_copy; - set_child_property_by_pspec (self, pspec, &value); + set_child_property_by_pspec (self, pspec, &value, NULL); g_param_spec_unref (pspec); g_value_unset (&value); diff --git a/ges/ges-timeline-element.h b/ges/ges-timeline-element.h index e2b9abb250..a812abb079 100644 --- a/ges/ges-timeline-element.h +++ b/ges/ges-timeline-element.h @@ -217,6 +217,9 @@ struct _GESTimelineElement * @set_child_property: Method for setting the child property given by * @pspec on @child to @value. Default implementation will use * g_object_set_property(). + * @set_child_property_full: Similar to @set_child_property, except + * setting can fail, with the @error being optionally set. Default + * implementation will call @set_child_property and return %TRUE. * @get_layer_priority: Get the #GESLayer:priority of the layer that this * element is part of. * @list_children_properties: List the children properties that have been @@ -265,15 +268,23 @@ struct _GESTimelineElementClass gboolean (*lookup_child) (GESTimelineElement *self, const gchar *prop_name, GObject **child, GParamSpec **pspec); GESTrackType (*get_track_types) (GESTimelineElement * self); - void (*set_child_property) (GESTimelineElement * self, GObject *child, - GParamSpec *pspec, GValue *value); + void (*set_child_property) (GESTimelineElement * self, + GObject *child, + GParamSpec *pspec, + GValue *value); guint32 (*get_layer_priority) (GESTimelineElement *self); /*< private > */ gboolean (*get_natural_framerate) (GESTimelineElement * self, gint *framerate_n, gint *framerate_d); + + gboolean (*set_child_property_full) (GESTimelineElement * self, + GObject *child, + GParamSpec *pspec, + const GValue *value, + GError ** error); /* Padding for API extension */ - gpointer _ges_reserved[GES_PADDING_LARGE - 5]; + gpointer _ges_reserved[GES_PADDING_LARGE - 6]; }; GES_API @@ -371,6 +382,11 @@ gboolean ges_timeline_element_set_child_property (GESTimeli const gchar *property_name, const GValue * value); GES_API +gboolean ges_timeline_element_set_child_property_full (GESTimelineElement *self, + const gchar *property_name, + const GValue * value, + GError ** error); +GES_API gboolean ges_timeline_element_get_child_property (GESTimelineElement *self, const gchar *property_name, GValue * value); diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index 5461432f03..ac4f2386d6 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -59,6 +59,7 @@ struct _GESTrackElementPrivate GstElement *element; /* The element contained in the nleobject (can be NULL) */ GESTrack *track; + gboolean has_internal_source_forbidden; gboolean has_internal_source; gboolean locked; /* If TRUE, then moves in sync with its controlling @@ -818,6 +819,12 @@ ges_track_element_set_has_internal_source (GESTrackElement * object, if (G_UNLIKELY (has_internal_source == object->priv->has_internal_source)) return; + if (has_internal_source && object->priv->has_internal_source_forbidden) { + GST_WARNING_OBJECT (object, "Setting an internal source for this " + "element is forbidden"); + return; + } + object->priv->has_internal_source = has_internal_source; if (!has_internal_source) { @@ -830,6 +837,13 @@ ges_track_element_set_has_internal_source (GESTrackElement * object, properties[PROP_HAS_INTERNAL_SOURCE]); } +void +ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement * + element) +{ + element->priv->has_internal_source_forbidden = TRUE; +} + /** * ges_track_element_set_track_type: * @object: A #GESTrackElement @@ -1124,7 +1138,7 @@ ges_track_element_set_layer_active (GESTrackElement * element, gboolean active) * ges_track_element_set_control_source(), and their values are the * corresponding created #GstControlBinding. * - * Returns: (element-type gchar* GstControlBinding)(transfer none): A + * Returns: (element-type gchar* GstControlBinding*)(transfer none): A * hash table containing all child-property-name/control-binding pairs * for @trackelement. */ diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index e93f36d12d..f12906f76d 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -3621,6 +3621,582 @@ GST_START_TEST (test_can_set_duration_limit) GST_END_TEST; +#define _assert_set_rate(element, prop_name, rate, val) \ +{ \ + GError *error = NULL; \ + if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \ + g_value_set_double (&val, rate); \ + else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \ + g_value_set_float (&val, rate); \ + \ + fail_unless (ges_timeline_element_set_child_property_full ( \ + GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \ + fail_if (error); \ + g_value_reset (&val); \ +} + +#define _assert_fail_set_rate(element, prop_name, rate, val, code) \ +{ \ + GError * error = NULL; \ + if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \ + g_value_set_double (&val, rate); \ + else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \ + g_value_set_float (&val, rate); \ + \ + fail_if (ges_timeline_element_set_child_property_full ( \ + GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \ + assert_GESError (error, code); \ + g_value_reset (&val); \ +} + +#define _assert_rate_equal(element, prop_name, rate, val) \ +{ \ + gdouble found = -1.0; \ + fail_unless (ges_timeline_element_get_child_property ( \ + GES_TIMELINE_ELEMENT (element), prop_name, &val)); \ + \ + if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \ + found = g_value_get_double (&val); \ + else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \ + found = g_value_get_float (&val); \ + \ + fail_unless (found == rate, "found %s: %g != expected: %g", found, \ + prop_name, rate); \ + g_value_reset (&val); \ +} + +GST_START_TEST (test_rate_effects_duration_limit) +{ + GESTimeline *timeline; + GESLayer *layer; + GESClip *clip; + GESTrackElement *source0, *source1; + GESTrackElement *overlay0, *overlay1, *videorate, *pitch; + GESTrack *track0, *track1; + gint limit_notify_count = 0; + GValue fval = G_VALUE_INIT; + GValue dval = G_VALUE_INIT; + + ges_init (); + + g_value_init (&fval, G_TYPE_FLOAT); + g_value_init (&dval, G_TYPE_DOUBLE); + + timeline = ges_timeline_new (); + track0 = GES_TRACK (ges_video_track_new ()); + track1 = GES_TRACK (ges_audio_track_new ()); + + fail_unless (ges_timeline_add_track (timeline, track0)); + fail_unless (ges_timeline_add_track (timeline, track1)); + + layer = ges_timeline_append_layer (timeline); + + /* place a dummy clip at the start of the layer */ + clip = GES_CLIP (ges_test_clip_new ()); + assert_set_start (clip, 0); + assert_set_duration (clip, 26); + + fail_unless (ges_layer_add_clip (layer, clip)); + + /* the clip we will be editing overlaps first clip by 16 at its start */ + clip = GES_CLIP (ges_test_clip_new ()); + + g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb), + &limit_notify_count); + + assert_set_start (clip, 10); + assert_set_duration (clip, 64); + + fail_unless (ges_layer_add_clip (layer, clip)); + + source0 = + ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE); + source1 = + ges_clip_find_track_element (clip, track1, GES_TYPE_AUDIO_TEST_SOURCE); + + fail_unless (source0); + fail_unless (source1); + + gst_object_unref (source0); + gst_object_unref (source1); + + assert_equals_int (limit_notify_count, 0); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 0, 64, GST_CLOCK_TIME_NONE); + + assert_set_inpoint (clip, 13); + + assert_equals_int (limit_notify_count, 0); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE); + + assert_set_max_duration (clip, 77); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + + /* add effects */ + overlay0 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay0, TRUE); + + videorate = GES_TRACK_ELEMENT (ges_effect_new ("videorate")); + fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (videorate))); + + overlay1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay")); + ges_track_element_set_has_internal_source (overlay1, TRUE); + + pitch = GES_TRACK_ELEMENT (ges_effect_new ("pitch")); + fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (pitch))); + + /* add overlay1 at highest priority */ + _assert_add (clip, overlay1); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + + _assert_set_rate (videorate, "rate", 4.0, dval); + _assert_rate_equal (videorate, "rate", 4.0, dval); + fail_unless (ges_track_add_element (track0, videorate)); + + /* cannot add videorate as it would cause the duration-limit to drop + * to 16, causing a full overlap */ + /* track keeps alive */ + fail_if (ges_container_add (GES_CONTAINER (clip), + GES_TIMELINE_ELEMENT (videorate))); + + /* setting to 1.0 makes it work again */ + _assert_set_rate (videorate, "rate", 1.0, dval); + _assert_add (clip, videorate); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + + /* add second overlay at lower priority */ + _assert_add (clip, overlay0); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + + /* also add a pitch element in another track */ + _assert_add (clip, pitch); + _assert_set_rate (pitch, "rate", 1.0, fval); + _assert_set_rate (pitch, "tempo", 1.0, fval); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + fail_unless (ges_track_element_get_track (overlay0) == track0); + fail_unless (ges_track_element_get_track (videorate) == track0); + fail_unless (ges_track_element_get_track (overlay1) == track0); + fail_unless (ges_track_element_get_track (pitch) == track1); + + /* flow in track0: + * source0 -> overlay0 -> videorate -> overlay1 -> timeline output + * + * flow in track1: + * source1 -> pitch -> timeline output + */ + + /* cannot set the rates to 4.0 since this would cause a full overlap */ + _assert_fail_set_rate (videorate, "rate", 4.0, dval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _assert_fail_set_rate (pitch, "rate", 4.0, fval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + _assert_fail_set_rate (pitch, "tempo", 4.0, fval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* limit overlay0 */ + assert_set_max_duration (overlay0, 91); + + assert_equals_int (limit_notify_count, 1); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + assert_set_inpoint (overlay0, 59); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 1.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* can set pitch rate to 2.0, but not videorate rate because videorate + * shares a track with overlay0 */ + + _assert_set_rate (pitch, "rate", 2.0, fval); + assert_equals_int (limit_notify_count, 2); + _assert_fail_set_rate (videorate, "rate", 2.0, dval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + assert_equals_int (limit_notify_count, 2); + /* can't set tempo to 2.0 since overall effect would bring duration + * limit too low */ + _assert_fail_set_rate (pitch, "tempo", 2.0, fval, + GES_ERROR_INVALID_OVERLAP_IN_TRACK); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 2.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* cannot set in-point of clip because pitch would cause limit to go + * to 16 */ + assert_fail_set_inpoint (clip, 45); + /* same for max-duration of source1 */ + assert_fail_set_max_duration (source1, 45); + + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 1.0, dval); + _assert_rate_equal (pitch, "rate", 2.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* can set rate to 0.5 */ + _assert_set_rate (videorate, "rate", 0.5, dval); + + /* no change yet, since pitch rate is still 2.0 */ + assert_equals_int (limit_notify_count, 2); + _assert_duration_limit (clip, 32); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 2.0, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + _assert_set_rate (pitch, "rate", 0.5, fval); + + assert_equals_int (limit_notify_count, 3); + /* duration-limit is 64 because overlay0 only has 32 nanoseconds of + * content, stretched to 64 by videorate */ + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* setting the max-duration of the sources does not change the limit + * since the limit on overlay0 is fine. + * Note that pitch handles the unlimited duration (GST_CLOCK_TIME_NONE) + * without any problems */ + assert_set_max_duration (clip, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + assert_set_max_duration (clip, 77); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* limit overlay1. It should not be changes by the videorate element + * since it acts at a lower priority + * first make it last longer, so no change in duration-limit */ + + assert_set_max_duration (overlay1, 81); + + assert_equals_int (limit_notify_count, 3); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, 81); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* now make it shorter */ + assert_set_inpoint (overlay1, 51); + + assert_equals_int (limit_notify_count, 4); + _assert_duration_limit (clip, 30); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, 91); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* remove the overlay0 limit */ + assert_set_max_duration (overlay0, GST_CLOCK_TIME_NONE); + + /* no change because of overlay1 */ + assert_equals_int (limit_notify_count, 4); + _assert_duration_limit (clip, 30); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + assert_set_max_duration (overlay1, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 5); + _assert_duration_limit (clip, 128); + /* can set up to the limit */ + assert_set_duration (clip, 128); + + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 128, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 128, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 128, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 128, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 128, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 128, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 128, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* tempo contributes the same factor as rate */ + + _assert_set_rate (pitch, "tempo", 2.0, fval); + + assert_equals_int (limit_notify_count, 6); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 2.0, fval); + + _assert_set_rate (videorate, "rate", 0.1, dval); + assert_equals_int (limit_notify_count, 6); + _assert_set_rate (pitch, "tempo", 0.5, dval); + + assert_equals_int (limit_notify_count, 7); + _assert_duration_limit (clip, 256); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.1, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 0.5, fval); + + _assert_set_rate (pitch, "tempo", 1.0, dval); + assert_equals_int (limit_notify_count, 8); + _assert_set_rate (videorate, "rate", 0.5, dval); + + assert_equals_int (limit_notify_count, 8); + _assert_duration_limit (clip, 128); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* make videorate in-active */ + fail_unless (ges_track_element_set_active (videorate, FALSE)); + + assert_equals_int (limit_notify_count, 9); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + fail_unless (ges_track_element_set_active (videorate, TRUE)); + + assert_equals_int (limit_notify_count, 10); + _assert_duration_limit (clip, 128); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + _assert_rate_equal (pitch, "rate", 0.5, fval); + _assert_rate_equal (pitch, "tempo", 1.0, fval); + + /* removing pitch, same effect as making inactive */ + _assert_remove (clip, pitch); + + assert_equals_int (limit_notify_count, 11); + _assert_duration_limit (clip, 64); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + + /* no max-duration will give unlimited limit */ + assert_set_max_duration (source1, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 12); + _assert_duration_limit (clip, 128); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + + assert_set_max_duration (source0, GST_CLOCK_TIME_NONE); + + assert_equals_int (limit_notify_count, 13); + _assert_duration_limit (clip, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE); + CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE); + _assert_rate_equal (videorate, "rate", 0.5, dval); + + gst_object_unref (timeline); + + g_value_unset (&fval); + g_value_unset (&dval); + + ges_deinit (); +} + +GST_END_TEST; + + GST_START_TEST (test_children_properties_contain) { GESTimeline *timeline; @@ -4232,6 +4808,7 @@ ges_suite (void) tcase_add_test (tc_chain, test_children_max_duration); tcase_add_test (tc_chain, test_duration_limit); tcase_add_test (tc_chain, test_can_set_duration_limit); + tcase_add_test (tc_chain, test_rate_effects_duration_limit); 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); -- 2.34.1