mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-29 07:12:36 +00:00
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: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
This commit is contained in:
parent
142456d8ba
commit
571120dcfb
10 changed files with 1419 additions and 69 deletions
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
210
ges/ges-clip.c
210
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;
|
||||
GESTrack *track = ((DurationLimitData *) (start->data))->track;
|
||||
|
||||
end = start;
|
||||
do {
|
||||
end = end->next;
|
||||
} while (end && ((DurationLimitData *) (end->data))->track == track);
|
||||
|
||||
if (track) {
|
||||
GstClockTime track_limit = _INTERNAL_LIMIT (data);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (self, "duration-limit for track %" GST_PTR_FORMAT
|
||||
" is %" GST_TIME_FORMAT, track, GST_TIME_ARGS (track_limit));
|
||||
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.
|
||||
|
|
125
ges/ges-effect.c
125
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. 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.
|
||||
* 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.
|
||||
*
|
||||
* A reference to the GESEffectClass can be obtained as follows:
|
||||
* 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().
|
||||
*
|
||||
* Note, you can obtain a reference to the GESEffectClass using
|
||||
*
|
||||
* ```
|
||||
* 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,
|
||||
|
|
|
@ -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 *
|
||||
****************************************************/
|
||||
|
|
|
@ -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,6 +1855,40 @@ 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!
|
||||
*
|
||||
* Returns: %TRUE if the property was found and set.
|
||||
*/
|
||||
gboolean
|
||||
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, error);
|
||||
|
||||
not_found:
|
||||
{
|
||||
GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -1849,22 +1898,8 @@ gboolean
|
|||
ges_timeline_element_set_child_property (GESTimelineElement * self,
|
||||
const gchar * property_name, const GValue * value)
|
||||
{
|
||||
GParamSpec *pspec;
|
||||
GObject *child;
|
||||
|
||||
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
|
||||
|
||||
if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec))
|
||||
goto not_found;
|
||||
|
||||
return set_child_property_by_pspec (self, pspec, value);
|
||||
|
||||
not_found:
|
||||
{
|
||||
GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
return ges_timeline_element_set_child_property_full (self, property_name,
|
||||
value, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue