From 142456d8ba802485d065b8306131837e3cce1521 Mon Sep 17 00:00:00 2001 From: Henry Wilkes Date: Fri, 15 May 2020 14:25:01 +0100 Subject: [PATCH] errors: added edit errors Added more errors to GES_ERROR for when edits fail (other than programming or usage errors). Also promoted some GST messages if they related to a usage error. Also added explanation of timeline overlap rules in user docs. Part-of: --- ges/ges-clip.c | 214 ++++++++++++------- ges/ges-clip.h | 55 +++-- ges/ges-gerror.h | 17 +- ges/ges-group.c | 2 +- ges/ges-internal.h | 22 +- ges/ges-layer.c | 102 ++++++--- ges/ges-layer.h | 79 ++++--- ges/ges-timeline-element.c | 73 ++++--- ges/ges-timeline-element.h | 8 + ges/ges-timeline-tree.c | 318 ++++++++++++++++++++-------- ges/ges-timeline-tree.h | 15 +- ges/ges-timeline.c | 253 ++++++++++++++++------ ges/ges-track-element.c | 44 ++-- ges/ges-track.c | 73 +++++-- ges/ges-track.h | 32 ++- tests/check/ges/clip.c | 46 ++-- tests/check/ges/test-utils.h | 9 + tests/check/python/common.py | 49 ++++- tests/check/python/test_timeline.py | 219 +++++++++++++------ 19 files changed, 1151 insertions(+), 479 deletions(-) diff --git a/ges/ges-clip.c b/ges/ges-clip.c index d1ff7a31f3..9cd79f59ac 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -212,10 +212,6 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip, (GST_CLOCK_TIME_IS_VALID (a) ? \ (GST_CLOCK_TIME_IS_VALID (b) ? MIN (a, b) : a) : b) \ -#define _CLOCK_TIME_IS_LESS(first, second) \ - (GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \ - || first < second)) - /**************************************************** * duration-limit * ****************************************************/ @@ -366,7 +362,7 @@ _update_duration_limit (GESClip * self) GST_INFO_OBJECT (self, "duration-limit for the clip is %" GST_TIME_FORMAT, GST_TIME_ARGS (duration_limit)); - if (_CLOCK_TIME_IS_LESS (duration_limit, element->duration) + if (GES_CLOCK_TIME_IS_LESS (duration_limit, element->duration) && !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) { gboolean res; @@ -379,7 +375,7 @@ _update_duration_limit (GESClip * self) if (element->timeline) res = timeline_tree_trim (timeline_get_tree (element->timeline), element, 0, GST_CLOCK_DIFF (duration_limit, element->duration), - GES_EDGE_END, 0); + GES_EDGE_END, 0, NULL); else res = ges_timeline_element_set_duration (element, duration_limit); @@ -396,18 +392,18 @@ _update_duration_limit (GESClip * self) /* transfer full of child_data */ static gboolean -_can_update_duration_limit (GESClip * self, GList * child_data) +_can_update_duration_limit (GESClip * self, GList * child_data, GError ** error) { GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); GstClockTime duration = _calculate_duration_limit (self, child_data); GESTimelineElement *element = GES_TIMELINE_ELEMENT (self); - if (_CLOCK_TIME_IS_LESS (duration, element->duration)) { + if (GES_CLOCK_TIME_IS_LESS (duration, element->duration)) { /* NOTE: timeline would normally not be NULL at this point */ if (timeline && !timeline_tree_can_move_element (timeline_get_tree (timeline), element, ges_timeline_element_get_layer_priority (element), - element->start, duration)) { + element->start, duration, error)) { return FALSE; } } @@ -446,7 +442,7 @@ _get_priority_range (GESContainer * container, guint32 * min_priority, gboolean ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, - guint32 priority) + guint32 priority, GError ** error) { GList *child_data; DurationLimitData *data; @@ -459,7 +455,7 @@ ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, child_data = _duration_limit_data_list_with_data (clip, data); - if (!_can_update_duration_limit (clip, child_data)) { + if (!_can_update_duration_limit (clip, child_data, error)) { GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from " "priority %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " because " "the duration-limit cannot be adjusted", GES_ARGS (child), @@ -489,7 +485,8 @@ _child_priority_changed (GESContainer * container, GESTimelineElement * child) ****************************************************/ static gboolean -_can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint) +_can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint, + GError ** error) { GList *tmp; GList *child_data = NULL; @@ -505,13 +502,18 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint) _duration_limit_data_new (GES_TRACK_ELEMENT (child)); if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) { - if (GST_CLOCK_TIME_IS_VALID (child->maxduration) - && child->maxduration < inpoint) { + if (GES_CLOCK_TIME_IS_LESS (child->maxduration, inpoint)) { GST_INFO_OBJECT (clip, "Cannot set the in-point from %" - G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT " because it would " + GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because it would " "cause the in-point of its core child %" GES_FORMAT - " to exceed its max-duration", _INPOINT (clip), inpoint, - GES_ARGS (child)); + " to exceed its max-duration", GST_TIME_ARGS (_INPOINT (clip)), + GST_TIME_ARGS (inpoint), GES_ARGS (child)); + g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "Cannot set the in-point of \"%s\" to %" GST_TIME_FORMAT + " because it would exceed the max-duration of %" GST_TIME_FORMAT + " for the child \"%s\"", GES_TIMELINE_ELEMENT_NAME (clip), + GST_TIME_ARGS (inpoint), GST_TIME_ARGS (child->maxduration), + child->name); _duration_limit_data_free (data); g_list_free_full (child_data, _duration_limit_data_free); @@ -524,10 +526,10 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint) child_data = g_list_prepend (child_data, data); } - if (!_can_update_duration_limit (clip, child_data)) { - GST_INFO_OBJECT (clip, "Cannot set the in-point from %" G_GUINT64_FORMAT - " to %" G_GUINT64_FORMAT " because the duration-limit cannot be " - "adjusted", _INPOINT (clip), inpoint); + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot set the in-point from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT " because the duration-limit cannot be " + "adjusted", GST_TIME_ARGS (_INPOINT (clip)), GST_TIME_ARGS (inpoint)); return FALSE; } @@ -538,7 +540,7 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint) * its children have a max-duration below it */ gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, - GstClockTime inpoint) + GstClockTime inpoint, GError ** error) { /* don't bother checking if we are setting the value */ if (clip->priv->setting_inpoint) @@ -555,11 +557,11 @@ ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, child_data = _duration_limit_data_list_with_data (clip, data); - if (!_can_update_duration_limit (clip, child_data)) { + if (!_can_update_duration_limit (clip, child_data, error)) { GST_INFO_OBJECT (clip, "Cannot set the in-point of non-core child %" - GES_FORMAT " from %" G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT + GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the duration-limit cannot be adjusted", GES_ARGS (child), - _INPOINT (child), inpoint); + GST_TIME_ARGS (_INPOINT (child)), GST_TIME_ARGS (inpoint)); return FALSE; } @@ -568,7 +570,7 @@ ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, /* setting the in-point of a core child will shift the in-point of all * core children with an internal source */ - return _can_set_inpoint_of_core_children (clip, inpoint); + return _can_set_inpoint_of_core_children (clip, inpoint, error); } /* returns TRUE if duration-limit needs to be updated */ @@ -622,7 +624,7 @@ _update_max_duration (GESContainer * container) gboolean ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, - GstClockTime max_duration) + GstClockTime max_duration, GError ** error) { GList *child_data; DurationLimitData *data; @@ -635,11 +637,11 @@ ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, child_data = _duration_limit_data_list_with_data (clip, data); - if (!_can_update_duration_limit (clip, child_data)) { + if (!_can_update_duration_limit (clip, child_data, error)) { GST_INFO_OBJECT (clip, "Cannot set the max-duration of child %" - GES_FORMAT " from %" G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT + GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the duration-limit cannot be adjusted", GES_ARGS (child), - _MAXDURATION (child), max_duration); + GST_TIME_ARGS (_MAXDURATION (child)), GST_TIME_ARGS (max_duration)); return FALSE; } @@ -685,7 +687,7 @@ _child_has_internal_source_changed (GESClip * self, GESTimelineElement * child) gboolean ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, - gboolean active) + gboolean active, GError ** error) { GESTrack *track = ges_track_element_get_track (child); gboolean is_core = _IS_CORE_CHILD (child); @@ -722,7 +724,7 @@ ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, } } - if (!_can_update_duration_limit (clip, child_data)) { + if (!_can_update_duration_limit (clip, child_data, error)) { GST_INFO_OBJECT (clip, "Cannot set the active of child %" GES_FORMAT " from %i to %i because the duration-limit cannot be adjusted", GES_ARGS (child), ges_track_element_is_active (child), active); @@ -803,7 +805,7 @@ _track_contains_non_core (GESClip * clip, GESTrack * track) gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, - GESTrack * track) + GESTrack * track, GError ** error) { GList *child_data; DurationLimitData *data; @@ -816,12 +818,14 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, if (current_track == track) return TRUE; + /* NOTE: we consider the following error cases programming errors by + * the user */ if (current_track) { /* can not remove a core element from a track if a non-core one sits * above it */ if (_IS_CORE_CHILD (child) && _track_contains_non_core (clip, current_track)) { - GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT + GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT " to the track %" GST_PTR_FORMAT " because it has non-core " "siblings above it in its current track %" GST_PTR_FORMAT, GES_ARGS (child), track, current_track); @@ -833,13 +837,13 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); const GESTimeline *track_timeline = ges_track_get_timeline (track); if (track_timeline == NULL) { - GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT + GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT " to the track %" GST_PTR_FORMAT " because it is not part " "of a timeline", GES_ARGS (child), track); return FALSE; } if (track_timeline != clip_timeline) { - GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT + GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT " to the track %" GST_PTR_FORMAT " because its timeline %" GST_PTR_FORMAT " does not match the clip's timeline %" GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline, @@ -852,7 +856,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, * placed in a track that already has a core child */ if (_IS_CORE_CHILD (child)) { if (core) { - GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT + GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT " to the track %" GST_PTR_FORMAT " because it contains a " "core sibling %" GES_FORMAT, GES_ARGS (child), track, GES_ARGS (core)); @@ -860,7 +864,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, } } else { if (!core) { - GST_INFO_OBJECT (clip, "Cannot move the non-core child %" + GST_WARNING_OBJECT (clip, "Cannot move the non-core child %" GES_FORMAT " to the track %" GST_PTR_FORMAT " because it " " does not contain a core sibling", GES_ARGS (child), track); return FALSE; @@ -881,7 +885,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, child_data = _duration_limit_data_list_with_data (clip, data); - if (!_can_update_duration_limit (clip, child_data)) { + if (!_can_update_duration_limit (clip, child_data, error)) { GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from " "track %" GST_PTR_FORMAT " to track %" GST_PTR_FORMAT " because " "the duration-limit cannot be adjusted", GES_ARGS (child), @@ -1034,7 +1038,7 @@ _set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint, static gboolean _set_inpoint (GESTimelineElement * element, GstClockTime inpoint) { - if (!_can_set_inpoint_of_core_children (GES_CLIP (element), inpoint)) { + if (!_can_set_inpoint_of_core_children (GES_CLIP (element), inpoint, NULL)) { GST_WARNING_OBJECT (element, "Cannot set the in-point to %" GST_TIME_FORMAT, GST_TIME_ARGS (inpoint)); return FALSE; @@ -1097,10 +1101,11 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration) child_data = g_list_prepend (child_data, data); } - if (!_can_update_duration_limit (self, child_data)) { + if (!_can_update_duration_limit (self, child_data, NULL)) { GST_WARNING_OBJECT (self, "Cannot set the max-duration from %" - G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT " because the " - "duration-limit cannot be adjusted", element->maxduration, maxduration); + GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the " + "duration-limit cannot be adjusted", + GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (maxduration)); return FALSE; } @@ -1357,7 +1362,7 @@ _add_child (GESContainer * container, GESTimelineElement * element) child_data = _duration_limit_data_list_with_data (self, data); - if (!_can_update_duration_limit (self, child_data)) { + if (!_can_update_duration_limit (self, child_data, NULL)) { GST_WARNING_OBJECT (self, "Cannot add core %" GES_FORMAT " as a " "child because the duration-limit cannot be adjusted", GES_ARGS (element)); @@ -1418,7 +1423,7 @@ _add_child (GESContainer * container, GESTimelineElement * element) data->priority++; } - if (!_can_update_duration_limit (self, child_data)) { + if (!_can_update_duration_limit (self, child_data, NULL)) { GST_WARNING_OBJECT (self, "Cannot add effect %" GES_FORMAT " as " "a child because the duration-limit cannot be adjusted", GES_ARGS (element)); @@ -1502,7 +1507,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element) g_list_prepend (child_data, _duration_limit_data_new (child)); } - if (!_can_update_duration_limit (self, child_data)) { + if (!_can_update_duration_limit (self, child_data, NULL)) { GST_WARNING_OBJECT (self, "Cannot remove the child %" GES_FORMAT " because the duration-limit cannot be adjusted", GES_ARGS (el)); return FALSE; @@ -2338,9 +2343,10 @@ ges_clip_is_moving_from_layer (GESClip * clip) } /** - * ges_clip_move_to_layer: + * ges_clip_move_to_layer_full: * @clip: A #GESClip * @layer: The new layer + * @error: (nullable): Return location for an error * * Moves a clip to a new layer. If the clip already exists in a layer, it * is first removed from its current layer before being added to the new @@ -2349,7 +2355,7 @@ ges_clip_is_moving_from_layer (GESClip * clip) * Returns: %TRUE if @clip was successfully moved to @layer. */ gboolean -ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) +ges_clip_move_to_layer_full (GESClip * clip, GESLayer * layer, GError ** error) { gboolean ret = FALSE; GESLayer *current_layer; @@ -2357,6 +2363,7 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); element = GES_TIMELINE_ELEMENT (clip); current_layer = clip->priv->layer; @@ -2387,7 +2394,7 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) /* move to new layer, also checks moving of toplevel */ return timeline_tree_move (timeline_get_tree (layer->timeline), element, (gint64) ges_layer_get_priority (current_layer) - - (gint64) ges_layer_get_priority (layer), 0, GES_EDGE_NONE, 0); + (gint64) ges_layer_get_priority (layer), 0, GES_EDGE_NONE, 0, error); } gst_object_ref (clip); @@ -2418,6 +2425,21 @@ done: return ret && (clip->priv->layer == layer); } +/** + * ges_clip_move_to_layer: + * @clip: A #GESClip + * @layer: The new layer + * + * See ges_clip_move_to_layer_full(), which also gives an error. + * + * Returns: %TRUE if @clip was successfully moved to @layer. + */ +gboolean +ges_clip_move_to_layer (GESClip * clip, GESLayer * layer) +{ + return ges_clip_move_to_layer_full (clip, layer, NULL); +} + /** * ges_clip_find_track_element: * @clip: A #GESClip @@ -2603,10 +2625,11 @@ ges_clip_set_top_effect_priority (GESClip * clip, } /** - * ges_clip_set_top_effect_index: + * ges_clip_set_top_effect_index_full: * @clip: A #GESClip * @effect: An effect within @clip to move * @newindex: The index for @effect in @clip + * @error: (nullable): Return location for an error * * Set the index of an effect within the clip. See * ges_clip_get_top_effect_index(). The new index must be an existing @@ -2617,8 +2640,8 @@ ges_clip_set_top_effect_priority (GESClip * clip, * Returns: %TRUE if @effect was successfully moved to @newindex. */ gboolean -ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, - guint newindex) +ges_clip_set_top_effect_index_full (GESClip * clip, GESBaseEffect * effect, + guint newindex, GError ** error) { gint inc; GList *top_effects, *tmp; @@ -2628,6 +2651,7 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, 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; @@ -2670,8 +2694,8 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, child_data = g_list_prepend (child_data, data); } - if (!_can_update_duration_limit (clip, child_data)) { - GST_WARNING_OBJECT (clip, "Cannot move top effect %" GES_FORMAT + if (!_can_update_duration_limit (clip, child_data, error)) { + GST_INFO_OBJECT (clip, "Cannot move top effect %" GES_FORMAT " to index %i because the duration-limit cannot adjust", GES_ARGS (effect), newindex); return FALSE; @@ -2707,9 +2731,28 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, } /** - * ges_clip_split: + * ges_clip_set_top_effect_index: + * @clip: A #GESClip + * @effect: An effect within @clip to move + * @newindex: The index for @effect in @clip + * + * See ges_clip_set_top_effect_index_full(), which also gives an error. + * + * Returns: %TRUE if @effect was successfully moved to @newindex. + */ +gboolean +ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, + guint newindex) +{ + return ges_clip_set_top_effect_index_full (clip, effect, newindex, NULL); +} + +/** + * ges_clip_split_full: * @clip: The #GESClip to split - * @position: The timeline position at which to perform the split + * @position: The timeline position at which to perform the split, between + * the start and end of the clip + * @error: (nullable): Return location for an error * * Splits a clip at the given timeline position into two clips. The clip * must already have a #GESClip:layer. @@ -2743,7 +2786,7 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, * from the splitting @clip, or %NULL if @clip can't be split. */ GESClip * -ges_clip_split (GESClip * clip, guint64 position) +ges_clip_split_full (GESClip * clip, guint64 position, GError ** error) { GList *tmp, *transitions = NULL; GESClip *new_object; @@ -2752,10 +2795,12 @@ ges_clip_split (GESClip * clip, guint64 position) GESTimelineElement *element; GESTimeline *timeline; GHashTable *track_for_copy; + guint32 layer_prio; g_return_val_if_fail (GES_IS_CLIP (clip), NULL); g_return_val_if_fail (clip->priv->layer, NULL); g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL); + g_return_val_if_fail (!error || !*error, NULL); element = GES_TIMELINE_ELEMENT (clip); timeline = element->timeline; @@ -2770,26 +2815,26 @@ ges_clip_split (GESClip * clip, guint64 position) return NULL; } + layer_prio = ges_timeline_element_get_layer_priority (element); + old_duration = position - start; - if (timeline && !timeline_tree_can_move_element (timeline_get_tree - (timeline), element, - ges_timeline_element_get_layer_priority (element), - start, old_duration)) { - GST_WARNING_OBJECT (clip, + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), element, + layer_prio, start, old_duration, error)) { + GST_INFO_OBJECT (clip, "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT - " as timeline would be in an illegal" " state.", GES_ARGS (clip), + " as timeline would be in an illegal state.", GES_ARGS (clip), GST_TIME_ARGS (position)); return NULL; } new_duration = duration + start - position; - if (timeline && !timeline_tree_can_move_element (timeline_get_tree - (timeline), element, - ges_timeline_element_get_layer_priority (element), - position, new_duration)) { - GST_WARNING_OBJECT (clip, + if (timeline + && !timeline_tree_can_move_element (timeline_get_tree (timeline), element, + layer_prio, position, new_duration, error)) { + GST_INFO_OBJECT (clip, "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT - " as timeline would end up in an illegal" " state.", GES_ARGS (clip), + " as timeline would be in an illegal state.", GES_ARGS (clip), GST_TIME_ARGS (position)); return NULL; } @@ -2879,6 +2924,22 @@ ges_clip_split (GESClip * clip, guint64 position) return new_object; } +/** + * ges_clip_split: + * @clip: The #GESClip to split + * @position: The timeline position at which to perform the split + * + * See ges_clip_split_full(), which also gives an error. + * + * Returns: (transfer none) (nullable): The newly created clip resulting + * from the splitting @clip, or %NULL if @clip can't be split. + */ +GESClip * +ges_clip_split (GESClip * clip, guint64 position) +{ + return ges_clip_split_full (clip, position, NULL); +} + /** * ges_clip_set_supported_formats: * @clip: A #GESClip @@ -3073,7 +3134,7 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip, * @clip: A #GESClip * @child: A child of @clip * @track: The track to add @child to - * @err: Return location for an error + * @error: (nullable): Return location for an error * * Adds the track element child of the clip to a specific track. * @@ -3098,14 +3159,12 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip, * such as causing three sources to overlap at a single time, or causing * a source to completely overlap another in the same track. * - * Note that @err will not always be set upon failure. - * * Returns: (transfer none): The element that was added to @track, either * @child or a copy of child, or %NULL if the element could not be added. */ GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, - GESTrack * track, GError ** err) + GESTrack * track, GError ** error) { GESTimeline *timeline; GESTrackElement *el; @@ -3114,7 +3173,7 @@ ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, g_return_val_if_fail (GES_IS_CLIP (clip), NULL); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL); g_return_val_if_fail (GES_IS_TRACK (track), NULL); - g_return_val_if_fail (!err || !*err, NULL); + g_return_val_if_fail (!error || !*error, NULL); timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); @@ -3164,11 +3223,8 @@ ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, el = child; } - /* FIXME: set error if can not be added to track: - * Either breaks the track rules for the clip, or the timeline - * configuration rules */ - if (!ges_track_add_element (track, el)) { - GST_WARNING_OBJECT (clip, "Could not add the track element %" + if (!ges_track_add_element_full (track, el, error)) { + GST_INFO_OBJECT (clip, "Could not add the track element %" GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track); if (el != child) ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el)); diff --git a/ges/ges-clip.h b/ges/ges-clip.h index 1e2c5d9a07..bf62eac770 100644 --- a/ges/ges-clip.h +++ b/ges/ges-clip.h @@ -145,48 +145,75 @@ struct _GESClipClass GES_API GESTrackType ges_clip_get_supported_formats (GESClip *clip); GES_API -void ges_clip_set_supported_formats (GESClip *clip, GESTrackType supportedformats); +void ges_clip_set_supported_formats (GESClip *clip, + GESTrackType supportedformats); GES_API -GESTrackElement* ges_clip_add_asset (GESClip *clip, GESAsset *asset); +GESTrackElement* ges_clip_add_asset (GESClip *clip, + GESAsset *asset); GES_API -GESTrackElement* ges_clip_find_track_element (GESClip *clip, GESTrack *track, +GESTrackElement* ges_clip_find_track_element (GESClip *clip, + GESTrack *track, GType type); GES_API -GList * ges_clip_find_track_elements (GESClip * clip, GESTrack * track, - GESTrackType track_type, GType type); +GList * ges_clip_find_track_elements (GESClip * clip, + GESTrack * track, + GESTrackType track_type, + GType type); GES_API -GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, GESTrack * track, GError **err); +GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, + GESTrackElement * child, + GESTrack * track, + GError ** error); /**************************************************** * Layer * ****************************************************/ GES_API -GESLayer* ges_clip_get_layer (GESClip *clip); +GESLayer* ges_clip_get_layer (GESClip * clip); GES_API -gboolean ges_clip_move_to_layer (GESClip *clip, GESLayer *layer); +gboolean ges_clip_move_to_layer (GESClip * clip, + GESLayer * layer); +GES_API +gboolean ges_clip_move_to_layer_full (GESClip * clip, + GESLayer * layer, + GError ** error); /**************************************************** * Effects * ****************************************************/ GES_API -GList* ges_clip_get_top_effects (GESClip *clip); +GList* ges_clip_get_top_effects (GESClip * clip); GES_API -gint ges_clip_get_top_effect_position (GESClip *clip, GESBaseEffect *effect); +gint ges_clip_get_top_effect_position (GESClip * clip, + GESBaseEffect * effect); GES_API -gint ges_clip_get_top_effect_index (GESClip *clip, GESBaseEffect *effect); +gint ges_clip_get_top_effect_index (GESClip * clip, + GESBaseEffect * effect); GES_API -gboolean ges_clip_set_top_effect_priority (GESClip *clip, GESBaseEffect *effect, +gboolean ges_clip_set_top_effect_priority (GESClip * clip, + GESBaseEffect * effect, guint newpriority); GES_API -gboolean ges_clip_set_top_effect_index (GESClip *clip, GESBaseEffect *effect, +gboolean ges_clip_set_top_effect_index (GESClip * clip, + GESBaseEffect * effect, guint newindex); +GES_API +gboolean ges_clip_set_top_effect_index_full (GESClip * clip, + GESBaseEffect * effect, + guint newindex, + GError ** error); /**************************************************** * Editing * ****************************************************/ GES_API -GESClip* ges_clip_split (GESClip *clip, guint64 position); +GESClip* ges_clip_split (GESClip *clip, + guint64 position); +GES_API +GESClip* ges_clip_split_full (GESClip *clip, + guint64 position, + GError ** error); GES_API GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip, diff --git a/ges/ges-gerror.h b/ges/ges-gerror.h index 1f4d8b4520..2e326e50f4 100644 --- a/ges/ges-gerror.h +++ b/ges/ges-gerror.h @@ -38,6 +38,17 @@ G_BEGIN_DECLS * @GES_ERROR_ASSET_WRONG_ID: The ID passed is malformed * @GES_ERROR_ASSET_LOADING: An error happened while loading the asset * @GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE: The formatted files was malformed + * @GES_ERROR_INVALID_FRAME_NUMBER: The frame number is invalid + * @GES_ERROR_NEGATIVE_LAYER: The operation would lead to a negative + * #GES_TIMELINE_ELEMENT_LAYER_PRIORITY + * @GES_ERROR_NEGATIVE_TIME: The operation would lead to a negative time. + * E.g. for the #GESTimelineElement:start #GESTimelineElement:duration or + * #GESTimelineElement:in-point. + * @GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT: Some #GESTimelineElement does + * not have a large enough #GESTimelineElement:max-duration to cover the + * desired operation + * @GES_ERROR_INVALID_OVERLAP_IN_TRACK: The operation would break one of + * the overlap conditions for the #GESTimeline */ typedef enum { @@ -45,6 +56,10 @@ typedef enum GES_ERROR_ASSET_LOADING, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, GES_ERROR_INVALID_FRAME_NUMBER, + GES_ERROR_NEGATIVE_LAYER, + GES_ERROR_NEGATIVE_TIME, + GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + GES_ERROR_INVALID_OVERLAP_IN_TRACK, } GESError; -G_END_DECLS \ No newline at end of file +G_END_DECLS diff --git a/ges/ges-group.c b/ges/ges-group.c index 82a292d973..482be30032 100644 --- a/ges/ges-group.c +++ b/ges/ges-group.c @@ -222,7 +222,7 @@ _set_priority (GESTimelineElement * element, guint32 priority) return timeline_tree_move (timeline_get_tree (timeline), element, (gint64) (element->priority) - (gint64) priority, 0, - GES_EDGE_NONE, 0); + GES_EDGE_NONE, 0, NULL); } static gboolean diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 6b5a4f8f2e..76eb260645 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -61,6 +61,10 @@ GstDebugCategory * _ges_debug (void); #define _set_duration0 ges_timeline_element_set_duration #define _set_priority0 ges_timeline_element_set_priority +#define GES_CLOCK_TIME_IS_LESS(first, second) \ + (GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \ + || (first) < (second))) + #define DEFAULT_FRAMERATE_N 30 #define DEFAULT_FRAMERATE_D 1 #define DEFAULT_WIDTH 1280 @@ -110,8 +114,8 @@ G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline); G_GNUC_INTERNAL gboolean ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, - GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, - guint64 position); + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, + guint64 position, GError ** error); G_GNUC_INTERNAL void timeline_add_group (GESTimeline *timeline, @@ -152,7 +156,7 @@ G_GNUC_INTERNAL void ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving); G_GNUC_INTERNAL gboolean -ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip); +ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error); G_GNUC_INTERNAL void ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip); @@ -403,11 +407,11 @@ G_GNUC_INTERNAL gboolean ges_clip_is_moving_from_layer (GESClip *clip G_GNUC_INTERNAL void ges_clip_set_moving_from_layer (GESClip *clip, gboolean is_moving); G_GNUC_INTERNAL GESTrackElement* ges_clip_create_track_element (GESClip *clip, GESTrackType type); G_GNUC_INTERNAL GList* ges_clip_create_track_elements (GESClip *clip, GESTrackType type); -G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, GstClockTime inpoint); -G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, GstClockTime max_duration); -G_GNUC_INTERNAL gboolean ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active); -G_GNUC_INTERNAL gboolean ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority); -G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack); +G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, GstClockTime inpoint, GError ** error); +G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, GstClockTime max_duration, GError ** error); +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 void ges_clip_empty_from_track (GESClip * clip, GESTrack * track); /**************************************************** @@ -420,7 +424,7 @@ G_GNUC_INTERNAL void layer_set_priority (GESLayer * layer, guint p * GESTrackElement * ****************************************************/ #define NLE_OBJECT_TRACK_ELEMENT_QUARK (g_quark_from_string ("nle_object_track_element_quark")) -G_GNUC_INTERNAL gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track); +G_GNUC_INTERNAL gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track, GError ** error); G_GNUC_INTERNAL void ges_track_element_copy_properties (GESTimelineElement * element, GESTimelineElement * elementcopy); G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement *element, gboolean active); diff --git a/ges/ges-layer.c b/ges/ges-layer.c index c46ee97ff9..6cef4c8c57 100644 --- a/ges/ges-layer.c +++ b/ges/ges-layer.c @@ -237,24 +237,8 @@ ges_layer_class_init (GESLayerClass * klass) * GESLayer:auto-transition: * * Whether to automatically create a #GESTransitionClip whenever two - * #GESClip-s overlap in the layer. Specifically, if this is set to - * %TRUE, then wherever two #GESSource-s (that belong to some clip in - * the layer) share the same #GESTrackElement:track and the end time of - * one source exceeds the #GESTimelineElement:start time of the other, - * there will exist a corresponding #GESTransitionClip in the same - * layer. These automatic transitions will be created and removed - * accordingly. Their temporal extent will cover the overlap of the - * two sources (their #GESTimelineElement:start will be set to the - * #GESTimelineElement:start of the later source, and their - * #GESTimelineElement:duration will be the difference between the - * #GESTimelineElement:start of the later source, and the end time of - * the earlier source). - * - * It may be difficult to use this feature if your timeline has multiple - * tracks of the same #GESTrack:track-type and you use the - * #GESTimeline::select-tracks-for-object signal. You will have to - * ensure that any #GESTransition that belongs to a newly created - * transition clip is able to arrive in the correct track. + * #GESSource-s that both belong to a #GESClip in the layer overlap. + * See #GESTimeline for what counts as an overlap. * * When a layer is added to a #GESTimeline, if this property is left as * %FALSE, but the timeline's #GESTimeline:auto-transition is %TRUE, it @@ -667,9 +651,10 @@ ges_layer_is_empty (GESLayer * layer) } /** - * ges_layer_add_clip: + * ges_layer_add_clip_full: * @layer: The #GESLayer * @clip: (transfer floating): The clip to add + * @error: (nullable): Return location for an error * * Adds the given clip to the layer. If the method succeeds, the layer * will take ownership of the clip. @@ -682,17 +667,19 @@ ges_layer_is_empty (GESLayer * layer) * if @layer refused to add @clip. */ gboolean -ges_layer_add_clip (GESLayer * layer, GESClip * clip) +ges_layer_add_clip_full (GESLayer * layer, GESClip * clip, GError ** error) { - GList *tmp, *prev_children; + GList *tmp, *prev_children, *new_children; GESAsset *asset; GESLayerPrivate *priv; GESLayer *current_layer; GESTimeline *timeline; GESContainer *container; + GError *timeline_error = NULL; g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); g_return_val_if_fail (GES_IS_CLIP (clip), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); container = GES_CONTAINER (clip); @@ -786,9 +773,23 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) prev_children = ges_container_get_children (container, FALSE); - if (timeline && !ges_timeline_add_clip (timeline, clip)) { + if (timeline && !ges_timeline_add_clip (timeline, clip, &timeline_error)) { + GST_INFO_OBJECT (layer, "Could not add the clip %" GES_FORMAT + " to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), timeline); + + if (timeline_error) { + if (error) { + *error = timeline_error; + } else { + GST_WARNING_OBJECT (timeline, "Adding the clip %" GES_FORMAT + " to the timeline failed: %s", GES_ARGS (clip), + timeline_error->message); + g_error_free (timeline_error); + } + } + /* remove any track elements that were newly created */ - GList *new_children = ges_container_get_children (container, FALSE); + new_children = ges_container_get_children (container, FALSE); for (tmp = new_children; tmp; tmp = tmp->next) { if (!g_list_find (prev_children, tmp->data)) ges_container_remove (container, tmp->data); @@ -796,8 +797,6 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) g_list_free_full (prev_children, gst_object_unref); g_list_free_full (new_children, gst_object_unref); - GST_WARNING_OBJECT (layer, "Could not add the clip %" GES_FORMAT - " to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), timeline); /* FIXME: change emit signal to FALSE once we are able to delay the * "clip-added" signal until after ges_timeline_add_clip */ ges_layer_remove_clip_internal (layer, clip, TRUE); @@ -818,7 +817,23 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) } /** - * ges_layer_add_asset: + * ges_layer_add_clip: + * @layer: The #GESLayer + * @clip: (transfer floating): The clip to add + * + * See ges_layer_add_clip_full(), which also gives an error. + * + * Returns: %TRUE if @clip was properly added to @layer, or %FALSE + * if @layer refused to add @clip. + */ +gboolean +ges_layer_add_clip (GESLayer * layer, GESClip * clip) +{ + return ges_layer_add_clip_full (layer, clip, NULL); +} + +/** + * ges_layer_add_asset_full: * @layer: The #GESLayer * @asset: The asset to extract the new clip from * @start: The #GESTimelineElement:start value to set on the new clip @@ -830,6 +845,7 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) * clip * @track_types: The #GESClip:supported-formats to set on the the new * clip, or #GES_TRACK_TYPE_UNKNOWN to use the default + * @error: (nullable): Return location for an error * * Extracts a new clip from an asset and adds it to the layer with * the given properties. @@ -837,14 +853,15 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip) * Returns: (transfer none): The newly created clip. */ GESClip * -ges_layer_add_asset (GESLayer * layer, +ges_layer_add_asset_full (GESLayer * layer, GESAsset * asset, GstClockTime start, GstClockTime inpoint, - GstClockTime duration, GESTrackType track_types) + GstClockTime duration, GESTrackType track_types, GError ** error) { GESClip *clip; g_return_val_if_fail (GES_IS_LAYER (layer), NULL); g_return_val_if_fail (GES_IS_ASSET (asset), NULL); + g_return_val_if_fail (!error || !*error, NULL); g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type (asset), GES_TYPE_CLIP), NULL); @@ -873,13 +890,40 @@ ges_layer_add_asset (GESLayer * layer, _set_duration0 (GES_TIMELINE_ELEMENT (clip), duration); } - if (!ges_layer_add_clip (layer, clip)) { + if (!ges_layer_add_clip_full (layer, clip, error)) { return NULL; } return clip; } +/** + * ges_layer_add_asset: + * @layer: The #GESLayer + * @asset: The asset to extract the new clip from + * @start: The #GESTimelineElement:start value to set on the new clip + * If `start == #GST_CLOCK_TIME_NONE`, it will be added to the end + * of @layer, i.e. it will be set to @layer's duration + * @inpoint: The #GESTimelineElement:in-point value to set on the new + * clip + * @duration: The #GESTimelineElement:duration value to set on the new + * clip + * @track_types: The #GESClip:supported-formats to set on the the new + * clip, or #GES_TRACK_TYPE_UNKNOWN to use the default + * + * See ges_layer_add_asset_full(), which also gives an error. + * + * Returns: (transfer none): The newly created clip. + */ +GESClip * +ges_layer_add_asset (GESLayer * layer, + GESAsset * asset, GstClockTime start, GstClockTime inpoint, + GstClockTime duration, GESTrackType track_types) +{ + return ges_layer_add_asset_full (layer, asset, start, inpoint, duration, + track_types, NULL); +} + /** * ges_layer_new: * diff --git a/ges/ges-layer.h b/ges/ges-layer.h index e2d6fa41d0..c9d2d06e1e 100644 --- a/ges/ges-layer.h +++ b/ges/ges-layer.h @@ -65,67 +65,82 @@ struct _GESLayerClass { /*< private >*/ /* Signals */ - void (*object_added) (GESLayer * layer, GESClip * object); - void (*object_removed) (GESLayer * layer, GESClip * object); + void (*object_added) (GESLayer * layer, GESClip * object); + void (*object_removed) (GESLayer * layer, GESClip * object); /* Padding for API extension */ gpointer _ges_reserved[GES_PADDING]; }; GES_API -GESLayer* ges_layer_new (void); +GESLayer* ges_layer_new (void); GES_API -void ges_layer_set_timeline (GESLayer * layer, - GESTimeline * timeline); - -GES_API GESTimeline * -ges_layer_get_timeline (GESLayer * layer); +void ges_layer_set_timeline (GESLayer * layer, + GESTimeline * timeline); +GES_API +GESTimeline * ges_layer_get_timeline (GESLayer * layer); GES_API -gboolean ges_layer_add_clip (GESLayer * layer, - GESClip * clip); +gboolean ges_layer_add_clip (GESLayer * layer, + GESClip * clip); GES_API -GESClip * ges_layer_add_asset (GESLayer *layer, - GESAsset *asset, - GstClockTime start, - GstClockTime inpoint, - GstClockTime duration, - GESTrackType track_types); +gboolean ges_layer_add_clip_full (GESLayer * layer, + GESClip * clip, + GError ** error); +GES_API +GESClip * ges_layer_add_asset (GESLayer *layer, + GESAsset *asset, + GstClockTime start, + GstClockTime inpoint, + GstClockTime duration, + GESTrackType track_types); +GES_API +GESClip * ges_layer_add_asset_full (GESLayer *layer, + GESAsset *asset, + GstClockTime start, + GstClockTime inpoint, + GstClockTime duration, + GESTrackType track_types, + GError ** error); GES_API -gboolean ges_layer_remove_clip (GESLayer * layer, - GESClip * clip); +gboolean ges_layer_remove_clip (GESLayer * layer, + GESClip * clip); GES_DEPRECATED_FOR(ges_timeline_move_layer) -void ges_layer_set_priority (GESLayer * layer, - guint priority); +void ges_layer_set_priority (GESLayer * layer, + guint priority); GES_API -gboolean ges_layer_is_empty (GESLayer * layer); +gboolean ges_layer_is_empty (GESLayer * layer); GES_API -GList* ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start, GstClockTime end); +GList* ges_layer_get_clips_in_interval (GESLayer * layer, + GstClockTime start, + GstClockTime end); GES_API -guint ges_layer_get_priority (GESLayer * layer); +guint ges_layer_get_priority (GESLayer * layer); GES_API -gboolean ges_layer_get_auto_transition (GESLayer * layer); +gboolean ges_layer_get_auto_transition (GESLayer * layer); GES_API -void ges_layer_set_auto_transition (GESLayer * layer, - gboolean auto_transition); +void ges_layer_set_auto_transition (GESLayer * layer, + gboolean auto_transition); GES_API -GList* ges_layer_get_clips (GESLayer * layer); +GList* ges_layer_get_clips (GESLayer * layer); GES_API -GstClockTime ges_layer_get_duration (GESLayer *layer); +GstClockTime ges_layer_get_duration (GESLayer *layer); GES_API -gboolean ges_layer_set_active_for_tracks (GESLayer *layer, gboolean active, - GList *tracks); +gboolean ges_layer_set_active_for_tracks (GESLayer *layer, + gboolean active, + GList *tracks); -GES_API gboolean ges_layer_get_active_for_track (GESLayer *layer, - GESTrack *track); +GES_API +gboolean ges_layer_get_active_for_track (GESLayer *layer, + GESTrack *track); G_END_DECLS diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index e025694a24..491564f8fa 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -48,12 +48,9 @@ * similar changes in neighbouring or later elements in the timeline. * * However, a timeline may refuse a change in these properties if they - * would place the timeline in an unsupported configuration. For example, - * it is not possible for three #GESSourceClip-s in the same layer and - * with the same track types to overlap at any given position in the - * timeline (only two may overlap, which corresponds to a single - * #GESTransition). Similarly, a #GESSourceClip may not entirely cover - * another #GESSourceClip in the same layer and with the same track types. + * would place the timeline in an unsupported configuration. See + * #GESTimeline for its overlap rules. + * * Additionally, an edit may be refused if it would place one of the * timing properties out of bounds (such as a negative time value for * #GESTimelineElement:start, or having insufficient internal @@ -1153,8 +1150,7 @@ ges_timeline_element_set_inpoint (GESTimelineElement * self, if (G_UNLIKELY (inpoint == self->inpoint)) return TRUE; - if (GST_CLOCK_TIME_IS_VALID (self->maxduration) - && inpoint > self->maxduration) { + if (GES_CLOCK_TIME_IS_LESS (self->maxduration, inpoint)) { GST_WARNING_OBJECT (self, "Can not set an in-point of %" GST_TIME_FORMAT " because it exceeds the element's max-duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (inpoint), GST_TIME_ARGS (self->maxduration)); @@ -1210,7 +1206,7 @@ ges_timeline_element_set_max_duration (GESTimelineElement * self, if (G_UNLIKELY (maxduration == self->maxduration)) return TRUE; - if (GST_CLOCK_TIME_IS_VALID (maxduration) && self->inpoint > maxduration) { + if (GES_CLOCK_TIME_IS_LESS (maxduration, self->inpoint)) { GST_WARNING_OBJECT (self, "Can not set a max-duration of %" GST_TIME_FORMAT " because it lies below the element's in-point: %" GST_TIME_FORMAT, GST_TIME_ARGS (maxduration), @@ -2328,17 +2324,15 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self) } /** - * ges_timeline_element_edit: + * ges_timeline_element_edit_full: * @self: The #GESTimelineElement to edit - * @layers: (element-type GESLayer) (nullable): A whitelist of layers - * where the edit can be performed, %NULL allows all layers in the - * timeline * @new_layer_priority: The priority/index of the layer @self should be * moved to. -1 means no move * @mode: The edit mode * @edge: The edge of @self where the edit should occur * @position: The edit position: a new location for the edge of @self * (in nanoseconds) + * @error: (nullable): Return location for an error * * Edits the element within its timeline by adjusting its * #GESTimelineElement:start, #GESTimelineElement:duration or @@ -2363,28 +2357,20 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self) * the corresponding layer priority/index does not yet exist for the * timeline. * - * @layers can be used as a whitelist to limit changes to elements that - * exist in the corresponding layers. If you intend to also switch - * elements between layers, then you must ensure that all the involved - * layers are included for the switch to succeed (with the exception of - * layers may be newly created). - * * Returns: %TRUE if the edit of @self completed, %FALSE on failure. - * - * Since: 1.18 */ -/* FIXME: handle the layers argument. Currently we always treat it as if - * it is NULL in the ges-timeline code */ gboolean -ges_timeline_element_edit (GESTimelineElement * self, GList * layers, - gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) +ges_timeline_element_edit_full (GESTimelineElement * self, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position, + GError ** error) { GESTimeline *timeline; guint32 layer_prio; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); g_return_val_if_fail (timeline, FALSE); @@ -2399,8 +2385,41 @@ ges_timeline_element_edit (GESTimelineElement * self, GList * layers, self->name, ges_edge_name (edge), GST_TIME_ARGS (position), ges_edit_mode_name (mode), new_layer_priority); - return ges_timeline_edit (timeline, self, layers, new_layer_priority, mode, - edge, position); + return ges_timeline_edit (timeline, self, new_layer_priority, mode, + edge, position, error); +} + +/** + * ges_timeline_element_edit: + * @self: The #GESTimelineElement to edit + * @layers: (element-type GESLayer) (nullable): A whitelist of layers + * where the edit can be performed, %NULL allows all layers in the + * timeline. + * @new_layer_priority: The priority/index of the layer @self should be + * moved to. -1 means no move + * @mode: The edit mode + * @edge: The edge of @self where the edit should occur + * @position: The edit position: a new location for the edge of @self + * (in nanoseconds) in the timeline coordinates + * + * See ges_timeline_element_edit_full(), which also gives an error. + * + * Note that the @layers argument is currently ignored, so you should + * just pass %NULL. + * + * Returns: %TRUE if the edit of @self completed, %FALSE on failure. + * + * Since: 1.18 + */ + +/* FIXME: handle the layers argument. Currently we always treat it as if + * it is NULL in the ges-timeline code */ +gboolean +ges_timeline_element_edit (GESTimelineElement * self, GList * layers, + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) +{ + return ges_timeline_element_edit_full (self, new_layer_priority, mode, edge, + position, NULL); } /** diff --git a/ges/ges-timeline-element.h b/ges/ges-timeline-element.h index bb5ce2ed54..e2b9abb250 100644 --- a/ges/ges-timeline-element.h +++ b/ges/ges-timeline-element.h @@ -401,4 +401,12 @@ gboolean ges_timeline_element_edit (GESTimeli GESEditMode mode, GESEdge edge, guint64 position); + +GES_API +gboolean ges_timeline_element_edit_full (GESTimelineElement * self, + gint64 new_layer_priority, + GESEditMode mode, + GESEdge edge, + guint64 position, + GError ** error); G_END_DECLS diff --git a/ges/ges-timeline-tree.c b/ges/ges-timeline-tree.c index 9d8aecd02c..edc9fbf3a1 100644 --- a/ges/ges-timeline-tree.c +++ b/ges/ges-timeline-tree.c @@ -79,6 +79,8 @@ struct _TreeIterationData { GNode *root; gboolean res; + /* an error to set */ + GError **error; /* The element we are visiting */ GESTimelineElement *element; @@ -299,7 +301,7 @@ _clock_time_plus (GstClockTime time, GstClockTime add) return GST_CLOCK_TIME_NONE; if (time >= (G_MAXUINT64 - add)) { - GST_INFO ("The time %" G_GUINT64_FORMAT " would overflow when " + GST_ERROR ("The time %" G_GUINT64_FORMAT " would overflow when " "adding %" G_GUINT64_FORMAT, time, add); return GST_CLOCK_TIME_NONE; } @@ -355,7 +357,7 @@ _abs_clock_time_distance (GstClockTime time1, GstClockTime time2) return time2 - time1; } -static gboolean +static void get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode, GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start, GstClockTime * end, gboolean * negative_end) @@ -388,22 +390,6 @@ get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode, *start = new_start; if (end) *end = new_end; - - if (start && !GST_CLOCK_TIME_IS_VALID (new_start)) { - GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT " with " - "offset %" G_GINT64_FORMAT " because it would result in an invalid " - "start", GES_ARGS (element), offset); - return FALSE; - } - - if (end && !GST_CLOCK_TIME_IS_VALID (new_end)) { - GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT " with " - "offset %" G_GINT64_FORMAT " because it would result in an invalid " - "end", GES_ARGS (element), offset); - return FALSE; - } - - return TRUE; } /**************************************************** @@ -571,9 +557,22 @@ timeline_tree_snap (GNode * root, GESTimelineElement * element, /* Allow negative start/end positions in case a snap makes them valid! * But we can still only snap to an existing edge in the timeline, * which should be a valid time */ - if (!get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, - *offset, &start, &negative_start, &end, &negative_end)) + get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, *offset, + &start, &negative_start, &end, &negative_end); + + if (!GST_CLOCK_TIME_IS_VALID (start)) { + GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in " + "an invalid start", GES_ARGS (element), *offset); goto done; + } + + if (!GST_CLOCK_TIME_IS_VALID (end)) { + GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT + " with offset %" G_GINT64_FORMAT " because it would result in " + "an invalid end", GES_ARGS (element), *offset); + goto done; + } switch (mode) { case EDIT_MOVE: @@ -619,6 +618,41 @@ done: * Check Overlaps * ****************************************************/ +#define _SOURCE_FORMAT "\"%s\"%s%s%s" +#define _SOURCE_ARGS(element) \ + element->name, element->parent ? " (parent: \"" : "", \ + element->parent ? element->parent->name : "", \ + element->parent ? "\")" : "" + +static void +set_full_overlap_error (GError ** error, GESTimelineElement * super, + GESTimelineElement * sub, GESTrack * track) +{ + if (error) { + gchar *track_name = gst_object_get_name (GST_OBJECT (track)); + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK, + "The source " _SOURCE_FORMAT " would totally overlap the " + "source " _SOURCE_FORMAT " in the track \"%s\"", _SOURCE_ARGS (super), + _SOURCE_ARGS (sub), track_name); + g_free (track_name); + } +} + +static void +set_triple_overlap_error (GError ** error, GESTimelineElement * first, + GESTimelineElement * second, GESTimelineElement * third, GESTrack * track) +{ + if (error) { + gchar *track_name = gst_object_get_name (GST_OBJECT (track)); + g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK, + "The sources " _SOURCE_FORMAT ", " _SOURCE_FORMAT " and " + _SOURCE_FORMAT " would all overlap at the same point in the " + "track \"%s\"", _SOURCE_ARGS (first), _SOURCE_ARGS (second), + _SOURCE_ARGS (third), track_name); + g_free (track_name); + } +} + #define _ELEMENT_FORMAT \ "%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \ "(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")" @@ -695,10 +729,19 @@ check_overlap_with_element (GNode * node, TreeIterationData * data) return FALSE; } - if ((cmp_start <= start && cmp_end >= end) || - (cmp_start >= start && cmp_end <= end)) { + if (cmp_start <= start && cmp_end >= end) { + /* cmp fully overlaps e */ GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap", _CMP_ARGS, _E_ARGS); + set_full_overlap_error (data->error, cmp, e, track); + goto error; + } + + if (cmp_start >= start && cmp_end <= end) { + /* e fully overlaps cmp */ + GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap", + _CMP_ARGS, _E_ARGS); + set_full_overlap_error (data->error, e, cmp, track); goto error; } @@ -710,6 +753,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data) if (data->overlaping_on_start) { GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start", _CMP_ARGS, data->overlaping_on_start->name, e->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start, + track); goto error; } if (GST_CLOCK_TIME_IS_VALID (data->overlap_end_first_time) && @@ -717,6 +762,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data) GST_INFO (_ELEMENT_FORMAT " overlaps %s on its start and %s on its " "end, but they already overlap each other", _CMP_ARGS, e->name, data->overlaping_on_end->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end, + track); goto error; } /* record the time at which the overlapped ends */ @@ -733,6 +780,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data) if (data->overlaping_on_end) { GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end", _CMP_ARGS, data->overlaping_on_end->name, e->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end, + track); goto error; } if (GST_CLOCK_TIME_IS_VALID (data->overlap_start_final_time) && @@ -740,6 +789,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data) GST_INFO (_ELEMENT_FORMAT " overlaps %s on its end and %s on its " "start, but they already overlap each other", _CMP_ARGS, e->name, data->overlaping_on_start->name); + set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start, + track); goto error; } /* record the time at which the overlapped starts */ @@ -789,12 +840,14 @@ check_moving_overlaps (GNode * node, TreeIterationData * data) /* whether the elements in moving can be moved to their corresponding * PositionData */ static gboolean -timeline_tree_can_move_elements (GNode * root, GHashTable * moving) +timeline_tree_can_move_elements (GNode * root, GHashTable * moving, + GError ** error) { TreeIterationData data = tree_iteration_data_init; data.moving = moving; data.root = root; data.res = TRUE; + data.error = error; /* sufficient to check the leaves, which is all the track elements or * empty clips * should also be sufficient to only check the moving elements */ @@ -808,8 +861,56 @@ timeline_tree_can_move_elements (GNode * root, GHashTable * moving) * Setting Edit Data * ****************************************************/ +static void +set_negative_start_error (GError ** error, GESTimelineElement * element, + GstClockTime neg_start) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The element \"%s\" would have a negative start of -%" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_start)); +} + +static void +set_negative_duration_error (GError ** error, GESTimelineElement * element, + GstClockTime neg_duration) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The element \"%s\" would have a negative duration of -%" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_duration)); +} + +static void +set_negative_inpoint_error (GError ** error, GESTimelineElement * element, + GstClockTime neg_inpoint) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME, + "The element \"%s\" would have a negative in-point of -%" + GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_inpoint)); +} + +static void +set_negative_layer_error (GError ** error, GESTimelineElement * element, + gint64 neg_layer) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_LAYER, + "The element \"%s\" would have a negative layer priority of -%" + G_GINT64_FORMAT, element->name, neg_layer); +} + +static void +set_breaks_duration_limit_error (GError ** error, GESClip * clip, + GstClockTime duration, GstClockTime duration_limit) +{ + g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT, + "The clip \"%s\" would have a duration of %" GST_TIME_FORMAT + " that would break its duration-limit of %" GST_TIME_FORMAT, + GES_TIMELINE_ELEMENT_NAME (clip), GST_TIME_ARGS (duration), + GST_TIME_ARGS (duration_limit)); +} + static gboolean -set_layer_priority (GESTimelineElement * element, EditData * data) +set_layer_priority (GESTimelineElement * element, EditData * data, + GError ** error) { gint64 layer_offset = data->layer_offset; guint32 layer_prio = ges_timeline_element_get_layer_priority (element); @@ -827,10 +928,12 @@ set_layer_priority (GESTimelineElement * element, EditData * data) GST_INFO_OBJECT (element, "%s would have a negative layer priority (%" G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name, layer_prio, layer_offset); + set_negative_layer_error (error, element, + layer_offset - (gint64) layer_prio); return FALSE; } if ((layer_prio - (gint64) layer_offset) >= G_MAXUINT32) { - GST_INFO_OBJECT (element, "%s would have an overflowing layer priority", + GST_ERROR_OBJECT (element, "%s would have an overflowing layer priority", element->name); return FALSE; } @@ -851,34 +954,45 @@ set_layer_priority (GESTimelineElement * element, EditData * data) } static gboolean -set_edit_move_values (GESTimelineElement * element, EditData * data) +set_edit_move_values (GESTimelineElement * element, EditData * data, + GError ** error) { + gboolean negative = FALSE; GstClockTime new_start = - _clock_time_minus_diff (element->start, data->offset, NULL); - if (!GST_CLOCK_TIME_IS_VALID (new_start)) { + _clock_time_minus_diff (element->start, data->offset, &negative); + if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) { GST_INFO_OBJECT (element, "Cannot move %" GES_FORMAT " with offset %" G_GINT64_FORMAT " because it would result in an invalid start", GES_ARGS (element), data->offset); + if (negative) + set_negative_start_error (error, element, new_start); return FALSE; } _CHECK_END (element, new_start, element->duration); data->start = new_start; + + if (GES_IS_GROUP (element)) + return TRUE; + GST_INFO_OBJECT (element, "%s will move by setting start to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start)); - return set_layer_priority (element, data); + return set_layer_priority (element, data, error); } static gboolean set_edit_trim_start_inpoint_value (GESTimelineElement * element, - EditData * data) + EditData * data, GError ** error) { + gboolean negative = FALSE; GstClockTime new_inpoint = _clock_time_minus_diff (element->inpoint, - data->offset, NULL); - if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) { + data->offset, &negative); + if (negative || !GST_CLOCK_TIME_IS_VALID (new_inpoint)) { GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT " with offset %" G_GINT64_FORMAT " because it would result in an " "invalid in-point", GES_ARGS (element), data->offset); + if (negative) + set_negative_inpoint_error (error, element, new_inpoint); return FALSE; } data->inpoint = new_inpoint; @@ -887,7 +1001,7 @@ set_edit_trim_start_inpoint_value (GESTimelineElement * element, static gboolean set_edit_trim_start_non_core_children (GESTimelineElement * clip, - GstClockTimeDiff offset, GHashTable * edit_table) + GstClockTimeDiff offset, GHashTable * edit_table, GError ** error) { GList *tmp; GESTimelineElement *child; @@ -916,7 +1030,7 @@ set_edit_trim_start_non_core_children (GESTimelineElement * clip, data = new_edit_data (EDIT_TRIM_START, offset, 0); g_hash_table_insert (edit_table, child, data); - if (!set_edit_trim_start_inpoint_value (child, data)) + if (!set_edit_trim_start_inpoint_value (child, data, error)) return FALSE; } } @@ -926,40 +1040,52 @@ set_edit_trim_start_non_core_children (GESTimelineElement * clip, /* trim the start of a clip or a track element */ static gboolean set_edit_trim_start_values (GESTimelineElement * element, EditData * data, - GHashTable * edit_table) + GHashTable * edit_table, GError ** error) { + gboolean negative = FALSE; + GstClockTime new_duration; GstClockTime new_start = - _clock_time_minus_diff (element->start, data->offset, NULL); - GstClockTime new_duration = - _clock_time_minus_diff (element->duration, -data->offset, NULL); + _clock_time_minus_diff (element->start, data->offset, &negative); - if (!GST_CLOCK_TIME_IS_VALID (new_start)) { + if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) { GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT " with offset %" G_GINT64_FORMAT " because it would result in an " "invalid start", GES_ARGS (element), data->offset); + if (negative) + set_negative_start_error (error, element, new_start); return FALSE; } - if (!GST_CLOCK_TIME_IS_VALID (new_duration)) { + + new_duration = + _clock_time_minus_diff (element->duration, -data->offset, &negative); + + if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) { GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT " with offset %" G_GINT64_FORMAT " because it would result in an " "invalid duration", GES_ARGS (element), data->offset); + if (negative) + set_negative_duration_error (error, element, new_duration); return FALSE; } _CHECK_END (element, new_start, new_duration); + data->start = new_start; + data->duration = new_duration; + + if (GES_IS_GROUP (element)) + return TRUE; + if (GES_IS_CLIP (element)) { - if (!set_edit_trim_start_inpoint_value (element, data)) + if (!set_edit_trim_start_inpoint_value (element, data, error)) return FALSE; if (!set_edit_trim_start_non_core_children (element, data->offset, - edit_table)) + edit_table, error)) return FALSE; } else if (GES_IS_TRACK_ELEMENT (element) && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) { - if (!set_edit_trim_start_inpoint_value (element, data)) + if (!set_edit_trim_start_inpoint_value (element, data, error)) return FALSE; } - data->start = new_start; - data->duration = new_duration; /* NOTE: without time effects, the duration-limit will increase with * a decrease in in-point by the same amount that duration increases, @@ -971,53 +1097,64 @@ set_edit_trim_start_values (GESTimelineElement * element, EditData * data, "to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start), GST_TIME_ARGS (data->inpoint), GST_TIME_ARGS (data->duration)); - return set_layer_priority (element, data); + return set_layer_priority (element, data, error); } /* trim the end of a clip or a track element */ static gboolean -set_edit_trim_end_values (GESTimelineElement * element, EditData * data) +set_edit_trim_end_values (GESTimelineElement * element, EditData * data, + GError ** error) { + gboolean negative = FALSE; GstClockTime new_duration = - _clock_time_minus_diff (element->duration, data->offset, NULL); - if (!GST_CLOCK_TIME_IS_VALID (new_duration)) { + _clock_time_minus_diff (element->duration, data->offset, &negative); + if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) { GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT " with offset %" G_GINT64_FORMAT " because it would result in an " "invalid duration", GES_ARGS (element), data->offset); + if (negative) + set_negative_duration_error (error, element, new_duration); return FALSE; } _CHECK_END (element, element->start, new_duration); if (GES_IS_CLIP (element)) { - GstClockTime limit = ges_clip_get_duration_limit (GES_CLIP (element)); - if (GST_CLOCK_TIME_IS_VALID (limit) && new_duration > limit) { + GESClip *clip = GES_CLIP (element); + GstClockTime limit = ges_clip_get_duration_limit (clip); + + if (GES_CLOCK_TIME_IS_LESS (limit, new_duration)) { GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT " with offset %" G_GINT64_FORMAT " because the duration would " "exceed the clip's duration-limit %" G_GINT64_FORMAT, GES_ARGS (element), data->offset, limit); + + set_breaks_duration_limit_error (error, clip, new_duration, limit); return FALSE; } } data->duration = new_duration; + + if (GES_IS_GROUP (element)) + return TRUE; + GST_INFO_OBJECT (element, "%s will trim end by setting duration to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->duration)); - return set_layer_priority (element, data); + return set_layer_priority (element, data, error); } -/* handles clips and track elements with no parents */ static gboolean -set_clip_edit_values (GESTimelineElement * element, EditData * data, - GHashTable * edit_table) +set_edit_values (GESTimelineElement * element, EditData * data, + GHashTable * edit_table, GError ** error) { switch (data->mode) { case EDIT_MOVE: - return set_edit_move_values (element, data); + return set_edit_move_values (element, data, error); case EDIT_TRIM_START: - return set_edit_trim_start_values (element, data, edit_table); + return set_edit_trim_start_values (element, data, edit_table, error); case EDIT_TRIM_END: - return set_edit_trim_end_values (element, data); + return set_edit_trim_end_values (element, data, error); } return FALSE; } @@ -1041,7 +1178,7 @@ add_clips_to_list (GNode * node, GList ** list) static gboolean replace_group_with_clip_edits (GNode * root, GESTimelineElement * group, - GHashTable * edit_table) + GHashTable * edit_table, GError ** err) { gboolean ret = TRUE; GList *tmp, *clips = NULL; @@ -1064,13 +1201,26 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group, goto error; } + group_edit->start = group->start; + group_edit->duration = group->duration; + + /* should only set the start and duration fields, table should not be + * needed, so we pass NULL */ + if (!set_edit_values (group, group_edit, NULL, err)) + goto error; + + new_start = group_edit->start; + new_end = _clock_time_plus (group_edit->start, group_edit->duration); + + if (!GST_CLOCK_TIME_IS_VALID (new_start) + || !GST_CLOCK_TIME_IS_VALID (new_end)) { + GST_ERROR_OBJECT (group, "Edit data gave an invalid start or end"); + goto error; + } + layer_offset = group_edit->layer_offset; mode = group_edit->mode; - if (!get_start_end_from_offset (group, mode, group_edit->offset, - &new_start, NULL, &new_end, NULL)) - goto error; - /* can traverse leaves to find all the clips since they are at _most_ * one step above the track elements */ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, @@ -1148,7 +1298,7 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group, } clip_data = new_edit_data (clip_mode, offset, layer_offset); g_hash_table_insert (edit_table, clip, clip_data); - if (!set_clip_edit_values (clip, clip_data, edit_table)) + if (!set_edit_values (clip, clip_data, edit_table, err)) goto error; } } @@ -1165,7 +1315,8 @@ error: /* set the edit values for the entries in @edits * any groups in @edits will be replaced by their clip children */ static gboolean -timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits) +timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits, + GError ** err) { gboolean ret = TRUE; GESTimelineElement *element; @@ -1183,9 +1334,9 @@ timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits) goto error; } if (GES_IS_GROUP (element)) - res = replace_group_with_clip_edits (root, element, edits); + res = replace_group_with_clip_edits (root, element, edits, err); else - res = set_clip_edit_values (element, edit_data, edits); + res = set_edit_values (element, edit_data, edits, err); if (!res) goto error; } @@ -1368,7 +1519,7 @@ add_element_edit (GHashTable * edits, GESTimelineElement * element, gboolean timeline_tree_can_move_element (GNode * root, GESTimelineElement * element, guint32 priority, GstClockTime start, - GstClockTime duration) + GstClockTime duration, GError ** error) { gboolean ret = FALSE; guint32 layer_prio = ges_timeline_element_get_layer_priority (element); @@ -1431,10 +1582,10 @@ timeline_tree_can_move_element (GNode * root, /* assume both edits can be performed if each could occur individually */ /* should not effect duration or in-point */ - if (!timeline_tree_set_element_edit_values (root, move_edits)) + if (!timeline_tree_set_element_edit_values (root, move_edits, error)) goto done; /* should not effect start or in-point or layer */ - if (!timeline_tree_set_element_edit_values (root, trim_edits)) + if (!timeline_tree_set_element_edit_values (root, trim_edits, error)) goto done; /* merge the two edits into moving positions */ @@ -1483,7 +1634,7 @@ timeline_tree_can_move_element (GNode * root, } /* check overlaps */ - if (!timeline_tree_can_move_elements (root, moving)) + if (!timeline_tree_can_move_elements (root, moving, error)) goto done; ret = TRUE; @@ -1624,7 +1775,7 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits) gboolean timeline_tree_ripple (GNode * root, GESTimelineElement * element, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance) + GstClockTime snapping_distance, GError ** error) { gboolean res = TRUE; GNode *node; @@ -1697,12 +1848,12 @@ timeline_tree_ripple (GNode * root, GESTimelineElement * element, /* check and set edits using snapped values */ give_edits_same_offset (edits, offset, layer_priority_offset); - if (!timeline_tree_set_element_edit_values (root, edits)) + if (!timeline_tree_set_element_edit_values (root, edits, error)) goto error; /* check overlaps */ set_moving_positions_from_edits (moving, edits); - if (!timeline_tree_can_move_elements (root, moving)) + if (!timeline_tree_can_move_elements (root, moving, error)) goto error; /* emit snapping now. Edits should only fail if a programming error @@ -1731,7 +1882,7 @@ error: gboolean timeline_tree_trim (GNode * root, GESTimelineElement * element, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance) + GstClockTime snapping_distance, GError ** error) { gboolean res = TRUE; GHashTable *edits = new_edit_table (); @@ -1778,12 +1929,12 @@ timeline_tree_trim (GNode * root, GESTimelineElement * element, /* check and set edits using snapped values */ give_edits_same_offset (edits, offset, layer_priority_offset); - if (!timeline_tree_set_element_edit_values (root, edits)) + if (!timeline_tree_set_element_edit_values (root, edits, error)) goto error; /* check overlaps */ set_moving_positions_from_edits (moving, edits); - if (!timeline_tree_can_move_elements (root, moving)) { + if (!timeline_tree_can_move_elements (root, moving, error)) { goto error; } @@ -1813,7 +1964,7 @@ error: gboolean timeline_tree_move (GNode * root, GESTimelineElement * element, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance) + GstClockTime snapping_distance, GError ** error) { gboolean res = TRUE; GHashTable *edits = new_edit_table (); @@ -1861,12 +2012,12 @@ timeline_tree_move (GNode * root, GESTimelineElement * element, /* check and set edits using snapped values */ give_edits_same_offset (edits, offset, layer_priority_offset); - if (!timeline_tree_set_element_edit_values (root, edits)) + if (!timeline_tree_set_element_edit_values (root, edits, error)) goto error; /* check overlaps */ set_moving_positions_from_edits (moving, edits); - if (!timeline_tree_can_move_elements (root, moving)) { + if (!timeline_tree_can_move_elements (root, moving, error)) { goto error; } @@ -1960,7 +2111,8 @@ find_sources_at_position (GNode * node, TreeIterationData * data) gboolean timeline_tree_roll (GNode * root, GESTimelineElement * element, - GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance) + GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance, + GError ** error) { gboolean res = TRUE; GList *tmp; @@ -2043,12 +2195,12 @@ timeline_tree_roll (GNode * root, GESTimelineElement * element, /* check and set edits using snapped values */ give_edits_same_offset (edits, offset, 0); - if (!timeline_tree_set_element_edit_values (root, edits)) + if (!timeline_tree_set_element_edit_values (root, edits, error)) goto error; /* check overlaps */ set_moving_positions_from_edits (moving, edits); - if (!timeline_tree_can_move_elements (root, moving)) { + if (!timeline_tree_can_move_elements (root, moving, error)) { goto error; } diff --git a/ges/ges-timeline-tree.h b/ges/ges-timeline-tree.h index 1e4c618c6e..5fafba35a4 100644 --- a/ges/ges-timeline-tree.h +++ b/ges/ges-timeline-tree.h @@ -13,14 +13,16 @@ gboolean timeline_tree_can_move_element (GNode *root, GESTimelineElement *element, guint32 priority, GstClockTime start, - GstClockTime duration); + GstClockTime duration, + GError ** error); gboolean timeline_tree_ripple (GNode *root, GESTimelineElement *element, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance); + GstClockTime snapping_distance, + GError ** error); void ges_timeline_emit_snapping (GESTimeline * timeline, GESTrackElement * elem1, @@ -32,7 +34,8 @@ gboolean timeline_tree_trim (GNode *root, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance); + GstClockTime snapping_distance, + GError ** error); gboolean timeline_tree_move (GNode *root, @@ -40,13 +43,15 @@ gboolean timeline_tree_move (GNode *root, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance); + GstClockTime snapping_distance, + GError ** error); gboolean timeline_tree_roll (GNode * root, GESTimelineElement * element, GstClockTimeDiff offset, GESEdge edge, - GstClockTime snapping_distance); + GstClockTime snapping_distance, + GError ** error); typedef GESAutoTransition * (*GESTreeGetAutoTransitionFunc) (GESTimeline * timeline, diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index e034c5c38f..94af1457d6 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -52,20 +52,74 @@ * content prioritised in the tracks. This ordering can be changed using * ges_timeline_move_layer(). * + * ## Editing + * + * See #GESTimelineElement for the various ways the elements of a timeline + * can be edited. + * + * If you change the timing or ordering of a timeline's + * #GESTimelineElement-s, then these changes will not actually be taken + * into account in the output of the timeline's tracks until the + * ges_timeline_commit() method is called. This allows you to move its + * elements around, say, in response to an end user's mouse dragging, with + * little expense before finalising their effect on the produced data. + * + * ## Overlaps and Auto-Transitions + * + * There are certain restrictions placed on how #GESSource-s may overlap + * in a #GESTrack that belongs to a timeline. These will be enforced by + * GES, so the user will not need to keep track of them, but they should + * be aware that certain edits will be refused as a result if the overlap + * rules would be broken. + * + * Consider two #GESSource-s, `A` and `B`, with start times `startA` and + * `startB`, and end times `endA` and `endB`, respectively. The start + * time refers to their #GESTimelineElement:start, and the end time is + * their #GESTimelineElement:start + #GESTimelineElement:duration. These + * two sources *overlap* if: + * + * + they share the same #GESTrackElement:track (non %NULL), which belongs + * to the timeline; + * + they share the same #GES_TIMELINE_ELEMENT_LAYER_PRIORITY; and + * + `startA < endB` and `startB < endA `. + * + * Note that when `startA = endB` or `startB = endA` then the two sources + * will *touch* at their edges, but are not considered overlapping. + * + * If, in addition, `startA < startB < endA`, then we can say that the + * end of `A` overlaps the start of `B`. + * + * If, instead, `startA <= startB` and `endA >= endB`, then we can say + * that `A` fully overlaps `B`. + * + * The overlap rules for a timeline are that: + * + * 1. One source cannot fully overlap another source. + * 2. A source can only overlap the end of up to one other source at its + * start. + * 3. A source can only overlap the start of up to one other source at its + * end. + * + * The last two rules combined essentially mean that at any given timeline + * position, only up to two #GESSource-s may overlap at that position. So + * triple or more overlaps are not allowed. + * + * If you switch on #GESTimeline:auto-transition, then at any moment when + * the end of one source (the first source) overlaps the start of another + * (the second source), a #GESTransitionClip will be automatically created + * for the pair in the same layer and it will cover their overlap. If the + * two elements are edited in a way such that the end of the first source + * no longer overlaps the start of the second, the transition will be + * automatically removed from the timeline. However, if the two sources + * still overlap at the same edges after the edit, then the same + * transition object will be kept, but with its timing and layer adjusted + * accordingly. + * * ## Saving * * To save/load a timeline, you can use the ges_timeline_load_from_uri() * and ges_timeline_save_to_uri() methods that use the default format. * - * ## Editing - * - * If you change the timing or ordering of a timeline's - * #GESTimelineElement-s, then these changes will not actually be taken - * into account in the timeline until the ges_timeline_commit() method is - * called. This allows you to move its elements around, say, in - * response to an end user's mouse dragging, with little expense before - * finalising their effect. - * * ## Playing * * A timeline is a #GstBin with a source #GstPad for each of its @@ -152,7 +206,11 @@ struct _GESTimelinePrivate /* While we are creating and adding the TrackElements for a clip, we need to * ignore the child-added signal */ gboolean track_elements_moving; - gboolean track_selection_error; + /* whether any error occurred during track selection, including + * programming or usage errors */ + gboolean has_any_track_selection_error; + /* error set for non-programming/usage errors */ + GError *track_selection_error; GList *groups; guint stream_start_group_id; @@ -355,6 +413,8 @@ ges_timeline_dispose (GObject * object) gst_clear_object (&priv->auto_transition_track); gst_clear_object (&priv->new_track); + g_clear_error (&priv->track_selection_error); + priv->track_selection_error = NULL; G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object); } @@ -553,8 +613,10 @@ ges_timeline_class_init (GESTimelineClass * klass) /** * GESTimeline:auto-transition: * - * Whether to automatically create a transition whenever two clips - * overlap in the timeline. See #GESLayer:auto-transition. + * Whether to automatically create a transition whenever two + * #GESSource-s overlap in a track of the timeline. See + * #GESLayer:auto-transition if you want this to only happen in some + * layers. */ g_object_class_install_property (object_class, PROP_AUTO_TRANSITION, g_param_spec_boolean ("auto-transition", "Auto-Transition", @@ -1168,8 +1230,8 @@ ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze) static gint _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element, - GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, - GstClockTime position) + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, + GstClockTime position, GError ** error) { GList *tmp; guint32 layer_prio = ges_timeline_element_get_layer_priority (element); @@ -1210,8 +1272,8 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element, GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place of " "trimming the corresponding auto-transition", GES_ARGS (replace)); - return ges_timeline_element_edit (replace, layers, -1, mode, edge, - position); + return ges_timeline_element_edit_full (replace, -1, mode, edge, + position, error); } } @@ -1220,8 +1282,8 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element, gboolean ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, - GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, - guint64 position) + gint64 new_layer_priority, GESEditMode mode, GESEdge edge, + guint64 position, GError ** error) { GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ? GST_CLOCK_DIFF (position, element->start + element->duration) : @@ -1231,8 +1293,8 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, gint res = -1; if ((GES_IS_TRANSITION (element) || GES_IS_TRANSITION_CLIP (element))) - res = _edit_auto_transition (timeline, element, layers, new_layer_priority, - mode, edge, position); + res = _edit_auto_transition (timeline, element, new_layer_priority, mode, + edge, position, error); if (res != -1) return res; @@ -1240,20 +1302,20 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, switch (mode) { case GES_EDIT_MODE_RIPPLE: return timeline_tree_ripple (timeline->priv->tree, element, prio_diff, - edge_diff, edge, timeline->priv->snapping_distance); + edge_diff, edge, timeline->priv->snapping_distance, error); case GES_EDIT_MODE_TRIM: return timeline_tree_trim (timeline->priv->tree, element, prio_diff, - edge_diff, edge, timeline->priv->snapping_distance); + edge_diff, edge, timeline->priv->snapping_distance, error); case GES_EDIT_MODE_NORMAL: return timeline_tree_move (timeline->priv->tree, element, prio_diff, - edge_diff, edge, timeline->priv->snapping_distance); + edge_diff, edge, timeline->priv->snapping_distance, error); case GES_EDIT_MODE_ROLL: if (prio_diff != 0) { GST_WARNING_OBJECT (element, "Cannot roll an element to a new layer"); return FALSE; } return timeline_tree_roll (timeline->priv->tree, element, - edge_diff, edge, timeline->priv->snapping_distance); + edge_diff, edge, timeline->priv->snapping_distance, error); case GES_EDIT_MODE_SLIDE: GST_ERROR_OBJECT (element, "Sliding not implemented."); return FALSE; @@ -1420,7 +1482,7 @@ _get_selected_tracks (GESTimeline * timeline, GESClip * clip, * selected tracks */ static gboolean _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip, - GESTrackElement * track_element) + GESTrackElement * track_element, GError ** error) { guint i; gboolean ret = TRUE; @@ -1428,8 +1490,11 @@ _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip, for (i = 0; i < tracks->len; i++) { GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i)); - if (!ges_clip_add_child_to_track (clip, track_element, track, NULL)) + if (!ges_clip_add_child_to_track (clip, track_element, track, error)) { ret = FALSE; + if (error) + break; + } } g_ptr_array_unref (tracks); @@ -1439,7 +1504,7 @@ _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip, static gboolean _try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip, - GESTrackElement * track_element, GESTrack * track) + GESTrackElement * track_element, GESTrack * track, GError ** error) { gboolean no_error = TRUE; GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element); @@ -1449,7 +1514,7 @@ _try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip, * if it does we add the track element to the track, or add a copy if * the track element is already in a track */ if (g_ptr_array_find (tracks, track, NULL)) { - if (!ges_clip_add_child_to_track (clip, track_element, track, NULL)) + if (!ges_clip_add_child_to_track (clip, track_element, track, error)) no_error = FALSE; } @@ -1469,21 +1534,46 @@ ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving) } static void -_set_track_selection_error (GESTimeline * timeline, gboolean error) +ges_timeline_set_track_selection_error (GESTimeline * timeline, + gboolean was_error, GError * error) { + GESTimelinePrivate *priv; + LOCK_DYN (timeline); - timeline->priv->track_selection_error = error; + + priv = timeline->priv; + g_clear_error (&priv->track_selection_error); + priv->track_selection_error = error; + priv->has_any_track_selection_error = was_error; + UNLOCK_DYN (timeline); } static gboolean -_get_track_selection_error (GESTimeline * timeline) +ges_timeline_take_track_selection_error (GESTimeline * timeline, + GError ** error) { gboolean ret; + GESTimelinePrivate *priv; LOCK_DYN (timeline); - ret = timeline->priv->track_selection_error; - timeline->priv->track_selection_error = FALSE; + + priv = timeline->priv; + if (error) { + if (*error) { + GST_ERROR_OBJECT (timeline, "Error not handled %s", (*error)->message); + g_error_free (*error); + } + *error = priv->track_selection_error; + } else if (priv->track_selection_error) { + GST_WARNING_OBJECT (timeline, "Got track selection error: %s", + priv->track_selection_error->message); + g_error_free (priv->track_selection_error); + } + priv->track_selection_error = NULL; + ret = priv->has_any_track_selection_error; + priv->has_any_track_selection_error = FALSE; + UNLOCK_DYN (timeline); return ret; @@ -1494,7 +1584,8 @@ clip_track_element_added_cb (GESClip * clip, GESTrackElement * track_element, GESTimeline * timeline) { GESTrack *auto_trans_track, *new_track; - gboolean error = FALSE; + GError *error = NULL; + gboolean success = FALSE; if (timeline->priv->track_elements_moving) { GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT @@ -1521,21 +1612,24 @@ clip_track_element_added_cb (GESClip * clip, if (auto_trans_track) { /* don't use track-selection */ - if (!ges_clip_add_child_to_track (clip, track_element, auto_trans_track, - NULL)) - error = TRUE; + success = ! !ges_clip_add_child_to_track (clip, track_element, + auto_trans_track, &error); gst_object_unref (auto_trans_track); } else { if (new_track) - error = - !_try_add_track_element_to_track (timeline, clip, track_element, - new_track); + success = _try_add_track_element_to_track (timeline, clip, track_element, + new_track, &error); else - error = !_add_track_element_to_tracks (timeline, clip, track_element); + success = _add_track_element_to_tracks (timeline, clip, track_element, + &error); } - if (error) - _set_track_selection_error (timeline, TRUE); + if (error || !success) { + if (!error) + GST_WARNING_OBJECT (timeline, "Track selection failed for %" GES_FORMAT, + GES_ARGS (track_element)); + ges_timeline_set_track_selection_error (timeline, TRUE, error); + } } static void @@ -1573,7 +1667,7 @@ track_element_added_cb (GESTrack * track, GESTrackElement * element, /* returns TRUE if no errors in adding to tracks */ static gboolean _add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip, - gboolean add_core, GESTrack * new_track, GList * blacklist) + gboolean add_core, GESTrack * new_track, GList * blacklist, GError ** error) { GList *tmp, *children; gboolean no_errors = TRUE; @@ -1589,13 +1683,19 @@ _add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip, if (ges_track_element_get_track (el) == NULL) { gboolean res; if (new_track) - res = _try_add_track_element_to_track (timeline, clip, el, new_track); + res = _try_add_track_element_to_track (timeline, clip, el, new_track, + error); else - res = _add_track_element_to_tracks (timeline, clip, el); - if (!res) + res = _add_track_element_to_tracks (timeline, clip, el, error); + if (!res) { no_errors = FALSE; + if (error) + goto done; + } } } + +done: g_list_free_full (children, gst_object_unref); return no_errors; @@ -1604,12 +1704,10 @@ _add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip, /* returns TRUE if no errors in adding to tracks */ static gboolean add_object_to_tracks (GESTimeline * timeline, GESClip * clip, - GESTrack * new_track) + GESTrack * new_track, GError ** error) { GList *tracks, *tmp, *list, *created, *just_added = NULL; gboolean no_errors = TRUE; - /* TODO: extend with GError ** argument, which is accepted by - * ges_clip_add_child_to_track */ GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT " trackelements and adding them to our tracks", clip); @@ -1619,12 +1717,19 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip, g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL); timeline->priv->new_track = new_track ? gst_object_ref (new_track) : NULL; UNLOCK_DYN (timeline); + /* create core elements */ for (tmp = tracks; tmp; tmp = tmp->next) { GESTrack *track = GES_TRACK (tmp->data); if (new_track && track != new_track) continue; + list = ges_clip_create_track_elements (clip, track->type); + /* just_added only used for pointer comparison, so safe to include + * elements that may be destroyed because they fail to be added to + * the clip */ + just_added = g_list_concat (just_added, list); + for (created = list; created; created = created->next) { GESTimelineElement *el = created->data; @@ -1640,21 +1745,25 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip, * released. And when a child is selected for multiple tracks, their * copy will be added to the clip before the track is selected, so * the track will not be set in the child-added signal */ - _set_track_selection_error (timeline, FALSE); - if (!ges_container_add (GES_CONTAINER (clip), el)) + ges_timeline_set_track_selection_error (timeline, FALSE, 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 (_get_track_selection_error (timeline)) - no_errors = FALSE; - + } gst_object_unref (el); + + if (error && !no_errors) + goto done; + + if (ges_timeline_take_track_selection_error (timeline, error)) { + no_errors = FALSE; + if (error) + goto done; + /* else, carry on as much as we can */ + } } - /* just_added only used for pointer comparison, so safe to include - * elements that are now destroyed because they failed to be added to - * the clip */ - just_added = g_list_concat (just_added, list); } - g_list_free_full (tracks, gst_object_unref); /* set the tracks for the other children, with core elements first to * make sure the non-core can be placed above them in the track (a @@ -1662,11 +1771,21 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip, /* include just_added as a blacklist to ensure we do not try the track * selection a second time when track selection returns no tracks */ if (!_add_clip_children_to_tracks (timeline, clip, TRUE, new_track, - just_added)) + just_added, error)) { no_errors = FALSE; + if (error) + goto done; + } + if (!_add_clip_children_to_tracks (timeline, clip, FALSE, new_track, - just_added)) + just_added, error)) { no_errors = FALSE; + if (error) + goto done; + } + +done: + g_list_free_full (tracks, gst_object_unref); LOCK_DYN (timeline); gst_clear_object (&timeline->priv->new_track); @@ -1721,12 +1840,10 @@ layer_auto_transition_changed_cb (GESLayer * layer, /* returns TRUE if selecting of tracks did not error */ gboolean -ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip) +ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error) { GESProject *project; gboolean ret; - /* TODO: extend with GError ** argument, which is accepted by - * ges_clip_add_child_to_track */ ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), timeline); @@ -1754,7 +1871,7 @@ ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip) /* timeline-tree handles creation of auto-transitions */ ret = TRUE; } else { - ret = add_object_to_tracks (timeline, clip, NULL); + ret = add_object_to_tracks (timeline, clip, NULL, error); } GST_DEBUG ("Done"); @@ -2215,7 +2332,7 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer) /* add any existing clips to the timeline */ objects = ges_layer_get_clips (layer); for (tmp = objects; tmp; tmp = tmp->next) - ges_timeline_add_clip (timeline, tmp->data); + ges_timeline_add_clip (timeline, tmp->data, NULL); g_list_free_full (objects, gst_object_unref); return TRUE; @@ -2361,7 +2478,7 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) objects = ges_layer_get_clips (tmp->data); for (obj = objects; obj; obj = obj->next) - add_object_to_tracks (timeline, obj->data, track); + add_object_to_tracks (timeline, obj->data, track, NULL); g_list_free_full (objects, gst_object_unref); } diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index 2174bb521f..5461432f03 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -630,6 +630,7 @@ _set_inpoint (GESTimelineElement * element, GstClockTime inpoint) { GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTimelineElement *parent = element->parent; + GError *error = NULL; g_return_val_if_fail (object->priv->nleobject, FALSE); if (inpoint && !object->priv->has_internal_source) { @@ -640,10 +641,12 @@ _set_inpoint (GESTimelineElement * element, GstClockTime inpoint) if (GES_IS_CLIP (parent) && !ges_clip_can_set_inpoint_of_child (GES_CLIP (parent), object, - inpoint)) { + inpoint, &error)) { GST_WARNING_OBJECT (element, "Cannot set an in-point of %" GST_TIME_FORMAT " because the parent clip %" GES_FORMAT - " would not allow it", GST_TIME_ARGS (inpoint), GES_ARGS (parent)); + " would not allow it%s%s", GST_TIME_ARGS (inpoint), + GES_ARGS (parent), error ? ": " : "", error ? error->message : ""); + g_clear_error (&error); return FALSE; } @@ -678,6 +681,7 @@ _set_max_duration (GESTimelineElement * element, GstClockTime max_duration) { GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTimelineElement *parent = element->parent; + GError *error = NULL; if (GST_CLOCK_TIME_IS_VALID (max_duration) && !object->priv->has_internal_source) { @@ -688,10 +692,12 @@ _set_max_duration (GESTimelineElement * element, GstClockTime max_duration) if (GES_IS_CLIP (parent) && !ges_clip_can_set_max_duration_of_child (GES_CLIP (parent), object, - max_duration)) { + max_duration, &error)) { GST_WARNING_OBJECT (element, "Cannot set a max-duration of %" GST_TIME_FORMAT " because the parent clip %" GES_FORMAT - " would not allow it", GST_TIME_ARGS (max_duration), GES_ARGS (parent)); + " would not allow it%s%s", GST_TIME_ARGS (max_duration), + GES_ARGS (parent), error ? ": " : "", error ? error->message : ""); + g_clear_error (&error); return FALSE; } @@ -704,6 +710,7 @@ _set_priority (GESTimelineElement * element, guint32 priority) { GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTimelineElement *parent = element->parent; + GError *error = NULL; g_return_val_if_fail (object->priv->nleobject, FALSE); @@ -720,10 +727,12 @@ _set_priority (GESTimelineElement * element, guint32 priority) if (GES_IS_CLIP (parent) && !ges_clip_can_set_priority_of_child (GES_CLIP (parent), object, - priority)) { + priority, &error)) { GST_WARNING_OBJECT (element, "Cannot set a priority of %" G_GUINT32_FORMAT " because the parent clip %" GES_FORMAT - " would not allow it", priority, GES_ARGS (parent)); + " would not allow it%s%s", priority, GES_ARGS (parent), + error ? ": " : "", error ? error->message : ""); + g_clear_error (&error); return FALSE; } @@ -751,6 +760,7 @@ gboolean ges_track_element_set_active (GESTrackElement * object, gboolean active) { GESTimelineElement *parent; + GError *error = NULL; g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); g_return_val_if_fail (object->priv->nleobject, FALSE); @@ -761,9 +771,13 @@ ges_track_element_set_active (GESTrackElement * object, gboolean active) parent = GES_TIMELINE_ELEMENT_PARENT (object); if (GES_IS_CLIP (parent) - && !ges_clip_can_set_active_of_child (GES_CLIP (parent), object, active)) { - GST_WARNING_OBJECT (object, "Cannot set active to %i because the parent " - "clip %" GES_FORMAT " would not allow it", active, GES_ARGS (parent)); + && !ges_clip_can_set_active_of_child (GES_CLIP (parent), object, active, + &error)) { + GST_WARNING_OBJECT (object, + "Cannot set active to %i because the parent clip %" GES_FORMAT + " would not allow it%s%s", active, GES_ARGS (parent), error ? ": " : "", + error ? error->message : ""); + g_clear_error (&error); return FALSE; } @@ -1057,7 +1071,8 @@ ges_track_element_add_children_props (GESTrackElement * self, /* INTERNAL USAGE */ gboolean -ges_track_element_set_track (GESTrackElement * object, GESTrack * track) +ges_track_element_set_track (GESTrackElement * object, GESTrack * track, + GError ** error) { GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (object); @@ -1066,10 +1081,11 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track) GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track); if (GES_IS_CLIP (parent) - && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track)) { - GST_WARNING_OBJECT (object, "The parent clip %" GES_FORMAT " would " - "not allow the track to be set to %" GST_PTR_FORMAT, - GES_ARGS (parent), track); + && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track, + error)) { + GST_INFO_OBJECT (object, + "The parent clip %" GES_FORMAT " would not allow the track to be " + "set to %" GST_PTR_FORMAT, GES_ARGS (parent), track); return FALSE; } diff --git a/ges/ges-track.c b/ges/ges-track.c index 8896c3375b..5dddb4d2f7 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -383,7 +383,7 @@ ges_track_get_composition (GESTrack * track) */ static gboolean remove_object_internal (GESTrack * track, GESTrackElement * object, - gboolean emit) + gboolean emit, GError ** error) { GESTrackPrivate *priv; GstElement *nleobject; @@ -397,8 +397,8 @@ remove_object_internal (GESTrack * track, GESTrackElement * object, return FALSE; } - if (!ges_track_element_set_track (object, NULL)) { - GST_WARNING_OBJECT (track, "Failed to unset the track for %" GES_FORMAT, + if (!ges_track_element_set_track (object, NULL, error)) { + GST_INFO_OBJECT (track, "Failed to unset the track for %" GES_FORMAT, GES_ARGS (object)); return FALSE; } @@ -426,7 +426,7 @@ remove_object_internal (GESTrack * track, GESTrackElement * object, static void dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track) { - remove_object_internal (track, trackelement, TRUE); + remove_object_internal (track, trackelement, TRUE, NULL); } /* GstElement virtual methods */ @@ -1110,7 +1110,7 @@ notify: static gboolean remove_element_internal (GESTrack * track, GESTrackElement * object, - gboolean emit) + gboolean emit, GError ** error) { GSequenceIter *it; GESTrackPrivate *priv = track->priv; @@ -1120,7 +1120,7 @@ remove_element_internal (GESTrack * track, GESTrackElement * object, it = g_hash_table_lookup (priv->trackelements_iter, object); g_sequence_remove (it); - if (remove_object_internal (track, object, emit) == TRUE) { + if (remove_object_internal (track, object, emit, error) == TRUE) { ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL); return TRUE; @@ -1134,9 +1134,10 @@ remove_element_internal (GESTrack * track, GESTrackElement * object, } /** - * ges_track_add_element: + * ges_track_add_element_full: * @track: A #GESTrack * @object: (transfer floating): The element to add + * @error: (nullable): Return location for an error * * Adds the given track element to the track, which takes ownership of the * element. @@ -1149,13 +1150,15 @@ remove_element_internal (GESTrack * track, GESTrackElement * object, * Returns: %TRUE if @object was successfully added to @track. */ gboolean -ges_track_add_element (GESTrack * track, GESTrackElement * object) +ges_track_add_element_full (GESTrack * track, GESTrackElement * object, + GError ** error) { GESTimeline *timeline; GESTimelineElement *el; g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); el = GES_TIMELINE_ELEMENT (object); @@ -1170,8 +1173,8 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) return FALSE; } - if (!ges_track_element_set_track (object, track)) { - GST_WARNING_OBJECT (track, "Failed to set the track for %" GES_FORMAT, + if (!ges_track_element_set_track (object, track, error)) { + GST_INFO_OBJECT (track, "Failed to set the track for %" GES_FORMAT, GES_ARGS (object)); gst_object_ref_sink (object); gst_object_unref (object); @@ -1186,7 +1189,9 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition, ges_track_element_get_nleobject (object)))) { GST_WARNING ("Couldn't add object to the NleComposition"); - ges_track_element_set_track (object, NULL); + if (!ges_track_element_set_track (object, NULL, NULL)) + GST_ERROR_OBJECT (track, "Failed to unset track of element %" + GES_FORMAT, GES_ARGS (object)); gst_object_ref_sink (object); gst_object_unref (object); return FALSE; @@ -1203,12 +1208,13 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) * element to the track */ if (timeline && !timeline_tree_can_move_element (timeline_get_tree (timeline), el, - GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration)) { - GST_WARNING_OBJECT (track, + GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration, + error)) { + GST_INFO_OBJECT (track, "Could not add the track element %" GES_FORMAT " to the track because it breaks the timeline " "configuration rules", GES_ARGS (el)); - remove_element_internal (track, object, FALSE); + remove_element_internal (track, object, FALSE, NULL); return FALSE; } @@ -1218,6 +1224,21 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object) return TRUE; } +/** + * ges_track_add_element: + * @track: A #GESTrack + * @object: (transfer floating): The element to add + * + * See ges_track_add_element(), which also gives an error. + * + * Returns: %TRUE if @object was successfully added to @track. + */ +gboolean +ges_track_add_element (GESTrack * track, GESTrackElement * object) +{ + return ges_track_add_element_full (track, object, NULL); +} + /** * ges_track_get_elements: * @track: A #GESTrack @@ -1245,9 +1266,10 @@ ges_track_get_elements (GESTrack * track) } /** - * ges_track_remove_element: + * ges_track_remove_element_full: * @track: A #GESTrack * @object: The element to remove + * @error: (nullable): Return location for an error * * Removes the given track element from the track, which revokes * ownership of the element. @@ -1255,16 +1277,33 @@ ges_track_get_elements (GESTrack * track) * Returns: %TRUE if @object was successfully removed from @track. */ gboolean -ges_track_remove_element (GESTrack * track, GESTrackElement * object) +ges_track_remove_element_full (GESTrack * track, GESTrackElement * object, + GError ** error) { g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); if (!track->priv->timeline || !ges_timeline_is_disposed (track->priv->timeline)) CHECK_THREAD (track); - return remove_element_internal (track, object, TRUE); + return remove_element_internal (track, object, TRUE, error); +} + +/** + * ges_track_remove_element: + * @track: A #GESTrack + * @object: The element to remove + * + * See ges_track_remove_element_full(), which also returns an error. + * + * Returns: %TRUE if @object was successfully removed from @track. + */ +gboolean +ges_track_remove_element (GESTrack * track, GESTrackElement * object) +{ + return ges_track_remove_element_full (track, object, NULL); } /** diff --git a/ges/ges-track.h b/ges/ges-track.h index 4a51ba760d..46da9c8ccf 100644 --- a/ges/ges-track.h +++ b/ges/ges-track.h @@ -83,25 +83,41 @@ const GESTimeline* ges_track_get_timeline (GESTrack *track); GES_API gboolean ges_track_commit (GESTrack *track); GES_API -void ges_track_set_timeline (GESTrack *track, GESTimeline *timeline); +void ges_track_set_timeline (GESTrack *track, + GESTimeline *timeline); GES_API -gboolean ges_track_add_element (GESTrack *track, GESTrackElement *object); +gboolean ges_track_add_element (GESTrack *track, + GESTrackElement *object); GES_API -gboolean ges_track_remove_element (GESTrack *track, GESTrackElement *object); +gboolean ges_track_add_element_full (GESTrack *track, + GESTrackElement *object, + GError ** error); GES_API -void ges_track_set_create_element_for_gap_func (GESTrack *track, GESCreateElementForGapFunc func); +gboolean ges_track_remove_element (GESTrack *track, + GESTrackElement *object); GES_API -void ges_track_set_mixing (GESTrack *track, gboolean mixing); +gboolean ges_track_remove_element_full (GESTrack *track, + GESTrackElement *object, + GError ** error); +GES_API +void ges_track_set_create_element_for_gap_func (GESTrack *track, + GESCreateElementForGapFunc func); +GES_API +void ges_track_set_mixing (GESTrack *track, + gboolean mixing); GES_API gboolean ges_track_get_mixing (GESTrack *track); GES_API -void ges_track_set_restriction_caps (GESTrack *track, const GstCaps *caps); +void ges_track_set_restriction_caps (GESTrack *track, + const GstCaps *caps); GES_API -void ges_track_update_restriction_caps (GESTrack *track, const GstCaps *caps); +void ges_track_update_restriction_caps (GESTrack *track, + const GstCaps *caps); GES_API GstCaps * ges_track_get_restriction_caps (GESTrack * track); GES_API -GESTrack* ges_track_new (GESTrackType type, GstCaps * caps); +GESTrack* ges_track_new (GESTrackType type, + GstCaps * caps); G_END_DECLS diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index 37bfaa93bf..e93f36d12d 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -1249,6 +1249,7 @@ GST_START_TEST (test_adding_children_to_track) GESTrackElement *source, *effect, *effect2, *added, *added2, *added3; GstControlSource *ctrl_source; guint selection_called = 0; + GError *error = NULL; ges_init (); @@ -1257,7 +1258,6 @@ GST_START_TEST (test_adding_children_to_track) track1 = GES_TRACK (ges_video_track_new ()); track2 = GES_TRACK (ges_video_track_new ()); - /* only add two for now */ fail_unless (ges_timeline_add_track (timeline, track1)); @@ -1325,18 +1325,21 @@ GST_START_TEST (test_adding_children_to_track) gst_object_unref (ctrl_source); /* can't add to a track that does not belong to the timeline */ - fail_if (ges_clip_add_child_to_track (clip, source, track2, NULL)); + fail_if (ges_clip_add_child_to_track (clip, source, track2, &error)); assert_num_children (clip, 3); fail_unless (ges_track_element_get_track (source) == track1); assert_num_in_track (track1, 3); assert_num_in_track (track2, 0); + /* programming/usage error gives no error code/message */ + fail_if (error); /* can't add the clip to a track that already contains our source */ - fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL)); + fail_if (ges_clip_add_child_to_track (clip, source, track1, &error)); assert_num_children (clip, 3); fail_unless (ges_track_element_get_track (source) == track1); assert_num_in_track (track1, 3); assert_num_in_track (track2, 0); + fail_if (error); /* can't remove a core element from its track whilst a non-core sits * above it */ @@ -1347,10 +1350,11 @@ GST_START_TEST (test_adding_children_to_track) assert_num_in_track (track2, 0); /* can not add to the same track as it is currently in */ - fail_if (ges_clip_add_child_to_track (clip, effect, track1, NULL)); + fail_if (ges_clip_add_child_to_track (clip, effect, track1, &error)); fail_unless (ges_track_element_get_track (effect) == track1); assert_num_in_track (track1, 3); assert_num_in_track (track2, 0); + fail_if (error); /* adding another video track, select-tracks-for-object will do nothing * since no each track element is already part of a track */ @@ -1360,14 +1364,15 @@ GST_START_TEST (test_adding_children_to_track) assert_num_in_track (track2, 0); /* can not add effect to a track that does not contain a core child */ - fail_if (ges_clip_add_child_to_track (clip, effect, track2, NULL)); + fail_if (ges_clip_add_child_to_track (clip, effect, track2, &error)); assert_num_children (clip, 3); assert_num_in_track (track1, 3); assert_num_in_track (track2, 0); + fail_if (error); /* can add core */ - added = ges_clip_add_child_to_track (clip, source, track2, NULL); + added = ges_clip_add_child_to_track (clip, source, track2, &error); fail_unless (added); assert_num_children (clip, 4); fail_unless (added != source); @@ -1375,6 +1380,7 @@ GST_START_TEST (test_adding_children_to_track) fail_unless (ges_track_element_get_track (added) == track2); assert_num_in_track (track1, 3); assert_num_in_track (track2, 1); + fail_if (error); assert_equal_children_properties (added, source); assert_equal_bindings (added, source); @@ -1385,8 +1391,9 @@ GST_START_TEST (test_adding_children_to_track) assert_equals_int (1, ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); - added2 = ges_clip_add_child_to_track (clip, effect, track2, NULL); + added2 = ges_clip_add_child_to_track (clip, effect, track2, &error); fail_unless (added2); + fail_if (error); assert_num_children (clip, 5); fail_unless (added2 != effect); fail_unless (ges_track_element_get_track (effect) == track1); @@ -1404,8 +1411,9 @@ GST_START_TEST (test_adding_children_to_track) assert_equals_int (2, ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); - added3 = ges_clip_add_child_to_track (clip, effect2, track2, NULL); + added3 = ges_clip_add_child_to_track (clip, effect2, track2, &error); fail_unless (added3); + fail_if (error); assert_num_children (clip, 6); fail_unless (added3 != effect2); fail_unless (ges_track_element_get_track (effect2) == track1); @@ -1500,36 +1508,41 @@ GST_START_TEST (test_adding_children_to_track) /* can not add the source to the track because it would overlap another * source */ - fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL)); + fail_if (ges_clip_add_child_to_track (clip, source, track1, &error)); assert_num_children (clip, 3); assert_num_in_track (track1, 4); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); /* can not add source at time 23 because it would result in three * overlapping sources in the track */ assert_set_start (clip, 23); - fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL)); + fail_if (ges_clip_add_child_to_track (clip, source, track1, &error)); assert_num_children (clip, 3); assert_num_in_track (track1, 4); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); /* can add at 5, with overlap */ assert_set_start (clip, 5); - added = ges_clip_add_child_to_track (clip, source, track1, NULL); + added = ges_clip_add_child_to_track (clip, source, track1, &error); /* added is the source since it was not already in a track */ fail_unless (added == source); + fail_if (error); assert_num_children (clip, 3); /* 4 sources + 2 transitions */ assert_num_in_track (track1, 6); /* also add effect */ - added = ges_clip_add_child_to_track (clip, effect, track1, NULL); + added = ges_clip_add_child_to_track (clip, effect, track1, &error); /* added is the source since it was not already in a track */ fail_unless (added == effect); + fail_if (error); assert_num_children (clip, 3); assert_num_in_track (track1, 7); - added = ges_clip_add_child_to_track (clip, effect2, track1, NULL); + added = ges_clip_add_child_to_track (clip, effect2, track1, &error); /* added is the source since it was not already in a track */ fail_unless (added == effect2); + fail_if (error); assert_num_children (clip, 3); assert_num_in_track (track1, 8); @@ -3071,6 +3084,7 @@ GST_START_TEST (test_can_set_duration_limit) GESTrackElement *effect0, *effect1, *effect2; GESTrack *track0, *track1; gint limit_notify_count = 0; + GError *error = NULL; ges_init (); @@ -3175,7 +3189,8 @@ GST_START_TEST (test_can_set_duration_limit) CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36); CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 10); - fail_if (ges_clip_add_child_to_track (clip, effect0, track0, NULL)); + fail_if (ges_clip_add_child_to_track (clip, effect0, track0, &error)); + assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK); /* set max-duration to 11 and we are fine to select a track */ assert_set_max_duration (effect0, 11); @@ -3183,7 +3198,8 @@ GST_START_TEST (test_can_set_duration_limit) _assert_duration_limit (clip, 20); fail_unless (ges_clip_add_child_to_track (clip, effect0, track0, - NULL) == effect0); + &error) == effect0); + fail_if (error); assert_equals_int (limit_notify_count, 2); _assert_duration_limit (clip, 11); diff --git a/tests/check/ges/test-utils.h b/tests/check/ges/test-utils.h index 282f389742..85ee03a9e3 100644 --- a/tests/check/ges/test-utils.h +++ b/tests/check/ges/test-utils.h @@ -404,4 +404,13 @@ G_STMT_START { \ free_children_properties (props2, num2); \ } +#define assert_GESError(error, error_code) \ +{ \ + fail_unless (error); \ + fail_unless (error->domain == GES_ERROR); \ + assert_equals_int (error->code, error_code); \ + g_error_free (error); \ + error = NULL; \ +} + void print_timeline(GESTimeline *timeline); diff --git a/tests/check/python/common.py b/tests/check/python/common.py index 2ff3dc540e..18609ccbf4 100644 --- a/tests/check/python/common.py +++ b/tests/check/python/common.py @@ -149,6 +149,21 @@ class GESTest(unittest.TestCase): for effect in effects] self.assertEqual(indexes, list(range(len(effects)))) + def assertGESError(self, error, code, message=""): + if error is None: + raise AssertionError( + "{}{}Received no error".format(message, message and ": ")) + if error.domain != "GES_ERROR": + raise AssertionError( + "{}{}Received error ({}) in domain {} rather than " + "GES_ERROR".format( + message, message and ": ", error.message, error.domain)) + err_code = GES.Error(error.code) + if err_code != code: + raise AssertionError( + "{}{}Received {} error ({}) rather than {}".format( + message, message and ": ", err_code.value_name, + error.message, code.value_name)) class GESSimpleTimelineTest(GESTest): @@ -680,7 +695,7 @@ class GESTimelineConfigTest(GESTest): def assertEdit(self, element, layer, mode, edge, position, snap, snap_froms, snap_tos, new_props, new_transitions, lost_transitions): - if not element.edit([], layer, mode, edge, position): + if not element.edit_full(layer, mode, edge, position): raise AssertionError( "Edit of {} to layer {}, mode {}, edge {}, at position {} " "failed when a success was expected".format( @@ -690,11 +705,31 @@ class GESTimelineConfigTest(GESTest): snap_tos=snap_tos, new_transitions=new_transitions, lost_transitions=lost_transitions) - def assertFailEdit(self, element, layer, mode, edge, position): - if element.edit([], layer, mode, edge, position): - raise AssertionError( - "Edit of {} to layer {}, mode {}, edge {}, at position {} " - "succeeded when a failure was expected".format( - element, layer, mode, edge, position)) + def assertFailEdit(self, element, layer, mode, edge, position, err_code): + res = None + error = None + try: + res = element.edit_full(layer, mode, edge, position) + except GLib.Error as exception: + error = exception + + if err_code is None: + if res is not False: + raise AssertionError( + "Edit of {} to layer {}, mode {}, edge {}, at " + "position {} succeeded when a failure was expected" + "".format( + element, layer, mode, edge, position)) + if error is not None: + raise AssertionError( + "Edit of {} to layer {}, mode {}, edge {}, at " + "position {} did produced an error when none was " + "expected".format( + element, layer, mode, edge, position)) + else: + self.assertGESError( + error, err_code, + "Edit of {} to layer {}, mode {}, edge {}, at " + "position {}".format(element, layer, mode, edge, position)) # should be no change or snapping if edit fails self.assertTimelineConfig() diff --git a/tests/check/python/test_timeline.py b/tests/check/python/test_timeline.py index 04d2aef53b..d438afacb6 100644 --- a/tests/check/python/test_timeline.py +++ b/tests/check/python/test_timeline.py @@ -27,6 +27,7 @@ gi.require_version("GES", "1.0") from gi.repository import Gst # noqa from gi.repository import GES # noqa +from gi.repository import GLib # noqa import unittest # noqa from unittest import mock @@ -561,8 +562,12 @@ class TestEditing(common.GESSimpleTimelineTest): self.assertTrue(clip.set_duration(10)) # cannot trim to a 0 because effect0 would have a negative in-point - self.assertFalse( - clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)) + error = None + try: + clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.NEGATIVE_TIME) self.assertEqual(clip.start, 10) self.assertEqual(clip.inpoint, 12) @@ -945,11 +950,20 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest): clip2 = self.add_clip(start=10, in_point=0, duration=4) clip3 = self.add_clip(start=12, in_point=0, duration=3) - self.assertIsNone(clip1.split(13)) - self.assertIsNone(clip1.split(8)) + self.assertIsNone(clip1.split_full(13)) + self.assertIsNone(clip1.split_full(8)) + self.assertIsNone(clip3.split_full(12)) + self.assertIsNone(clip3.split_full(15)) - self.assertIsNone(clip3.split(12)) - self.assertIsNone(clip3.split(15)) + def _fail_split(self, clip, position): + split = None + error = None + try: + split = clip.split_full(position) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) + self.assertIsNone(split) def test_split_with_transition(self): self.track_types = [GES.TrackType.AUDIO] @@ -958,22 +972,41 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest): clip0 = self.add_clip(start=0, in_point=0, duration=50) clip1 = self.add_clip(start=20, in_point=0, duration=50) + clip2 = self.add_clip(start=60, in_point=0, duration=20) self.assertTimelineTopology([ [ (GES.TestClip, 0, 50), (GES.TransitionClip, 20, 30), - (GES.TestClip, 20, 50) + (GES.TestClip, 20, 50), + (GES.TransitionClip, 60, 10), + (GES.TestClip, 60, 20), ] ]) - # Split should file as the first part of the split + # Split should fail as the first part of the split # would be fully overlapping clip0 - self.assertIsNone(clip1.split(40)) + self._fail_split(clip1, 40) + self.assertTimelineTopology([ [ (GES.TestClip, 0, 50), (GES.TransitionClip, 20, 30), - (GES.TestClip, 20, 50) + (GES.TestClip, 20, 50), + (GES.TransitionClip, 60, 10), + (GES.TestClip, 60, 20), + ] + ]) + + # same with end of the clip + self._fail_split(clip1, 65) + + self.assertTimelineTopology([ + [ + (GES.TestClip, 0, 50), + (GES.TransitionClip, 20, 30), + (GES.TestClip, 20, 50), + (GES.TransitionClip, 60, 10), + (GES.TestClip, 60, 20), ] ]) @@ -1277,79 +1310,82 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest): class TestConfigurationRules(common.GESSimpleTimelineTest): - def _try_add_clip(self, start, duration, layer=None): + def _try_add_clip(self, start, duration, layer=None, error=None): if layer is None: layer = self.layer asset = GES.Asset.request(GES.TestClip, None) + found_err = None + clip = None # large inpoint to allow trims - return layer.add_asset (asset, start, 1000, duration, - GES.TrackType.UNKNOWN) + try: + clip = layer.add_asset_full( + asset, start, 1000, duration, GES.TrackType.UNKNOWN) + except GLib.Error as err: + found_err = err + if error is None: + self.assertIsNotNone(clip) + else: + self.assertIsNone(clip) + self.assertGESError(found_err, error) + return clip def test_full_overlap_add(self): clip1 = self._try_add_clip(50, 50) - self.assertIsNotNone(clip1) - self.assertIsNone(self._try_add_clip(50, 50)) - self.assertIsNone(self._try_add_clip(49, 51)) - self.assertIsNone(self._try_add_clip(51, 49)) + self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK) def test_triple_overlap_add(self): clip1 = self._try_add_clip(0, 50) - self.assertIsNotNone(clip1) clip2 = self._try_add_clip(40, 50) - self.assertIsNotNone(clip2) - self.assertIsNone(self._try_add_clip(40, 10)) - self.assertIsNone(self._try_add_clip(30, 30)) - self.assertIsNone(self._try_add_clip(1, 88)) + self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK) + self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK) def test_full_overlap_move(self): clip1 = self._try_add_clip(0, 50) - self.assertIsNotNone(clip1) clip2 = self._try_add_clip(50, 50) - self.assertIsNotNone(clip2) self.assertFalse(clip2.set_start(0)) def test_triple_overlap_move(self): clip1 = self._try_add_clip(0, 50) - self.assertIsNotNone(clip1) clip2 = self._try_add_clip(40, 50) - self.assertIsNotNone(clip2) clip3 = self._try_add_clip(100, 60) - self.assertIsNotNone(clip3) self.assertFalse(clip3.set_start(30)) def test_full_overlap_move_into_layer(self): clip1 = self._try_add_clip(0, 50) - self.assertIsNotNone(clip1) layer2 = self.timeline.append_layer() clip2 = self._try_add_clip(0, 50, layer2) - self.assertIsNotNone(clip2) - self.assertFalse(clip2.move_to_layer(self.layer)) + res = None + try: + res = clip2.move_to_layer_full(self.layer) + except GLib.Error as error: + self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) + self.assertIsNone(res) def test_triple_overlap_move_into_layer(self): clip1 = self._try_add_clip(0, 50) - self.assertIsNotNone(clip1) clip2 = self._try_add_clip(40, 50) - self.assertIsNotNone(clip2) layer2 = self.timeline.append_layer() clip3 = self._try_add_clip(30, 30, layer2) - self.assertIsNotNone(clip3) - self.assertFalse(clip3.move_to_layer(self.layer)) + res = None + try: + res = clip3.move_to_layer_full(self.layer) + except GLib.Error as error: + self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK) + self.assertIsNone(res) def test_full_overlap_trim(self): clip1 = self._try_add_clip(0, 50) - self.assertIsNotNone(clip1) clip2 = self._try_add_clip(50, 50) - self.assertIsNotNone(clip2) self.assertFalse(clip2.trim(0)) self.assertFalse(clip1.set_duration(100)) def test_triple_overlap_trim(self): clip1 = self._try_add_clip(0, 20) - self.assertIsNotNone(clip1) clip2 = self._try_add_clip(10, 30) - self.assertIsNotNone(clip2) clip3 = self._try_add_clip(30, 20) - self.assertIsNotNone(clip3) self.assertFalse(clip3.trim(19)) self.assertFalse(clip1.set_duration(31)) @@ -1506,26 +1542,34 @@ class TestComplexEditing(common.GESTimelineConfigTest): # cannot move c0 up one layer because it would cause a triple # overlap between c1, c2 and c3 when g0 moves self.assertFailEdit( - c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23) + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move c0, without moving g1, to 21 layer 1 because it # would be completely overlapped by c2 self.assertFailEdit( - c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20) + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move c1, without moving g1, with end 25 because it # would be completely overlapped by c2 self.assertFailEdit( - c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25) + c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move g0 to layer 0 because it would make c0 go to a # negative layer self.assertFailEdit( - g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10) + g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, + GES.Error.NEGATIVE_LAYER) # cannot move c1 for same reason - self.assertFalse( - c1.move_to_layer(self.timeline.get_layer(0))) + error = None + try: + c1.move_to_layer_full(self.timeline.get_layer(0)) + except GLib.Error as err: + error = err + self.assertGESError(error, GES.Error.NEGATIVE_LAYER) self.assertTimelineConfig({}, []) # failure with snapping @@ -1534,17 +1578,20 @@ class TestComplexEditing(common.GESTimelineConfigTest): # cannot move to 0 because end edge of c0 would snap with end of # c3, making the new start become negative self.assertFailEdit( - g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0) + g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0, + GES.Error.NEGATIVE_TIME) # cannot move start of c1 to 14 because snapping causes a full # overlap with c0 self.assertFailEdit( - c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14) + c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot move end of c2 to 21 because snapping causes a full # overlap with c0 self.assertFailEdit( - c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21) + c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, + GES.Error.INVALID_OVERLAP_IN_TRACK) # successes self.timeline.set_snapping_distance(3) @@ -1701,19 +1748,22 @@ class TestComplexEditing(common.GESTimelineConfigTest): # would cause negative layer priority for c0 self.assertFailEdit( - c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5) + c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5, + GES.Error.NEGATIVE_LAYER) # would lead to c2 fully overlapping c3 since c2 does ripple # but c3 does not(c3 shares a toplevel with c0, and # GES_EDGE_START, same as NORMAL mode, does not move the # toplevel self.assertFailEdit( - c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25) + c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25, + GES.Error.INVALID_OVERLAP_IN_TRACK) # would lead to c2 fully overlapping c3 since c2 does not # ripple but c3 does self.assertFailEdit( - c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13) + c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13, + GES.Error.INVALID_OVERLAP_IN_TRACK) # add two more clips @@ -2160,34 +2210,41 @@ class TestComplexEditing(common.GESTimelineConfigTest): # cannot trim end of g0 to 16 because a0 and a1 would fully # overlap self.assertFailEdit( - g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15) + g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot edit to new layer because there would be triple overlaps # between v2, v3, v4 and v5 self.assertFailEdit( - g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20) + g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot trim g1 end to 14 because it would result in a negative # duration for a2 and a4 self.assertFailEdit( - g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14) + g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14, + GES.Error.NEGATIVE_TIME) # cannot trim end of v2 below its start self.assertFailEdit( - v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2) + v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2, + GES.Error.NEGATIVE_TIME) # cannot trim end of g0 because a0's duration-limit would be # exceeded self.assertFailEdit( - g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23) + g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, + GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) # cannot trim g0 to 12 because a0 and a1 would fully overlap self.assertFailEdit( - g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12) + g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot trim start of v2 beyond its end point self.assertFailEdit( - v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20) + v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20, + GES.Error.NEGATIVE_TIME) # with snapping self.timeline.set_snapping_distance(4) @@ -2195,12 +2252,14 @@ class TestComplexEditing(common.GESTimelineConfigTest): # cannot trim end of g2 to 19 because v1 and v2 would fully # overlap after snapping to v5 start edge(18) self.assertFailEdit( - g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19) + g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot trim g2 to 3 because it would snap to start edge of # v4(0), causing v2's in-point to be negative self.assertFailEdit( - g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3) + g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3, + GES.Error.NEGATIVE_TIME) # success @@ -2737,46 +2796,66 @@ class TestComplexEditing(common.GESTimelineConfigTest): # cannot roll c10 to 22, which snaps to 23, because it will # extend c5 beyond its duration limit of 8 self.assertFailEdit( - c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22) + c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, + GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) # same with g2 self.assertFailEdit( - g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22) + g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, + GES.Error.NOT_ENOUGH_INTERNAL_CONTENT) # cannot roll end c9 to 8, which snaps to 7, because it would # cause c3's in-point to become negative self.assertFailEdit( - c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8) + c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, + GES.Error.NEGATIVE_TIME) # same with g1 self.assertFailEdit( - g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8) + g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, + GES.Error.NEGATIVE_TIME) # cannot roll c13 to 19, snap to 20, because it would cause # c4 to fully overlap c5 self.assertFailEdit( - c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19) + c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19, + GES.Error.INVALID_OVERLAP_IN_TRACK) # cannot roll c12 to 11, snap to 10, because it would cause # c3 to fully overlap c4 self.assertFailEdit( - c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11) + c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11, + GES.Error.INVALID_OVERLAP_IN_TRACK) + # give c6 a bit more allowed duration so we can focus on c9 + self.assertTrue(c6.set_inpoint(10)) + self.assertTimelineConfig({ c6 : {"in-point": 10}}) # cannot roll c6 to 0 because it would cause c9 to be trimmed # below its start self.assertFailEdit( - c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0) + c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0, + GES.Error.NEGATIVE_TIME) + # set back + self.assertTrue(c6.set_inpoint(7)) + self.assertTimelineConfig({ c6 : {"in-point": 7}}) + # give c7 a bit more allowed duration so we can focus on c10 + self.assertTrue(c7.set_inpoint(0)) + self.assertTimelineConfig({ c7 : {"in-point": 0}}) # cannot roll end c7 to 30 because it would cause c10 to be # trimmed beyond its end self.assertFailEdit( - c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30) + c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30, + GES.Error.NEGATIVE_TIME) + # set back + self.assertTrue(c7.set_inpoint(1)) + self.assertTimelineConfig({ c7 : {"in-point": 1}}) # moving layer is not supported self.assertFailEdit( - c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7) + c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, None) self.assertFailEdit( - c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23) + c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, None) # successes self.timeline.set_snapping_distance(0)