clip: add method for adding top effects

Unlike ges_container_add, this lets you set the index and will check
that track selection did not fail. This is useful for time effects whose
addition would create an unsupported timeline configuration.

Also can use the clip add error in ges_timeline_add_clip to let the user
know when adding a clip to a layer that its in-point is set larger than
the max-duration of its core children.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
This commit is contained in:
Henry Wilkes 2020-05-15 14:53:49 +01:00
parent 478db52ded
commit 53d335b4ed
6 changed files with 536 additions and 49 deletions

View file

@ -92,12 +92,12 @@
*
* ## Effects
*
* Some subclasses (#GESSourceClip and #GESBaseEffect) may also allow
* Some subclasses (#GESSourceClip and #GESBaseEffectClip) may also allow
* their objects to have additional non-core #GESBaseEffect-s elements as
* children. These are additional effects that are applied to the output
* data of the core elements. They can be added to the clip using
* ges_container_add(), which will take care of adding the effect to the
* timeline's tracks. The new effect will be placed between the clip's
* ges_clip_add_top_effect(), which will take care of adding the effect to
* the timeline's tracks. The new effect will be placed between the clip's
* core track elements and its other effects. As such, the newly added
* effect will be applied to any source data **before** the other existing
* effects. You can change the ordering of effects using
@ -169,6 +169,11 @@ struct _GESClipPrivate
gboolean prevent_duration_limit_update;
gboolean allow_any_remove;
gboolean use_effect_priority;
guint32 effect_priority;
GError *add_error;
GError *remove_error;
};
enum
@ -1435,9 +1440,37 @@ _compute_height (GESContainer * container)
_ges_container_set_height (container, max_prio - min_prio + 1);
}
void
ges_clip_take_add_error (GESClip * clip, GError ** error)
{
GESClipPrivate *priv = clip->priv;
g_clear_error (error);
if (error) {
if (*error) {
GST_ERROR_OBJECT (clip, "Error not handled: %s", (*error)->message);
g_error_free (*error);
}
*error = priv->add_error;
} else {
g_clear_error (&priv->add_error);
}
priv->add_error = NULL;
}
void
ges_clip_set_add_error (GESClip * clip, GError * error)
{
GESClipPrivate *priv = clip->priv;
g_clear_error (&priv->add_error);
priv->add_error = error;
}
static gboolean
_add_child (GESContainer * container, GESTimelineElement * element)
{
gboolean ret = FALSE;
GESClip *self = GES_CLIP (container);
GESTrackElement *track_el = GES_TRACK_ELEMENT (element);
GESClipClass *klass = GES_CLIP_GET_CLASS (self);
@ -1448,6 +1481,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
GESAsset *asset, *creator_asset;
gboolean prev_prevent = priv->prevent_duration_limit_update;
GList *tmp;
GError *error = NULL;
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
@ -1456,7 +1490,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
"because its timeline is %" GST_PTR_FORMAT " rather than the "
"clip's timeline %" GST_PTR_FORMAT, GES_ARGS (element),
element->timeline, timeline);
return FALSE;
goto done;
}
asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
@ -1466,7 +1500,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
"Cannot add the track element %" GES_FORMAT " as a child "
"because it is a core element created by another clip with a "
"different asset to the current clip's asset", GES_ARGS (element));
return FALSE;
goto done;
}
track = ges_track_element_get_track (track_el);
@ -1480,7 +1514,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
"because its track %" GST_PTR_FORMAT " is part of the timeline %"
GST_PTR_FORMAT " rather than the clip's timeline %" GST_PTR_FORMAT,
GES_ARGS (element), track, ges_track_get_timeline (track), timeline);
return FALSE;
goto done;
}
/* NOTE: notifies are currently frozen by ges_container_add */
@ -1522,7 +1556,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
" because it is in the same track %" GST_PTR_FORMAT " as an "
"existing core child %" GES_FORMAT, GES_ARGS (element), track,
GES_ARGS (core));
return FALSE;
goto done;
}
data = _duration_limit_data_new (track_el);
@ -1531,21 +1565,37 @@ _add_child (GESContainer * container, GESTimelineElement * element)
child_data = _duration_limit_data_list_with_data (self, data);
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot add core %" GES_FORMAT " as a "
if (!_can_update_duration_limit (self, child_data, &error)) {
GST_INFO_OBJECT (self, "Cannot add core %" GES_FORMAT " as a "
"child because the duration-limit cannot be adjusted",
GES_ARGS (element));
return FALSE;
goto done;
}
}
if (GES_CLOCK_TIME_IS_LESS (element->maxduration, new_inpoint)) {
GST_INFO_OBJECT (self, "Can not set the in-point of the "
"element %" GES_FORMAT " to %" GST_TIME_FORMAT " because its "
"max-duration is %" GST_TIME_FORMAT, GES_ARGS (element),
GST_TIME_ARGS (new_inpoint), GST_TIME_ARGS (element->maxduration));
g_set_error (&error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
"Cannot add the child \"%s\" to clip \"%s\" because its max-"
"duration is %" GST_TIME_FORMAT ", which is less than the in-"
"point of the clip %" GST_TIME_FORMAT, element->name,
GES_TIMELINE_ELEMENT_NAME (self),
GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (new_inpoint));
goto done;
}
/* adding can fail if the max-duration of the element is smaller
* than the current in-point of the clip */
if (!_set_inpoint0 (element, new_inpoint)) {
GST_WARNING_OBJECT (self, "Could not set the in-point of the "
"element %" GES_FORMAT " to %" GST_TIME_FORMAT ". Not adding "
"as a child", GES_ARGS (element), GST_TIME_ARGS (new_inpoint));
return FALSE;
goto done;
}
_set_priority0 (element, new_prio);
@ -1556,10 +1606,14 @@ _add_child (GESContainer * container, GESTimelineElement * element)
* to make room. */
/* new priority is the lowest priority effect */
new_prio = min_prio;
for (tmp = container->children; tmp; tmp = tmp->next) {
if (_IS_TOP_EFFECT (tmp->data))
new_prio = MAX (new_prio, _PRIORITY (tmp->data) + 1);
if (priv->use_effect_priority) {
new_prio = priv->effect_priority;
} else {
new_prio = min_prio;
for (tmp = container->children; tmp; tmp = tmp->next) {
if (_IS_TOP_EFFECT (tmp->data))
new_prio = MAX (new_prio, _PRIORITY (tmp->data) + 1);
}
}
/* make sure higher than core */
for (tmp = container->children; tmp; tmp = tmp->next) {
@ -1576,7 +1630,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT
" because its track %" GST_PTR_FORMAT " does not contain one "
"of the clip's core children", GES_ARGS (element), track);
return FALSE;
goto done;
}
data = _duration_limit_data_new (track_el);
@ -1592,11 +1646,11 @@ _add_child (GESContainer * container, GESTimelineElement * element)
data->priority++;
}
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot add effect %" GES_FORMAT " as "
if (!_can_update_duration_limit (self, child_data, &error)) {
GST_INFO_OBJECT (self, "Cannot add effect %" GES_FORMAT " as "
"a child because the duration-limit cannot be adjusted",
GES_ARGS (element));
return FALSE;
goto done;
}
}
@ -1642,13 +1696,46 @@ _add_child (GESContainer * container, GESTimelineElement * element)
GST_WARNING_OBJECT (self, "Cannot add the track element %"
GES_FORMAT " because it is not a core element created by the "
"clip itself", GES_ARGS (element));
return FALSE;
goto done;
}
_set_start0 (element, GES_TIMELINE_ELEMENT_START (self));
_set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (self));
return TRUE;
ret = TRUE;
done:
if (error)
ges_clip_set_add_error (self, error);
return ret;
}
void
ges_clip_take_remove_error (GESClip * clip, GError ** error)
{
GESClipPrivate *priv = clip->priv;
g_clear_error (error);
if (error) {
if (*error) {
GST_ERROR ("Error not handled: %s", (*error)->message);
g_error_free (*error);
}
*error = priv->remove_error;
} else {
g_clear_error (&priv->remove_error);
}
priv->remove_error = NULL;
}
void
ges_clip_set_remove_error (GESClip * clip, GError * error)
{
GESClipPrivate *priv = clip->priv;
g_clear_error (&priv->remove_error);
priv->remove_error = error;
}
static gboolean
@ -1667,6 +1754,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
ges_track_element_get_track (el)) {
GList *child_data = NULL;
GList *tmp;
GError *error = NULL;
for (tmp = container->children; tmp; tmp = tmp->next) {
GESTrackElement *child = tmp->data;
@ -1676,8 +1764,9 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
g_list_prepend (child_data, _duration_limit_data_new (child));
}
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot remove the child %" GES_FORMAT
if (!_can_update_duration_limit (self, child_data, &error)) {
ges_clip_set_remove_error (self, error);
GST_INFO_OBJECT (self, "Cannot remove the child %" GES_FORMAT
" because the duration-limit cannot be adjusted", GES_ARGS (el));
return FALSE;
}
@ -2249,6 +2338,11 @@ ges_clip_dispose (GObject * object)
self->priv->copied_track_elements = NULL;
g_clear_object (&self->priv->copied_layer);
g_clear_error (&self->priv->add_error);
self->priv->add_error = NULL;
g_clear_error (&self->priv->remove_error);
self->priv->remove_error = NULL;
G_OBJECT_CLASS (ges_clip_parent_class)->dispose (object);
}
@ -2716,6 +2810,143 @@ _cmp_children_by_priority (gconstpointer a_p, gconstpointer b_p)
return 0;
}
/**
* ges_clip_add_top_effect:
* @clip: A #GESClip
* @effect: A top effect to add
* @index: The index to add @effect at, or -1 to add at the highest
* @error: (nullable): Return location for an error
*
* Add a top effect to a clip at the given index.
*
* Unlike using ges_container_add(), this allows you to set the index
* in advance. It will also check that no error occurred during the track
* selection for the effect.
*
* Note, only subclasses of #GESClipClass that have
* #GES_CLIP_CLASS_CAN_ADD_EFFECTS set to %TRUE (such as #GESSourceClip
* and #GESBaseEffectClip) can have additional top effects added.
*
* Note, if the effect is a time effect, this may be refused if the clip
* would not be able to adapt itself once the effect is added.
*
* Returns: %TRUE if @effect was successfully added to @clip at @index.
*/
gboolean
ges_clip_add_top_effect (GESClip * clip, GESBaseEffect * effect, gint index,
GError ** error)
{
GESClipPrivate *priv;
GList *top_effects;
GESTimelineElement *replace;
GESTimeline *timeline;
gboolean res;
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
priv = clip->priv;
if (index >= 0) {
top_effects = ges_clip_get_top_effects (clip);
replace = g_list_nth_data (top_effects, index);
if (replace) {
priv->use_effect_priority = TRUE;
priv->effect_priority = replace->priority;
}
g_list_free_full (top_effects, gst_object_unref);
}
/* otherwise the default _add_child will place it at the lowest
* priority / highest index */
timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
if (timeline)
ges_timeline_set_track_selection_error (timeline, FALSE, NULL);
/* note, if several tracks are selected, this may lead to several
* effects being added to the clip.
* The first effect we are adding will use the set effect_priority.
* The error on the timeline could be from any of the copies */
ges_clip_set_add_error (clip, NULL);
res = ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect));
priv->use_effect_priority = FALSE;
if (!res) {
/* if adding fails, there should have been no track selection, which
* means no other elements were added so the clip, so the adding error
* for the effect, if any, should still be available on the clip */
ges_clip_take_add_error (clip, error);
return FALSE;
}
if (timeline && ges_timeline_take_track_selection_error (timeline, error))
goto remove;
return TRUE;
remove:
if (!ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (effect)))
GST_ERROR_OBJECT (clip, "Failed to remove effect %" GES_FORMAT,
GES_ARGS (effect));
return FALSE;
}
static gboolean
_is_added_effect (GESClip * clip, GESBaseEffect * effect)
{
if (GES_TIMELINE_ELEMENT_PARENT (effect) != GES_TIMELINE_ELEMENT (clip)) {
GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT
" does not belong to this clip", GES_ARGS (effect));
return FALSE;
}
if (!_IS_TOP_EFFECT (effect)) {
GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT " is not a top "
"effect of this clip (it is a core element of the clip)",
GES_ARGS (effect));
return FALSE;
}
return TRUE;
}
/**
* ges_clip_remove_top_effect:
* @clip: A #GESClip
* @effect: The top effect to remove
* @error: (nullable): Return location for an error
*
* Remove a top effect from the clip.
*
* Note, if the effect is a time effect, this may be refused if the clip
* would not be able to adapt itself once the effect is removed.
*
* Returns: %TRUE if @effect was successfully added to @clip at @index.
*/
gboolean
ges_clip_remove_top_effect (GESClip * clip, GESBaseEffect * effect,
GError ** error)
{
gboolean res;
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (!_is_added_effect (clip, effect))
return FALSE;
ges_clip_set_remove_error (clip, NULL);
res = ges_container_remove (GES_CONTAINER (clip),
GES_TIMELINE_ELEMENT (effect));
if (!res)
ges_clip_take_remove_error (clip, error);
return res;
}
/**
* ges_clip_get_top_effects:
* @clip: A #GESClip
@ -2747,23 +2978,6 @@ ges_clip_get_top_effects (GESClip * clip)
return g_list_sort (ret, _cmp_children_by_priority);
}
static gboolean
_is_added_effect (GESClip * clip, GESBaseEffect * effect)
{
if (GES_TIMELINE_ELEMENT_PARENT (effect) != GES_TIMELINE_ELEMENT (clip)) {
GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT
" doe not belong to this clip", GES_ARGS (effect));
return FALSE;
}
if (!_IS_TOP_EFFECT (effect)) {
GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT " is not a top "
"effect of this clip (it is a core element of the clip)",
GES_ARGS (effect));
return FALSE;
}
return TRUE;
}
/**
* ges_clip_get_top_effect_index:
* @clip: A #GESClip
@ -4059,18 +4273,24 @@ ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
/* copy if the element is already in a track */
if (current_track) {
/* TODO: rather than add the effect at the next highest priority, we
* want to add copied effect into the same EffectCollection, which all
* share the same priority/index */
if (_IS_TOP_EFFECT (child)) {
clip->priv->use_effect_priority = TRUE;
/* add at next lowest priority */
clip->priv->effect_priority = GES_TIMELINE_ELEMENT_PRIORITY (child) + 1;
}
el = ges_clip_copy_track_element_into (clip, child, GST_CLOCK_TIME_NONE);
clip->priv->use_effect_priority = FALSE;
if (!el) {
GST_ERROR_OBJECT (clip, "Could not add a copy of the track element %"
GES_FORMAT " to the clip so cannot add it to the track %"
GST_PTR_FORMAT, GES_ARGS (child), track);
return NULL;
}
if (_IS_TOP_EFFECT (child)) {
/* add at next lowest priority */
ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (el),
ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (child)) + 1);
}
} else {
el = child;
}

View file

@ -183,6 +183,15 @@ gboolean ges_clip_move_to_layer_full (GESClip * clip,
* Effects *
****************************************************/
GES_API
gboolean ges_clip_add_top_effect (GESClip * clip,
GESBaseEffect * effect,
gint index,
GError ** error);
GES_API
gboolean ges_clip_remove_top_effect (GESClip * clip,
GESBaseEffect * effect,
GError ** error);
GES_API
GList* ges_clip_get_top_effects (GESClip * clip);
GES_API
gint ges_clip_get_top_effect_position (GESClip * clip,

View file

@ -121,6 +121,14 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position, GError ** error);
G_GNUC_INTERNAL void
ges_timeline_set_track_selection_error (GESTimeline * timeline,
gboolean was_error,
GError * error);
G_GNUC_INTERNAL gboolean
ges_timeline_take_track_selection_error (GESTimeline * timeline,
GError ** error);
G_GNUC_INTERNAL void
timeline_add_group (GESTimeline *timeline,
GESGroup *group);
@ -420,6 +428,10 @@ G_GNUC_INTERNAL gboolean ges_clip_can_set_time_property_of_child (GESCl
G_GNUC_INTERNAL GstClockTime ges_clip_duration_limit_with_new_children_inpoints (GESClip * clip, GHashTable * child_inpoints);
G_GNUC_INTERNAL GstClockTime ges_clip_get_core_internal_time_from_timeline_time (GESClip * clip, GstClockTime timeline_time, gboolean * no_core, GError ** error);
G_GNUC_INTERNAL void ges_clip_empty_from_track (GESClip * clip, GESTrack * track);
G_GNUC_INTERNAL void ges_clip_set_add_error (GESClip * clip, GError * error);
G_GNUC_INTERNAL void ges_clip_take_add_error (GESClip * clip, GError ** error);
G_GNUC_INTERNAL void ges_clip_set_remove_error (GESClip * clip, GError * error);
G_GNUC_INTERNAL void ges_clip_take_remove_error (GESClip * clip, GError ** error);
/****************************************************
* GESLayer *

View file

@ -1533,7 +1533,7 @@ ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving)
}
}
static void
void
ges_timeline_set_track_selection_error (GESTimeline * timeline,
gboolean was_error, GError * error)
{
@ -1549,7 +1549,7 @@ ges_timeline_set_track_selection_error (GESTimeline * timeline,
UNLOCK_DYN (timeline);
}
static gboolean
gboolean
ges_timeline_take_track_selection_error (GESTimeline * timeline,
GError ** error)
{
@ -1746,12 +1746,15 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
* copy will be added to the clip before the track is selected, so
* the track will not be set in the child-added signal */
ges_timeline_set_track_selection_error (timeline, FALSE, NULL);
ges_clip_set_add_error (clip, NULL);
if (!ges_container_add (GES_CONTAINER (clip), el)) {
no_errors = FALSE;
GST_ERROR_OBJECT (clip, "Could not add the core element %s "
"to the clip", el->name);
if (!error)
GST_ERROR_OBJECT (clip, "Could not add the core element %s "
"to the clip", el->name);
}
gst_object_unref (el);
ges_clip_take_add_error (clip, error);
if (error && !no_errors)
goto done;

View file

@ -2010,6 +2010,33 @@ GST_START_TEST (test_children_time_setters)
GST_END_TEST;
GST_START_TEST (test_not_enough_internal_content_for_core)
{
GESTimeline *timeline;
GESLayer *layer;
GESAsset *asset;
GError *error = NULL;
ges_init ();
timeline = ges_timeline_new_audio_video ();
layer = ges_timeline_append_layer (timeline);
asset = ges_asset_request (GES_TYPE_TEST_CLIP, "max-duration=30", NULL);
fail_unless (asset);
fail_if (ges_layer_add_asset_full (layer, asset, 0, 31, 10,
GES_TRACK_TYPE_UNKNOWN, &error));
assert_GESError (error, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT);
gst_object_unref (timeline);
gst_object_unref (asset);
ges_deinit ();
}
GST_END_TEST;
GST_START_TEST (test_can_add_effect)
{
struct CanAddEffectData
@ -5219,6 +5246,7 @@ ges_suite (void)
tcase_add_test (tc_chain, test_clip_find_track_element);
tcase_add_test (tc_chain, test_effects_priorities);
tcase_add_test (tc_chain, test_children_time_setters);
tcase_add_test (tc_chain, test_not_enough_internal_content_for_core);
tcase_add_test (tc_chain, test_can_add_effect);
tcase_add_test (tc_chain, test_children_active);
tcase_add_test (tc_chain, test_children_inpoint);

View file

@ -674,6 +674,220 @@ GST_START_TEST (test_split_clip_effect_priorities)
GST_END_TEST;
#define _NO_ERROR -1
#define _set_rate(videorate, rate, error_code) \
{ \
g_value_set_double (&val, rate); \
if (error_code != _NO_ERROR) { \
fail_if (ges_timeline_element_set_child_property_full ( \
GES_TIMELINE_ELEMENT (videorate), "rate", &val, &error)); \
assert_GESError (error, error_code); \
} else { \
fail_unless (ges_timeline_element_set_child_property_full ( \
GES_TIMELINE_ELEMENT (videorate), "rate", &val, &error)); \
fail_if (error); \
} \
}
#define _add_effect(clip, effect, _index, error_code) \
{ \
gint index = _index; \
if (error_code != _NO_ERROR) { \
fail_if (ges_clip_add_top_effect (clip, effect, index, &error)); \
assert_GESError (error, error_code); \
} else { \
GList *effects; \
gboolean res = ges_clip_add_top_effect (clip, effect, index, &error); \
fail_unless (res, "Adding effect " #effect " failed: %s", \
error ? error->message : "No error produced"); \
fail_if (error); \
effects = ges_clip_get_top_effects (clip); \
fail_unless (g_list_find (effects, effect)); \
if (index < 0 || index >= g_list_length (effects)) \
index = g_list_length (effects) - 1; \
assert_equals_int (ges_clip_get_top_effect_index (clip, effect), index); \
fail_unless (g_list_nth_data (effects, index) == effect); \
g_list_free_full (effects, gst_object_unref); \
} \
}
#define _remove_effect(clip, effect, error_code) \
{ \
if (error_code != _NO_ERROR) { \
fail_if (ges_clip_remove_top_effect (clip, effect, &error)); \
assert_GESError (error, error_code); \
} else { \
GList *effects; \
gboolean res = ges_clip_remove_top_effect (clip, effect, &error); \
fail_unless (res, "Removing effect " #effect " failed: %s", \
error ? error->message : "No error produced"); \
fail_if (error); \
effects = ges_clip_get_top_effects (clip); \
fail_if (g_list_find (effects, effect)); \
g_list_free_full (effects, gst_object_unref); \
} \
}
#define _move_effect(clip, effect, index, error_code) \
{ \
if (error_code != _NO_ERROR) { \
fail_if ( \
ges_clip_set_top_effect_index_full (clip, effect, index, &error)); \
assert_GESError (error, error_code); \
} else { \
GList *effects; \
gboolean res = \
ges_clip_set_top_effect_index_full (clip, effect, index, &error); \
fail_unless (res, "Moving effect " #effect " failed: %s", \
error ? error->message : "No error produced"); \
fail_if (error); \
effects = ges_clip_get_top_effects (clip); \
fail_unless (g_list_find (effects, effect)); \
assert_equals_int (ges_clip_get_top_effect_index (clip, effect), index); \
fail_unless (g_list_nth_data (effects, index) == effect); \
g_list_free_full (effects, gst_object_unref); \
} \
}
GST_START_TEST (test_move_time_effect)
{
GESTimeline *timeline;
GESTrack *track;
GESLayer *layer;
GESAsset *asset;
GESClip *clip;
GESBaseEffect *rate0, *rate1, *overlay;
GError *error = NULL;
GValue val = G_VALUE_INIT;
ges_init ();
g_value_init (&val, G_TYPE_DOUBLE);
timeline = ges_timeline_new ();
track = GES_TRACK (ges_video_track_new ());
fail_unless (ges_timeline_add_track (timeline, track));
layer = ges_timeline_append_layer (timeline);
/* add a dummy clip for overlap */
asset = ges_asset_request (GES_TYPE_TEST_CLIP, "max-duration=16", &error);
fail_unless (asset);
fail_if (error);
fail_unless (ges_layer_add_asset_full (layer, asset, 0, 0, 16,
GES_TRACK_TYPE_UNKNOWN, &error));
fail_if (error);
clip = GES_CLIP (ges_asset_extract (asset, &error));
fail_unless (clip);
fail_if (error);
assert_set_start (clip, 8);
assert_set_duration (clip, 16);
rate0 = GES_BASE_EFFECT (ges_effect_new ("videorate"));
rate1 = GES_BASE_EFFECT (ges_effect_new ("videorate"));
overlay = GES_BASE_EFFECT (ges_effect_new ("textoverlay"));
ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (overlay), TRUE);
/* only has 8ns of content */
assert_set_inpoint (overlay, 13);
assert_set_max_duration (overlay, 21);
_set_rate (rate0, 2.0, _NO_ERROR);
_set_rate (rate1, 0.5, _NO_ERROR);
/* keep alive */
gst_object_ref (clip);
gst_object_ref (rate0);
gst_object_ref (rate1);
gst_object_ref (overlay);
/* cannot add to layer with rate effect because it would cause a full
* overlap */
_add_effect (clip, rate0, 0, _NO_ERROR);
fail_if (ges_layer_add_clip_full (layer, clip, &error));
assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
_remove_effect (clip, rate0, _NO_ERROR);
/* same with overlay */
_add_effect (clip, overlay, 0, _NO_ERROR);
fail_if (ges_layer_add_clip_full (layer, clip, &error));
assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
_remove_effect (clip, overlay, _NO_ERROR);
CHECK_OBJECT_PROPS (clip, 8, 0, 16);
fail_unless (ges_layer_add_clip_full (layer, clip, &error));
fail_if (error);
CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 16);
/* can't add rate0 or overlay in the same way */
_add_effect (clip, rate0, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
_add_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* rate1 extends the duration-limit instead */
_add_effect (clip, rate1, 0, _NO_ERROR);
/* can't add overlay next to the timeline */
_add_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* but next to source is ok */
_add_effect (clip, overlay, 1, _NO_ERROR);
/* can't add rate0 after overlay */
_add_effect (clip, rate0, 1, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* but before is ok */
_add_effect (clip, rate0, -1, _NO_ERROR);
/* can't move rate0 to end */
_move_effect (clip, rate0, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* can't move overlay to start or end */
_move_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
_move_effect (clip, overlay, 2, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* can now move: swap places with rate1 */
_set_rate (rate0, 0.5, _NO_ERROR);
_move_effect (clip, rate0, 0, _NO_ERROR);
_move_effect (clip, rate1, 2, _NO_ERROR);
_set_rate (rate1, 2.0, _NO_ERROR);
/* cannot speed up either rate too much */
_set_rate (rate0, 1.0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
_set_rate (rate1, 4.0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* cannot remove rate0 which is slowing down */
_remove_effect (clip, rate0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* removing the speed-up is fine */
_remove_effect (clip, rate1, _NO_ERROR);
/* removing the overlay is fine */
_remove_effect (clip, overlay, _NO_ERROR);
CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 16);
assert_set_max_duration (clip, 8);
CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 8);
/* still can't remove the slow down since it is the only thing stopping
* a full overlap */
_remove_effect (clip, rate0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
gst_object_unref (clip);
/* shouldn't have any problems when removing from the layer */
fail_unless (ges_layer_remove_clip (layer, clip));
g_value_reset (&val);
gst_object_unref (rate0);
gst_object_unref (rate1);
gst_object_unref (overlay);
gst_object_unref (asset);
gst_object_unref (timeline);
ges_deinit ();
}
GST_END_TEST;
static Suite *
ges_suite (void)
@ -691,6 +905,7 @@ ges_suite (void)
tcase_add_test (tc_chain, test_effect_set_properties);
tcase_add_test (tc_chain, test_clip_signals);
tcase_add_test (tc_chain, test_split_clip_effect_priorities);
tcase_add_test (tc_chain, test_move_time_effect);
return s;
}