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: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
This commit is contained in:
Henry Wilkes 2020-05-15 14:25:01 +01:00
parent a507f7017e
commit 142456d8ba
19 changed files with 1151 additions and 479 deletions

View file

@ -212,10 +212,6 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip,
(GST_CLOCK_TIME_IS_VALID (a) ? \ (GST_CLOCK_TIME_IS_VALID (a) ? \
(GST_CLOCK_TIME_IS_VALID (b) ? MIN (a, b) : a) : b) \ (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 * * duration-limit *
****************************************************/ ****************************************************/
@ -366,7 +362,7 @@ _update_duration_limit (GESClip * self)
GST_INFO_OBJECT (self, "duration-limit for the clip is %" GST_INFO_OBJECT (self, "duration-limit for the clip is %"
GST_TIME_FORMAT, GST_TIME_ARGS (duration_limit)); 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)) { && !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) {
gboolean res; gboolean res;
@ -379,7 +375,7 @@ _update_duration_limit (GESClip * self)
if (element->timeline) if (element->timeline)
res = timeline_tree_trim (timeline_get_tree (element->timeline), res = timeline_tree_trim (timeline_get_tree (element->timeline),
element, 0, GST_CLOCK_DIFF (duration_limit, element->duration), element, 0, GST_CLOCK_DIFF (duration_limit, element->duration),
GES_EDGE_END, 0); GES_EDGE_END, 0, NULL);
else else
res = ges_timeline_element_set_duration (element, duration_limit); res = ges_timeline_element_set_duration (element, duration_limit);
@ -396,18 +392,18 @@ _update_duration_limit (GESClip * self)
/* transfer full of child_data */ /* transfer full of child_data */
static gboolean 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); GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
GstClockTime duration = _calculate_duration_limit (self, child_data); GstClockTime duration = _calculate_duration_limit (self, child_data);
GESTimelineElement *element = GES_TIMELINE_ELEMENT (self); 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 */ /* NOTE: timeline would normally not be NULL at this point */
if (timeline if (timeline
&& !timeline_tree_can_move_element (timeline_get_tree (timeline), && !timeline_tree_can_move_element (timeline_get_tree (timeline),
element, ges_timeline_element_get_layer_priority (element), element, ges_timeline_element_get_layer_priority (element),
element->start, duration)) { element->start, duration, error)) {
return FALSE; return FALSE;
} }
} }
@ -446,7 +442,7 @@ _get_priority_range (GESContainer * container, guint32 * min_priority,
gboolean gboolean
ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child,
guint32 priority) guint32 priority, GError ** error)
{ {
GList *child_data; GList *child_data;
DurationLimitData *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); 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 " GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from "
"priority %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " because " "priority %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " because "
"the duration-limit cannot be adjusted", GES_ARGS (child), "the duration-limit cannot be adjusted", GES_ARGS (child),
@ -489,7 +485,8 @@ _child_priority_changed (GESContainer * container, GESTimelineElement * child)
****************************************************/ ****************************************************/
static gboolean 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 *tmp;
GList *child_data = NULL; 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)); _duration_limit_data_new (GES_TRACK_ELEMENT (child));
if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) { if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) {
if (GST_CLOCK_TIME_IS_VALID (child->maxduration) if (GES_CLOCK_TIME_IS_LESS (child->maxduration, inpoint)) {
&& child->maxduration < inpoint) {
GST_INFO_OBJECT (clip, "Cannot set the in-point from %" 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 "cause the in-point of its core child %" GES_FORMAT
" to exceed its max-duration", _INPOINT (clip), inpoint, " to exceed its max-duration", GST_TIME_ARGS (_INPOINT (clip)),
GES_ARGS (child)); 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); _duration_limit_data_free (data);
g_list_free_full (child_data, _duration_limit_data_free); 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); child_data = g_list_prepend (child_data, 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 from %" G_GUINT64_FORMAT GST_INFO_OBJECT (clip, "Cannot set the in-point from %" GST_TIME_FORMAT
" to %" G_GUINT64_FORMAT " because the duration-limit cannot be " " to %" GST_TIME_FORMAT " because the duration-limit cannot be "
"adjusted", _INPOINT (clip), inpoint); "adjusted", GST_TIME_ARGS (_INPOINT (clip)), GST_TIME_ARGS (inpoint));
return FALSE; return FALSE;
} }
@ -538,7 +540,7 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint)
* its children have a max-duration below it */ * its children have a max-duration below it */
gboolean gboolean
ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, 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 */ /* don't bother checking if we are setting the value */
if (clip->priv->setting_inpoint) 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); 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 %" 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), " because the duration-limit cannot be adjusted", GES_ARGS (child),
_INPOINT (child), inpoint); GST_TIME_ARGS (_INPOINT (child)), GST_TIME_ARGS (inpoint));
return FALSE; 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 /* setting the in-point of a core child will shift the in-point of all
* core children with an internal source */ * 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 */ /* returns TRUE if duration-limit needs to be updated */
@ -622,7 +624,7 @@ _update_max_duration (GESContainer * container)
gboolean gboolean
ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child,
GstClockTime max_duration) GstClockTime max_duration, GError ** error)
{ {
GList *child_data; GList *child_data;
DurationLimitData *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); 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 %" 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), " 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; return FALSE;
} }
@ -685,7 +687,7 @@ _child_has_internal_source_changed (GESClip * self, GESTimelineElement * child)
gboolean gboolean
ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, 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); GESTrack *track = ges_track_element_get_track (child);
gboolean is_core = _IS_CORE_CHILD (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 GST_INFO_OBJECT (clip, "Cannot set the active of child %" GES_FORMAT
" from %i to %i because the duration-limit cannot be adjusted", " from %i to %i because the duration-limit cannot be adjusted",
GES_ARGS (child), ges_track_element_is_active (child), active); GES_ARGS (child), ges_track_element_is_active (child), active);
@ -803,7 +805,7 @@ _track_contains_non_core (GESClip * clip, GESTrack * track)
gboolean gboolean
ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
GESTrack * track) GESTrack * track, GError ** error)
{ {
GList *child_data; GList *child_data;
DurationLimitData *data; DurationLimitData *data;
@ -816,12 +818,14 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
if (current_track == track) if (current_track == track)
return TRUE; return TRUE;
/* NOTE: we consider the following error cases programming errors by
* the user */
if (current_track) { if (current_track) {
/* can not remove a core element from a track if a non-core one sits /* can not remove a core element from a track if a non-core one sits
* above it */ * above it */
if (_IS_CORE_CHILD (child) if (_IS_CORE_CHILD (child)
&& _track_contains_non_core (clip, current_track)) { && _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 " " to the track %" GST_PTR_FORMAT " because it has non-core "
"siblings above it in its current track %" GST_PTR_FORMAT, "siblings above it in its current track %" GST_PTR_FORMAT,
GES_ARGS (child), track, current_track); 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); GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
const GESTimeline *track_timeline = ges_track_get_timeline (track); const GESTimeline *track_timeline = ges_track_get_timeline (track);
if (track_timeline == NULL) { 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 " " to the track %" GST_PTR_FORMAT " because it is not part "
"of a timeline", GES_ARGS (child), track); "of a timeline", GES_ARGS (child), track);
return FALSE; return FALSE;
} }
if (track_timeline != clip_timeline) { 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 %" " to the track %" GST_PTR_FORMAT " because its timeline %"
GST_PTR_FORMAT " does not match the clip's timeline %" GST_PTR_FORMAT " does not match the clip's timeline %"
GST_PTR_FORMAT, GES_ARGS (child), track, track_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 */ * placed in a track that already has a core child */
if (_IS_CORE_CHILD (child)) { if (_IS_CORE_CHILD (child)) {
if (core) { 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 " " to the track %" GST_PTR_FORMAT " because it contains a "
"core sibling %" GES_FORMAT, GES_ARGS (child), track, "core sibling %" GES_FORMAT, GES_ARGS (child), track,
GES_ARGS (core)); GES_ARGS (core));
@ -860,7 +864,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
} }
} else { } else {
if (!core) { 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 " GES_FORMAT " to the track %" GST_PTR_FORMAT " because it "
" does not contain a core sibling", GES_ARGS (child), track); " does not contain a core sibling", GES_ARGS (child), track);
return FALSE; 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); 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 " GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from "
"track %" GST_PTR_FORMAT " to track %" GST_PTR_FORMAT " because " "track %" GST_PTR_FORMAT " to track %" GST_PTR_FORMAT " because "
"the duration-limit cannot be adjusted", GES_ARGS (child), "the duration-limit cannot be adjusted", GES_ARGS (child),
@ -1034,7 +1038,7 @@ _set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint,
static gboolean static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint) _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_WARNING_OBJECT (element, "Cannot set the in-point to %"
GST_TIME_FORMAT, GST_TIME_ARGS (inpoint)); GST_TIME_FORMAT, GST_TIME_ARGS (inpoint));
return FALSE; return FALSE;
@ -1097,10 +1101,11 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
child_data = g_list_prepend (child_data, data); 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 %" GST_WARNING_OBJECT (self, "Cannot set the max-duration from %"
G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT " because the " GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the "
"duration-limit cannot be adjusted", element->maxduration, maxduration); "duration-limit cannot be adjusted",
GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (maxduration));
return FALSE; return FALSE;
} }
@ -1357,7 +1362,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
child_data = _duration_limit_data_list_with_data (self, data); 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 " GST_WARNING_OBJECT (self, "Cannot add core %" GES_FORMAT " as a "
"child because the duration-limit cannot be adjusted", "child because the duration-limit cannot be adjusted",
GES_ARGS (element)); GES_ARGS (element));
@ -1418,7 +1423,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
data->priority++; 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 " GST_WARNING_OBJECT (self, "Cannot add effect %" GES_FORMAT " as "
"a child because the duration-limit cannot be adjusted", "a child because the duration-limit cannot be adjusted",
GES_ARGS (element)); GES_ARGS (element));
@ -1502,7 +1507,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
g_list_prepend (child_data, _duration_limit_data_new (child)); 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 GST_WARNING_OBJECT (self, "Cannot remove the child %" GES_FORMAT
" because the duration-limit cannot be adjusted", GES_ARGS (el)); " because the duration-limit cannot be adjusted", GES_ARGS (el));
return FALSE; 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 * @clip: A #GESClip
* @layer: The new layer * @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 * 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 * 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. * Returns: %TRUE if @clip was successfully moved to @layer.
*/ */
gboolean 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; gboolean ret = FALSE;
GESLayer *current_layer; 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_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
element = GES_TIMELINE_ELEMENT (clip); element = GES_TIMELINE_ELEMENT (clip);
current_layer = clip->priv->layer; 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 */ /* move to new layer, also checks moving of toplevel */
return timeline_tree_move (timeline_get_tree (layer->timeline), return timeline_tree_move (timeline_get_tree (layer->timeline),
element, (gint64) ges_layer_get_priority (current_layer) - 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); gst_object_ref (clip);
@ -2418,6 +2425,21 @@ done:
return ret && (clip->priv->layer == layer); 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: * ges_clip_find_track_element:
* @clip: A #GESClip * @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 * @clip: A #GESClip
* @effect: An effect within @clip to move * @effect: An effect within @clip to move
* @newindex: The index for @effect in @clip * @newindex: The index for @effect in @clip
* @error: (nullable): Return location for an error
* *
* Set the index of an effect within the clip. See * Set the index of an effect within the clip. See
* ges_clip_get_top_effect_index(). The new index must be an existing * 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. * Returns: %TRUE if @effect was successfully moved to @newindex.
*/ */
gboolean gboolean
ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect, ges_clip_set_top_effect_index_full (GESClip * clip, GESBaseEffect * effect,
guint newindex) guint newindex, GError ** error)
{ {
gint inc; gint inc;
GList *top_effects, *tmp; 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_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), 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)) if (!_is_added_effect (clip, effect))
return FALSE; return FALSE;
@ -2670,8 +2694,8 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
child_data = g_list_prepend (child_data, data); child_data = g_list_prepend (child_data, data);
} }
if (!_can_update_duration_limit (clip, child_data)) { if (!_can_update_duration_limit (clip, child_data, error)) {
GST_WARNING_OBJECT (clip, "Cannot move top effect %" GES_FORMAT GST_INFO_OBJECT (clip, "Cannot move top effect %" GES_FORMAT
" to index %i because the duration-limit cannot adjust", " to index %i because the duration-limit cannot adjust",
GES_ARGS (effect), newindex); GES_ARGS (effect), newindex);
return FALSE; 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 * @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 * Splits a clip at the given timeline position into two clips. The clip
* must already have a #GESClip:layer. * 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. * from the splitting @clip, or %NULL if @clip can't be split.
*/ */
GESClip * GESClip *
ges_clip_split (GESClip * clip, guint64 position) ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
{ {
GList *tmp, *transitions = NULL; GList *tmp, *transitions = NULL;
GESClip *new_object; GESClip *new_object;
@ -2752,10 +2795,12 @@ ges_clip_split (GESClip * clip, guint64 position)
GESTimelineElement *element; GESTimelineElement *element;
GESTimeline *timeline; GESTimeline *timeline;
GHashTable *track_for_copy; GHashTable *track_for_copy;
guint32 layer_prio;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL); g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (clip->priv->layer, 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 (GST_CLOCK_TIME_IS_VALID (position), NULL);
g_return_val_if_fail (!error || !*error, NULL);
element = GES_TIMELINE_ELEMENT (clip); element = GES_TIMELINE_ELEMENT (clip);
timeline = element->timeline; timeline = element->timeline;
@ -2770,26 +2815,26 @@ ges_clip_split (GESClip * clip, guint64 position)
return NULL; return NULL;
} }
layer_prio = ges_timeline_element_get_layer_priority (element);
old_duration = position - start; old_duration = position - start;
if (timeline && !timeline_tree_can_move_element (timeline_get_tree if (timeline
(timeline), element, && !timeline_tree_can_move_element (timeline_get_tree (timeline), element,
ges_timeline_element_get_layer_priority (element), layer_prio, start, old_duration, error)) {
start, old_duration)) { GST_INFO_OBJECT (clip,
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT "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)); GST_TIME_ARGS (position));
return NULL; return NULL;
} }
new_duration = duration + start - position; new_duration = duration + start - position;
if (timeline && !timeline_tree_can_move_element (timeline_get_tree if (timeline
(timeline), element, && !timeline_tree_can_move_element (timeline_get_tree (timeline), element,
ges_timeline_element_get_layer_priority (element), layer_prio, position, new_duration, error)) {
position, new_duration)) { GST_INFO_OBJECT (clip,
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT "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)); GST_TIME_ARGS (position));
return NULL; return NULL;
} }
@ -2879,6 +2924,22 @@ ges_clip_split (GESClip * clip, guint64 position)
return new_object; 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: * ges_clip_set_supported_formats:
* @clip: A #GESClip * @clip: A #GESClip
@ -3073,7 +3134,7 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
* @clip: A #GESClip * @clip: A #GESClip
* @child: A child of @clip * @child: A child of @clip
* @track: The track to add @child to * @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. * 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 * such as causing three sources to overlap at a single time, or causing
* a source to completely overlap another in the same track. * 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 * 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. * @child or a copy of child, or %NULL if the element could not be added.
*/ */
GESTrackElement * GESTrackElement *
ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
GESTrack * track, GError ** err) GESTrack * track, GError ** error)
{ {
GESTimeline *timeline; GESTimeline *timeline;
GESTrackElement *el; 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_CLIP (clip), NULL);
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), 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 (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); timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
@ -3164,11 +3223,8 @@ ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
el = child; el = child;
} }
/* FIXME: set error if can not be added to track: if (!ges_track_add_element_full (track, el, error)) {
* Either breaks the track rules for the clip, or the timeline GST_INFO_OBJECT (clip, "Could not add the track element %"
* configuration rules */
if (!ges_track_add_element (track, el)) {
GST_WARNING_OBJECT (clip, "Could not add the track element %"
GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track); GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track);
if (el != child) if (el != child)
ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el)); ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el));

View file

@ -145,48 +145,75 @@ struct _GESClipClass
GES_API GES_API
GESTrackType ges_clip_get_supported_formats (GESClip *clip); GESTrackType ges_clip_get_supported_formats (GESClip *clip);
GES_API GES_API
void ges_clip_set_supported_formats (GESClip *clip, GESTrackType supportedformats); void ges_clip_set_supported_formats (GESClip *clip,
GESTrackType supportedformats);
GES_API GES_API
GESTrackElement* ges_clip_add_asset (GESClip *clip, GESAsset *asset); GESTrackElement* ges_clip_add_asset (GESClip *clip,
GESAsset *asset);
GES_API GES_API
GESTrackElement* ges_clip_find_track_element (GESClip *clip, GESTrack *track, GESTrackElement* ges_clip_find_track_element (GESClip *clip,
GESTrack *track,
GType type); GType type);
GES_API GES_API
GList * ges_clip_find_track_elements (GESClip * clip, GESTrack * track, GList * ges_clip_find_track_elements (GESClip * clip,
GESTrackType track_type, GType type); GESTrack * track,
GESTrackType track_type,
GType type);
GES_API 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 * * Layer *
****************************************************/ ****************************************************/
GES_API GES_API
GESLayer* ges_clip_get_layer (GESClip *clip); GESLayer* ges_clip_get_layer (GESClip * clip);
GES_API 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 * * Effects *
****************************************************/ ****************************************************/
GES_API GES_API
GList* ges_clip_get_top_effects (GESClip *clip); GList* ges_clip_get_top_effects (GESClip * clip);
GES_API 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 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 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); guint newpriority);
GES_API 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); guint newindex);
GES_API
gboolean ges_clip_set_top_effect_index_full (GESClip * clip,
GESBaseEffect * effect,
guint newindex,
GError ** error);
/**************************************************** /****************************************************
* Editing * * Editing *
****************************************************/ ****************************************************/
GES_API 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 GES_API
GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip, GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip,

View file

@ -38,6 +38,17 @@ G_BEGIN_DECLS
* @GES_ERROR_ASSET_WRONG_ID: The ID passed is malformed * @GES_ERROR_ASSET_WRONG_ID: The ID passed is malformed
* @GES_ERROR_ASSET_LOADING: An error happened while loading the asset * @GES_ERROR_ASSET_LOADING: An error happened while loading the asset
* @GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE: The formatted files was malformed * @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 typedef enum
{ {
@ -45,6 +56,10 @@ typedef enum
GES_ERROR_ASSET_LOADING, GES_ERROR_ASSET_LOADING,
GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE,
GES_ERROR_INVALID_FRAME_NUMBER, 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; } GESError;
G_END_DECLS G_END_DECLS

View file

@ -222,7 +222,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
return timeline_tree_move (timeline_get_tree (timeline), return timeline_tree_move (timeline_get_tree (timeline),
element, (gint64) (element->priority) - (gint64) priority, 0, element, (gint64) (element->priority) - (gint64) priority, 0,
GES_EDGE_NONE, 0); GES_EDGE_NONE, 0, NULL);
} }
static gboolean static gboolean

View file

@ -61,6 +61,10 @@ GstDebugCategory * _ges_debug (void);
#define _set_duration0 ges_timeline_element_set_duration #define _set_duration0 ges_timeline_element_set_duration
#define _set_priority0 ges_timeline_element_set_priority #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_N 30
#define DEFAULT_FRAMERATE_D 1 #define DEFAULT_FRAMERATE_D 1
#define DEFAULT_WIDTH 1280 #define DEFAULT_WIDTH 1280
@ -110,8 +114,8 @@ G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline);
G_GNUC_INTERNAL gboolean G_GNUC_INTERNAL gboolean
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position); guint64 position, GError ** error);
G_GNUC_INTERNAL void G_GNUC_INTERNAL void
timeline_add_group (GESTimeline *timeline, timeline_add_group (GESTimeline *timeline,
@ -152,7 +156,7 @@ G_GNUC_INTERNAL void
ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving); ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving);
G_GNUC_INTERNAL gboolean 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 G_GNUC_INTERNAL void
ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip); 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 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 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 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_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); 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); 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); 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); 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); 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 * * GESTrackElement *
****************************************************/ ****************************************************/
#define NLE_OBJECT_TRACK_ELEMENT_QUARK (g_quark_from_string ("nle_object_track_element_quark")) #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, G_GNUC_INTERNAL void ges_track_element_copy_properties (GESTimelineElement * element,
GESTimelineElement * elementcopy); GESTimelineElement * elementcopy);
G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement *element, gboolean active); G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement *element, gboolean active);

View file

@ -237,24 +237,8 @@ ges_layer_class_init (GESLayerClass * klass)
* GESLayer:auto-transition: * GESLayer:auto-transition:
* *
* Whether to automatically create a #GESTransitionClip whenever two * Whether to automatically create a #GESTransitionClip whenever two
* #GESClip-s overlap in the layer. Specifically, if this is set to * #GESSource-s that both belong to a #GESClip in the layer overlap.
* %TRUE, then wherever two #GESSource-s (that belong to some clip in * See #GESTimeline for what counts as an overlap.
* 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.
* *
* When a layer is added to a #GESTimeline, if this property is left as * 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 * %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 * @layer: The #GESLayer
* @clip: (transfer floating): The clip to add * @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 * Adds the given clip to the layer. If the method succeeds, the layer
* will take ownership of the clip. * will take ownership of the clip.
@ -682,17 +667,19 @@ ges_layer_is_empty (GESLayer * layer)
* if @layer refused to add @clip. * if @layer refused to add @clip.
*/ */
gboolean 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; GESAsset *asset;
GESLayerPrivate *priv; GESLayerPrivate *priv;
GESLayer *current_layer; GESLayer *current_layer;
GESTimeline *timeline; GESTimeline *timeline;
GESContainer *container; GESContainer *container;
GError *timeline_error = NULL;
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE); 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 (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip); timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
container = GES_CONTAINER (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); 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 */ /* 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) { for (tmp = new_children; tmp; tmp = tmp->next) {
if (!g_list_find (prev_children, tmp->data)) if (!g_list_find (prev_children, tmp->data))
ges_container_remove (container, 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 (prev_children, gst_object_unref);
g_list_free_full (new_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 /* FIXME: change emit signal to FALSE once we are able to delay the
* "clip-added" signal until after ges_timeline_add_clip */ * "clip-added" signal until after ges_timeline_add_clip */
ges_layer_remove_clip_internal (layer, clip, TRUE); 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 * @layer: The #GESLayer
* @asset: The asset to extract the new clip from * @asset: The asset to extract the new clip from
* @start: The #GESTimelineElement:start value to set on the new clip * @start: The #GESTimelineElement:start value to set on the new clip
@ -830,6 +845,7 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
* clip * clip
* @track_types: The #GESClip:supported-formats to set on the the new * @track_types: The #GESClip:supported-formats to set on the the new
* clip, or #GES_TRACK_TYPE_UNKNOWN to use the default * 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 * Extracts a new clip from an asset and adds it to the layer with
* the given properties. * the given properties.
@ -837,14 +853,15 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
* Returns: (transfer none): The newly created clip. * Returns: (transfer none): The newly created clip.
*/ */
GESClip * GESClip *
ges_layer_add_asset (GESLayer * layer, ges_layer_add_asset_full (GESLayer * layer,
GESAsset * asset, GstClockTime start, GstClockTime inpoint, GESAsset * asset, GstClockTime start, GstClockTime inpoint,
GstClockTime duration, GESTrackType track_types) GstClockTime duration, GESTrackType track_types, GError ** error)
{ {
GESClip *clip; GESClip *clip;
g_return_val_if_fail (GES_IS_LAYER (layer), NULL); 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 (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 g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type
(asset), GES_TYPE_CLIP), NULL); (asset), GES_TYPE_CLIP), NULL);
@ -873,13 +890,40 @@ ges_layer_add_asset (GESLayer * layer,
_set_duration0 (GES_TIMELINE_ELEMENT (clip), duration); _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 NULL;
} }
return clip; 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: * ges_layer_new:
* *

View file

@ -65,67 +65,82 @@ struct _GESLayerClass {
/*< private >*/ /*< private >*/
/* Signals */ /* Signals */
void (*object_added) (GESLayer * layer, GESClip * object); void (*object_added) (GESLayer * layer, GESClip * object);
void (*object_removed) (GESLayer * layer, GESClip * object); void (*object_removed) (GESLayer * layer, GESClip * object);
/* Padding for API extension */ /* Padding for API extension */
gpointer _ges_reserved[GES_PADDING]; gpointer _ges_reserved[GES_PADDING];
}; };
GES_API GES_API
GESLayer* ges_layer_new (void); GESLayer* ges_layer_new (void);
GES_API GES_API
void ges_layer_set_timeline (GESLayer * layer, void ges_layer_set_timeline (GESLayer * layer,
GESTimeline * timeline); GESTimeline * timeline);
GES_API
GES_API GESTimeline * GESTimeline * ges_layer_get_timeline (GESLayer * layer);
ges_layer_get_timeline (GESLayer * layer);
GES_API GES_API
gboolean ges_layer_add_clip (GESLayer * layer, gboolean ges_layer_add_clip (GESLayer * layer,
GESClip * clip); GESClip * clip);
GES_API GES_API
GESClip * ges_layer_add_asset (GESLayer *layer, gboolean ges_layer_add_clip_full (GESLayer * layer,
GESAsset *asset, GESClip * clip,
GstClockTime start, GError ** error);
GstClockTime inpoint, GES_API
GstClockTime duration, GESClip * ges_layer_add_asset (GESLayer *layer,
GESTrackType track_types); 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 GES_API
gboolean ges_layer_remove_clip (GESLayer * layer, gboolean ges_layer_remove_clip (GESLayer * layer,
GESClip * clip); GESClip * clip);
GES_DEPRECATED_FOR(ges_timeline_move_layer) GES_DEPRECATED_FOR(ges_timeline_move_layer)
void ges_layer_set_priority (GESLayer * layer, void ges_layer_set_priority (GESLayer * layer,
guint priority); guint priority);
GES_API GES_API
gboolean ges_layer_is_empty (GESLayer * layer); gboolean ges_layer_is_empty (GESLayer * layer);
GES_API 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 GES_API
guint ges_layer_get_priority (GESLayer * layer); guint ges_layer_get_priority (GESLayer * layer);
GES_API GES_API
gboolean ges_layer_get_auto_transition (GESLayer * layer); gboolean ges_layer_get_auto_transition (GESLayer * layer);
GES_API GES_API
void ges_layer_set_auto_transition (GESLayer * layer, void ges_layer_set_auto_transition (GESLayer * layer,
gboolean auto_transition); gboolean auto_transition);
GES_API GES_API
GList* ges_layer_get_clips (GESLayer * layer); GList* ges_layer_get_clips (GESLayer * layer);
GES_API GES_API
GstClockTime ges_layer_get_duration (GESLayer *layer); GstClockTime ges_layer_get_duration (GESLayer *layer);
GES_API GES_API
gboolean ges_layer_set_active_for_tracks (GESLayer *layer, gboolean active, gboolean ges_layer_set_active_for_tracks (GESLayer *layer,
GList *tracks); gboolean active,
GList *tracks);
GES_API gboolean ges_layer_get_active_for_track (GESLayer *layer, GES_API
GESTrack *track); gboolean ges_layer_get_active_for_track (GESLayer *layer,
GESTrack *track);
G_END_DECLS G_END_DECLS

View file

@ -48,12 +48,9 @@
* similar changes in neighbouring or later elements in the timeline. * similar changes in neighbouring or later elements in the timeline.
* *
* However, a timeline may refuse a change in these properties if they * However, a timeline may refuse a change in these properties if they
* would place the timeline in an unsupported configuration. For example, * would place the timeline in an unsupported configuration. See
* it is not possible for three #GESSourceClip-s in the same layer and * #GESTimeline for its overlap rules.
* 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.
* Additionally, an edit may be refused if it would place one of the * 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 * timing properties out of bounds (such as a negative time value for
* #GESTimelineElement:start, or having insufficient internal * #GESTimelineElement:start, or having insufficient internal
@ -1153,8 +1150,7 @@ ges_timeline_element_set_inpoint (GESTimelineElement * self,
if (G_UNLIKELY (inpoint == self->inpoint)) if (G_UNLIKELY (inpoint == self->inpoint))
return TRUE; return TRUE;
if (GST_CLOCK_TIME_IS_VALID (self->maxduration) if (GES_CLOCK_TIME_IS_LESS (self->maxduration, inpoint)) {
&& inpoint > self->maxduration) {
GST_WARNING_OBJECT (self, "Can not set an in-point of %" GST_TIME_FORMAT 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, " because it exceeds the element's max-duration: %" GST_TIME_FORMAT,
GST_TIME_ARGS (inpoint), GST_TIME_ARGS (self->maxduration)); 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)) if (G_UNLIKELY (maxduration == self->maxduration))
return TRUE; 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_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 " because it lies below the element's in-point: %"
GST_TIME_FORMAT, GST_TIME_ARGS (maxduration), 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 * @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 * @new_layer_priority: The priority/index of the layer @self should be
* moved to. -1 means no move * moved to. -1 means no move
* @mode: The edit mode * @mode: The edit mode
* @edge: The edge of @self where the edit should occur * @edge: The edge of @self where the edit should occur
* @position: The edit position: a new location for the edge of @self * @position: The edit position: a new location for the edge of @self
* (in nanoseconds) * (in nanoseconds)
* @error: (nullable): Return location for an error
* *
* Edits the element within its timeline by adjusting its * Edits the element within its timeline by adjusting its
* #GESTimelineElement:start, #GESTimelineElement:duration or * #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 * the corresponding layer priority/index does not yet exist for the
* timeline. * 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. * 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 gboolean
ges_timeline_element_edit (GESTimelineElement * self, GList * layers, ges_timeline_element_edit_full (GESTimelineElement * self,
gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position,
GError ** error)
{ {
GESTimeline *timeline; GESTimeline *timeline;
guint32 layer_prio; guint32 layer_prio;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); 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 (GST_CLOCK_TIME_IS_VALID (position), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
g_return_val_if_fail (timeline, FALSE); 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), self->name, ges_edge_name (edge), GST_TIME_ARGS (position),
ges_edit_mode_name (mode), new_layer_priority); ges_edit_mode_name (mode), new_layer_priority);
return ges_timeline_edit (timeline, self, layers, new_layer_priority, mode, return ges_timeline_edit (timeline, self, new_layer_priority, mode,
edge, position); 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);
} }
/** /**

View file

@ -401,4 +401,12 @@ gboolean ges_timeline_element_edit (GESTimeli
GESEditMode mode, GESEditMode mode,
GESEdge edge, GESEdge edge,
guint64 position); 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 G_END_DECLS

View file

@ -79,6 +79,8 @@ struct _TreeIterationData
{ {
GNode *root; GNode *root;
gboolean res; gboolean res;
/* an error to set */
GError **error;
/* The element we are visiting */ /* The element we are visiting */
GESTimelineElement *element; GESTimelineElement *element;
@ -299,7 +301,7 @@ _clock_time_plus (GstClockTime time, GstClockTime add)
return GST_CLOCK_TIME_NONE; return GST_CLOCK_TIME_NONE;
if (time >= (G_MAXUINT64 - add)) { 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); "adding %" G_GUINT64_FORMAT, time, add);
return GST_CLOCK_TIME_NONE; return GST_CLOCK_TIME_NONE;
} }
@ -355,7 +357,7 @@ _abs_clock_time_distance (GstClockTime time1, GstClockTime time2)
return time2 - time1; return time2 - time1;
} }
static gboolean static void
get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode, get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start, GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start,
GstClockTime * end, gboolean * negative_end) GstClockTime * end, gboolean * negative_end)
@ -388,22 +390,6 @@ get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
*start = new_start; *start = new_start;
if (end) if (end)
*end = new_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! /* 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, * But we can still only snap to an existing edge in the timeline,
* which should be a valid time */ * which should be a valid time */
if (!get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, *offset,
*offset, &start, &negative_start, &end, &negative_end)) &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; 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) { switch (mode) {
case EDIT_MOVE: case EDIT_MOVE:
@ -619,6 +618,41 @@ done:
* Check Overlaps * * 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 \ #define _ELEMENT_FORMAT \
"%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \ "%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \
"(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")" "(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")"
@ -695,10 +729,19 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
return FALSE; return FALSE;
} }
if ((cmp_start <= start && cmp_end >= end) || if (cmp_start <= start && cmp_end >= end) {
(cmp_start >= start && cmp_end <= end)) { /* cmp fully overlaps e */
GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap", GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap",
_CMP_ARGS, _E_ARGS); _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; goto error;
} }
@ -710,6 +753,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
if (data->overlaping_on_start) { if (data->overlaping_on_start) {
GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start", GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start",
_CMP_ARGS, data->overlaping_on_start->name, e->name); _CMP_ARGS, data->overlaping_on_start->name, e->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start,
track);
goto error; goto error;
} }
if (GST_CLOCK_TIME_IS_VALID (data->overlap_end_first_time) && 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 " GST_INFO (_ELEMENT_FORMAT " overlaps %s on its start and %s on its "
"end, but they already overlap each other", _CMP_ARGS, e->name, "end, but they already overlap each other", _CMP_ARGS, e->name,
data->overlaping_on_end->name); data->overlaping_on_end->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end,
track);
goto error; goto error;
} }
/* record the time at which the overlapped ends */ /* 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) { if (data->overlaping_on_end) {
GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end", GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end",
_CMP_ARGS, data->overlaping_on_end->name, e->name); _CMP_ARGS, data->overlaping_on_end->name, e->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end,
track);
goto error; goto error;
} }
if (GST_CLOCK_TIME_IS_VALID (data->overlap_start_final_time) && 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 " GST_INFO (_ELEMENT_FORMAT " overlaps %s on its end and %s on its "
"start, but they already overlap each other", _CMP_ARGS, e->name, "start, but they already overlap each other", _CMP_ARGS, e->name,
data->overlaping_on_start->name); data->overlaping_on_start->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start,
track);
goto error; goto error;
} }
/* record the time at which the overlapped starts */ /* 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 /* whether the elements in moving can be moved to their corresponding
* PositionData */ * PositionData */
static gboolean 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; TreeIterationData data = tree_iteration_data_init;
data.moving = moving; data.moving = moving;
data.root = root; data.root = root;
data.res = TRUE; data.res = TRUE;
data.error = error;
/* sufficient to check the leaves, which is all the track elements or /* sufficient to check the leaves, which is all the track elements or
* empty clips * empty clips
* should also be sufficient to only check the moving elements */ * 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 * * 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 static gboolean
set_layer_priority (GESTimelineElement * element, EditData * data) set_layer_priority (GESTimelineElement * element, EditData * data,
GError ** error)
{ {
gint64 layer_offset = data->layer_offset; gint64 layer_offset = data->layer_offset;
guint32 layer_prio = ges_timeline_element_get_layer_priority (element); 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 (%" GST_INFO_OBJECT (element, "%s would have a negative layer priority (%"
G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name, G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name,
layer_prio, layer_offset); layer_prio, layer_offset);
set_negative_layer_error (error, element,
layer_offset - (gint64) layer_prio);
return FALSE; return FALSE;
} }
if ((layer_prio - (gint64) layer_offset) >= G_MAXUINT32) { 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); element->name);
return FALSE; return FALSE;
} }
@ -851,34 +954,45 @@ set_layer_priority (GESTimelineElement * element, EditData * data)
} }
static gboolean 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 = GstClockTime new_start =
_clock_time_minus_diff (element->start, 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 move %" GES_FORMAT " with offset %" GST_INFO_OBJECT (element, "Cannot move %" GES_FORMAT " with offset %"
G_GINT64_FORMAT " because it would result in an invalid start", G_GINT64_FORMAT " because it would result in an invalid start",
GES_ARGS (element), data->offset); GES_ARGS (element), data->offset);
if (negative)
set_negative_start_error (error, element, new_start);
return FALSE; return FALSE;
} }
_CHECK_END (element, new_start, element->duration); _CHECK_END (element, new_start, element->duration);
data->start = new_start; data->start = new_start;
if (GES_IS_GROUP (element))
return TRUE;
GST_INFO_OBJECT (element, "%s will move by setting start to %" GST_INFO_OBJECT (element, "%s will move by setting start to %"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start)); 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 static gboolean
set_edit_trim_start_inpoint_value (GESTimelineElement * element, 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, GstClockTime new_inpoint = _clock_time_minus_diff (element->inpoint,
data->offset, NULL); data->offset, &negative);
if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) { if (negative || !GST_CLOCK_TIME_IS_VALID (new_inpoint)) {
GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an " " with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid in-point", GES_ARGS (element), data->offset); "invalid in-point", GES_ARGS (element), data->offset);
if (negative)
set_negative_inpoint_error (error, element, new_inpoint);
return FALSE; return FALSE;
} }
data->inpoint = new_inpoint; data->inpoint = new_inpoint;
@ -887,7 +1001,7 @@ set_edit_trim_start_inpoint_value (GESTimelineElement * element,
static gboolean static gboolean
set_edit_trim_start_non_core_children (GESTimelineElement * clip, set_edit_trim_start_non_core_children (GESTimelineElement * clip,
GstClockTimeDiff offset, GHashTable * edit_table) GstClockTimeDiff offset, GHashTable * edit_table, GError ** error)
{ {
GList *tmp; GList *tmp;
GESTimelineElement *child; GESTimelineElement *child;
@ -916,7 +1030,7 @@ set_edit_trim_start_non_core_children (GESTimelineElement * clip,
data = new_edit_data (EDIT_TRIM_START, offset, 0); data = new_edit_data (EDIT_TRIM_START, offset, 0);
g_hash_table_insert (edit_table, child, data); 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; 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 */ /* trim the start of a clip or a track element */
static gboolean static gboolean
set_edit_trim_start_values (GESTimelineElement * element, EditData * data, 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 = GstClockTime new_start =
_clock_time_minus_diff (element->start, data->offset, NULL); _clock_time_minus_diff (element->start, data->offset, &negative);
GstClockTime new_duration =
_clock_time_minus_diff (element->duration, -data->offset, NULL);
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 GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an " " with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid start", GES_ARGS (element), data->offset); "invalid start", GES_ARGS (element), data->offset);
if (negative)
set_negative_start_error (error, element, new_start);
return FALSE; 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 GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an " " with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid duration", GES_ARGS (element), data->offset); "invalid duration", GES_ARGS (element), data->offset);
if (negative)
set_negative_duration_error (error, element, new_duration);
return FALSE; return FALSE;
} }
_CHECK_END (element, new_start, new_duration); _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 (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; return FALSE;
if (!set_edit_trim_start_non_core_children (element, data->offset, if (!set_edit_trim_start_non_core_children (element, data->offset,
edit_table)) edit_table, error))
return FALSE; return FALSE;
} else if (GES_IS_TRACK_ELEMENT (element) } else if (GES_IS_TRACK_ELEMENT (element)
&& ges_track_element_has_internal_source (GES_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; return FALSE;
} }
data->start = new_start;
data->duration = new_duration;
/* NOTE: without time effects, the duration-limit will increase with /* NOTE: without time effects, the duration-limit will increase with
* a decrease in in-point by the same amount that duration increases, * 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), "to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start),
GST_TIME_ARGS (data->inpoint), GST_TIME_ARGS (data->duration)); 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 */ /* trim the end of a clip or a track element */
static gboolean 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 = GstClockTime new_duration =
_clock_time_minus_diff (element->duration, data->offset, NULL); _clock_time_minus_diff (element->duration, data->offset, &negative);
if (!GST_CLOCK_TIME_IS_VALID (new_duration)) { if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) {
GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an " " with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid duration", GES_ARGS (element), data->offset); "invalid duration", GES_ARGS (element), data->offset);
if (negative)
set_negative_duration_error (error, element, new_duration);
return FALSE; return FALSE;
} }
_CHECK_END (element, element->start, new_duration); _CHECK_END (element, element->start, new_duration);
if (GES_IS_CLIP (element)) { if (GES_IS_CLIP (element)) {
GstClockTime limit = ges_clip_get_duration_limit (GES_CLIP (element)); GESClip *clip = GES_CLIP (element);
if (GST_CLOCK_TIME_IS_VALID (limit) && new_duration > limit) { 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 GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because the duration would " " with offset %" G_GINT64_FORMAT " because the duration would "
"exceed the clip's duration-limit %" G_GINT64_FORMAT, "exceed the clip's duration-limit %" G_GINT64_FORMAT,
GES_ARGS (element), data->offset, limit); GES_ARGS (element), data->offset, limit);
set_breaks_duration_limit_error (error, clip, new_duration, limit);
return FALSE; return FALSE;
} }
} }
data->duration = new_duration; data->duration = new_duration;
if (GES_IS_GROUP (element))
return TRUE;
GST_INFO_OBJECT (element, "%s will trim end by setting duration to %" GST_INFO_OBJECT (element, "%s will trim end by setting duration to %"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->duration)); 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 static gboolean
set_clip_edit_values (GESTimelineElement * element, EditData * data, set_edit_values (GESTimelineElement * element, EditData * data,
GHashTable * edit_table) GHashTable * edit_table, GError ** error)
{ {
switch (data->mode) { switch (data->mode) {
case EDIT_MOVE: case EDIT_MOVE:
return set_edit_move_values (element, data); return set_edit_move_values (element, data, error);
case EDIT_TRIM_START: 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: case EDIT_TRIM_END:
return set_edit_trim_end_values (element, data); return set_edit_trim_end_values (element, data, error);
} }
return FALSE; return FALSE;
} }
@ -1041,7 +1178,7 @@ add_clips_to_list (GNode * node, GList ** list)
static gboolean static gboolean
replace_group_with_clip_edits (GNode * root, GESTimelineElement * group, replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
GHashTable * edit_table) GHashTable * edit_table, GError ** err)
{ {
gboolean ret = TRUE; gboolean ret = TRUE;
GList *tmp, *clips = NULL; GList *tmp, *clips = NULL;
@ -1064,13 +1201,26 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
goto error; 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; layer_offset = group_edit->layer_offset;
mode = group_edit->mode; 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_ /* can traverse leaves to find all the clips since they are at _most_
* one step above the track elements */ * one step above the track elements */
g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, 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); clip_data = new_edit_data (clip_mode, offset, layer_offset);
g_hash_table_insert (edit_table, clip, clip_data); 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; goto error;
} }
} }
@ -1165,7 +1315,8 @@ error:
/* set the edit values for the entries in @edits /* set the edit values for the entries in @edits
* any groups in @edits will be replaced by their clip children */ * any groups in @edits will be replaced by their clip children */
static gboolean 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; gboolean ret = TRUE;
GESTimelineElement *element; GESTimelineElement *element;
@ -1183,9 +1334,9 @@ timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits)
goto error; goto error;
} }
if (GES_IS_GROUP (element)) 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 else
res = set_clip_edit_values (element, edit_data, edits); res = set_edit_values (element, edit_data, edits, err);
if (!res) if (!res)
goto error; goto error;
} }
@ -1368,7 +1519,7 @@ add_element_edit (GHashTable * edits, GESTimelineElement * element,
gboolean gboolean
timeline_tree_can_move_element (GNode * root, timeline_tree_can_move_element (GNode * root,
GESTimelineElement * element, guint32 priority, GstClockTime start, GESTimelineElement * element, guint32 priority, GstClockTime start,
GstClockTime duration) GstClockTime duration, GError ** error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
guint32 layer_prio = ges_timeline_element_get_layer_priority (element); 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 */ /* assume both edits can be performed if each could occur individually */
/* should not effect duration or in-point */ /* 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; goto done;
/* should not effect start or in-point or layer */ /* 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; goto done;
/* merge the two edits into moving positions */ /* merge the two edits into moving positions */
@ -1483,7 +1634,7 @@ timeline_tree_can_move_element (GNode * root,
} }
/* check overlaps */ /* check overlaps */
if (!timeline_tree_can_move_elements (root, moving)) if (!timeline_tree_can_move_elements (root, moving, error))
goto done; goto done;
ret = TRUE; ret = TRUE;
@ -1624,7 +1775,7 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits)
gboolean gboolean
timeline_tree_ripple (GNode * root, GESTimelineElement * element, timeline_tree_ripple (GNode * root, GESTimelineElement * element,
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
GstClockTime snapping_distance) GstClockTime snapping_distance, GError ** error)
{ {
gboolean res = TRUE; gboolean res = TRUE;
GNode *node; GNode *node;
@ -1697,12 +1848,12 @@ timeline_tree_ripple (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */ /* check and set edits using snapped values */
give_edits_same_offset (edits, offset, layer_priority_offset); 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; goto error;
/* check overlaps */ /* check overlaps */
set_moving_positions_from_edits (moving, edits); 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; goto error;
/* emit snapping now. Edits should only fail if a programming error /* emit snapping now. Edits should only fail if a programming error
@ -1731,7 +1882,7 @@ error:
gboolean gboolean
timeline_tree_trim (GNode * root, GESTimelineElement * element, timeline_tree_trim (GNode * root, GESTimelineElement * element,
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
GstClockTime snapping_distance) GstClockTime snapping_distance, GError ** error)
{ {
gboolean res = TRUE; gboolean res = TRUE;
GHashTable *edits = new_edit_table (); GHashTable *edits = new_edit_table ();
@ -1778,12 +1929,12 @@ timeline_tree_trim (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */ /* check and set edits using snapped values */
give_edits_same_offset (edits, offset, layer_priority_offset); 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; goto error;
/* check overlaps */ /* check overlaps */
set_moving_positions_from_edits (moving, edits); 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; goto error;
} }
@ -1813,7 +1964,7 @@ error:
gboolean gboolean
timeline_tree_move (GNode * root, GESTimelineElement * element, timeline_tree_move (GNode * root, GESTimelineElement * element,
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge, gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
GstClockTime snapping_distance) GstClockTime snapping_distance, GError ** error)
{ {
gboolean res = TRUE; gboolean res = TRUE;
GHashTable *edits = new_edit_table (); GHashTable *edits = new_edit_table ();
@ -1861,12 +2012,12 @@ timeline_tree_move (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */ /* check and set edits using snapped values */
give_edits_same_offset (edits, offset, layer_priority_offset); 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; goto error;
/* check overlaps */ /* check overlaps */
set_moving_positions_from_edits (moving, edits); 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; goto error;
} }
@ -1960,7 +2111,8 @@ find_sources_at_position (GNode * node, TreeIterationData * data)
gboolean gboolean
timeline_tree_roll (GNode * root, GESTimelineElement * element, 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; gboolean res = TRUE;
GList *tmp; GList *tmp;
@ -2043,12 +2195,12 @@ timeline_tree_roll (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */ /* check and set edits using snapped values */
give_edits_same_offset (edits, offset, 0); 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; goto error;
/* check overlaps */ /* check overlaps */
set_moving_positions_from_edits (moving, edits); 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; goto error;
} }

View file

@ -13,14 +13,16 @@ gboolean timeline_tree_can_move_element (GNode *root,
GESTimelineElement *element, GESTimelineElement *element,
guint32 priority, guint32 priority,
GstClockTime start, GstClockTime start,
GstClockTime duration); GstClockTime duration,
GError ** error);
gboolean timeline_tree_ripple (GNode *root, gboolean timeline_tree_ripple (GNode *root,
GESTimelineElement *element, GESTimelineElement *element,
gint64 layer_priority_offset, gint64 layer_priority_offset,
GstClockTimeDiff offset, GstClockTimeDiff offset,
GESEdge edge, GESEdge edge,
GstClockTime snapping_distance); GstClockTime snapping_distance,
GError ** error);
void ges_timeline_emit_snapping (GESTimeline * timeline, void ges_timeline_emit_snapping (GESTimeline * timeline,
GESTrackElement * elem1, GESTrackElement * elem1,
@ -32,7 +34,8 @@ gboolean timeline_tree_trim (GNode *root,
gint64 layer_priority_offset, gint64 layer_priority_offset,
GstClockTimeDiff offset, GstClockTimeDiff offset,
GESEdge edge, GESEdge edge,
GstClockTime snapping_distance); GstClockTime snapping_distance,
GError ** error);
gboolean timeline_tree_move (GNode *root, gboolean timeline_tree_move (GNode *root,
@ -40,13 +43,15 @@ gboolean timeline_tree_move (GNode *root,
gint64 layer_priority_offset, gint64 layer_priority_offset,
GstClockTimeDiff offset, GstClockTimeDiff offset,
GESEdge edge, GESEdge edge,
GstClockTime snapping_distance); GstClockTime snapping_distance,
GError ** error);
gboolean timeline_tree_roll (GNode * root, gboolean timeline_tree_roll (GNode * root,
GESTimelineElement * element, GESTimelineElement * element,
GstClockTimeDiff offset, GstClockTimeDiff offset,
GESEdge edge, GESEdge edge,
GstClockTime snapping_distance); GstClockTime snapping_distance,
GError ** error);
typedef GESAutoTransition * typedef GESAutoTransition *
(*GESTreeGetAutoTransitionFunc) (GESTimeline * timeline, (*GESTreeGetAutoTransitionFunc) (GESTimeline * timeline,

View file

@ -52,20 +52,74 @@
* content prioritised in the tracks. This ordering can be changed using * content prioritised in the tracks. This ordering can be changed using
* ges_timeline_move_layer(). * 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 * ## Saving
* *
* To save/load a timeline, you can use the ges_timeline_load_from_uri() * 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. * 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 * ## Playing
* *
* A timeline is a #GstBin with a source #GstPad for each of its * 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 /* While we are creating and adding the TrackElements for a clip, we need to
* ignore the child-added signal */ * ignore the child-added signal */
gboolean track_elements_moving; 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; GList *groups;
guint stream_start_group_id; 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->auto_transition_track);
gst_clear_object (&priv->new_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); G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
} }
@ -553,8 +613,10 @@ ges_timeline_class_init (GESTimelineClass * klass)
/** /**
* GESTimeline:auto-transition: * GESTimeline:auto-transition:
* *
* Whether to automatically create a transition whenever two clips * Whether to automatically create a transition whenever two
* overlap in the timeline. See #GESLayer:auto-transition. * #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_object_class_install_property (object_class, PROP_AUTO_TRANSITION,
g_param_spec_boolean ("auto-transition", "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 static gint
_edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element, _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
GstClockTime position) GstClockTime position, GError ** error)
{ {
GList *tmp; GList *tmp;
guint32 layer_prio = ges_timeline_element_get_layer_priority (element); 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 " GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place of "
"trimming the corresponding auto-transition", GES_ARGS (replace)); "trimming the corresponding auto-transition", GES_ARGS (replace));
return ges_timeline_element_edit (replace, layers, -1, mode, edge, return ges_timeline_element_edit_full (replace, -1, mode, edge,
position); position, error);
} }
} }
@ -1220,8 +1282,8 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
gboolean gboolean
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element, ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position) guint64 position, GError ** error)
{ {
GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ? GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ?
GST_CLOCK_DIFF (position, element->start + element->duration) : GST_CLOCK_DIFF (position, element->start + element->duration) :
@ -1231,8 +1293,8 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
gint res = -1; gint res = -1;
if ((GES_IS_TRANSITION (element) || GES_IS_TRANSITION_CLIP (element))) if ((GES_IS_TRANSITION (element) || GES_IS_TRANSITION_CLIP (element)))
res = _edit_auto_transition (timeline, element, layers, new_layer_priority, res = _edit_auto_transition (timeline, element, new_layer_priority, mode,
mode, edge, position); edge, position, error);
if (res != -1) if (res != -1)
return res; return res;
@ -1240,20 +1302,20 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
switch (mode) { switch (mode) {
case GES_EDIT_MODE_RIPPLE: case GES_EDIT_MODE_RIPPLE:
return timeline_tree_ripple (timeline->priv->tree, element, prio_diff, 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: case GES_EDIT_MODE_TRIM:
return timeline_tree_trim (timeline->priv->tree, element, prio_diff, 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: case GES_EDIT_MODE_NORMAL:
return timeline_tree_move (timeline->priv->tree, element, prio_diff, 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: case GES_EDIT_MODE_ROLL:
if (prio_diff != 0) { if (prio_diff != 0) {
GST_WARNING_OBJECT (element, "Cannot roll an element to a new layer"); GST_WARNING_OBJECT (element, "Cannot roll an element to a new layer");
return FALSE; return FALSE;
} }
return timeline_tree_roll (timeline->priv->tree, element, 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: case GES_EDIT_MODE_SLIDE:
GST_ERROR_OBJECT (element, "Sliding not implemented."); GST_ERROR_OBJECT (element, "Sliding not implemented.");
return FALSE; return FALSE;
@ -1420,7 +1482,7 @@ _get_selected_tracks (GESTimeline * timeline, GESClip * clip,
* selected tracks */ * selected tracks */
static gboolean static gboolean
_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip, _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element) GESTrackElement * track_element, GError ** error)
{ {
guint i; guint i;
gboolean ret = TRUE; gboolean ret = TRUE;
@ -1428,8 +1490,11 @@ _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
for (i = 0; i < tracks->len; i++) { for (i = 0; i < tracks->len; i++) {
GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, 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; ret = FALSE;
if (error)
break;
}
} }
g_ptr_array_unref (tracks); g_ptr_array_unref (tracks);
@ -1439,7 +1504,7 @@ _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
static gboolean static gboolean
_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip, _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; gboolean no_error = TRUE;
GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element); 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 * if it does we add the track element to the track, or add a copy if
* the track element is already in a track */ * the track element is already in a track */
if (g_ptr_array_find (tracks, track, NULL)) { 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; no_error = FALSE;
} }
@ -1469,21 +1534,46 @@ ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving)
} }
static void 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); 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); UNLOCK_DYN (timeline);
} }
static gboolean static gboolean
_get_track_selection_error (GESTimeline * timeline) ges_timeline_take_track_selection_error (GESTimeline * timeline,
GError ** error)
{ {
gboolean ret; gboolean ret;
GESTimelinePrivate *priv;
LOCK_DYN (timeline); 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); UNLOCK_DYN (timeline);
return ret; return ret;
@ -1494,7 +1584,8 @@ clip_track_element_added_cb (GESClip * clip,
GESTrackElement * track_element, GESTimeline * timeline) GESTrackElement * track_element, GESTimeline * timeline)
{ {
GESTrack *auto_trans_track, *new_track; GESTrack *auto_trans_track, *new_track;
gboolean error = FALSE; GError *error = NULL;
gboolean success = FALSE;
if (timeline->priv->track_elements_moving) { if (timeline->priv->track_elements_moving) {
GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT
@ -1521,21 +1612,24 @@ clip_track_element_added_cb (GESClip * clip,
if (auto_trans_track) { if (auto_trans_track) {
/* don't use track-selection */ /* don't use track-selection */
if (!ges_clip_add_child_to_track (clip, track_element, auto_trans_track, success = ! !ges_clip_add_child_to_track (clip, track_element,
NULL)) auto_trans_track, &error);
error = TRUE;
gst_object_unref (auto_trans_track); gst_object_unref (auto_trans_track);
} else { } else {
if (new_track) if (new_track)
error = success = _try_add_track_element_to_track (timeline, clip, track_element,
!_try_add_track_element_to_track (timeline, clip, track_element, new_track, &error);
new_track);
else else
error = !_add_track_element_to_tracks (timeline, clip, track_element); success = _add_track_element_to_tracks (timeline, clip, track_element,
&error);
} }
if (error) if (error || !success) {
_set_track_selection_error (timeline, TRUE); 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 static void
@ -1573,7 +1667,7 @@ track_element_added_cb (GESTrack * track, GESTrackElement * element,
/* returns TRUE if no errors in adding to tracks */ /* returns TRUE if no errors in adding to tracks */
static gboolean static gboolean
_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip, _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; GList *tmp, *children;
gboolean no_errors = TRUE; 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) { if (ges_track_element_get_track (el) == NULL) {
gboolean res; gboolean res;
if (new_track) 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 else
res = _add_track_element_to_tracks (timeline, clip, el); res = _add_track_element_to_tracks (timeline, clip, el, error);
if (!res) if (!res) {
no_errors = FALSE; no_errors = FALSE;
if (error)
goto done;
}
} }
} }
done:
g_list_free_full (children, gst_object_unref); g_list_free_full (children, gst_object_unref);
return no_errors; 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 */ /* returns TRUE if no errors in adding to tracks */
static gboolean static gboolean
add_object_to_tracks (GESTimeline * timeline, GESClip * clip, add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
GESTrack * new_track) GESTrack * new_track, GError ** error)
{ {
GList *tracks, *tmp, *list, *created, *just_added = NULL; GList *tracks, *tmp, *list, *created, *just_added = NULL;
gboolean no_errors = TRUE; 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 GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
" trackelements and adding them to our tracks", clip); " 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); g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
timeline->priv->new_track = new_track ? gst_object_ref (new_track) : NULL; timeline->priv->new_track = new_track ? gst_object_ref (new_track) : NULL;
UNLOCK_DYN (timeline); UNLOCK_DYN (timeline);
/* create core elements */ /* create core elements */
for (tmp = tracks; tmp; tmp = tmp->next) { for (tmp = tracks; tmp; tmp = tmp->next) {
GESTrack *track = GES_TRACK (tmp->data); GESTrack *track = GES_TRACK (tmp->data);
if (new_track && track != new_track) if (new_track && track != new_track)
continue; continue;
list = ges_clip_create_track_elements (clip, track->type); 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) { for (created = list; created; created = created->next) {
GESTimelineElement *el = created->data; 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 * released. And when a child is selected for multiple tracks, their
* copy will be added to the clip before the track is selected, so * copy will be added to the clip before the track is selected, so
* the track will not be set in the child-added signal */ * the track will not be set in the child-added signal */
_set_track_selection_error (timeline, FALSE); ges_timeline_set_track_selection_error (timeline, FALSE, NULL);
if (!ges_container_add (GES_CONTAINER (clip), el)) if (!ges_container_add (GES_CONTAINER (clip), el)) {
no_errors = FALSE;
GST_ERROR_OBJECT (clip, "Could not add the core element %s " GST_ERROR_OBJECT (clip, "Could not add the core element %s "
"to the clip", el->name); "to the clip", el->name);
if (_get_track_selection_error (timeline)) }
no_errors = FALSE;
gst_object_unref (el); 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 /* 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 * 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 /* include just_added as a blacklist to ensure we do not try the track
* selection a second time when track selection returns no tracks */ * selection a second time when track selection returns no tracks */
if (!_add_clip_children_to_tracks (timeline, clip, TRUE, new_track, if (!_add_clip_children_to_tracks (timeline, clip, TRUE, new_track,
just_added)) just_added, error)) {
no_errors = FALSE; no_errors = FALSE;
if (error)
goto done;
}
if (!_add_clip_children_to_tracks (timeline, clip, FALSE, new_track, if (!_add_clip_children_to_tracks (timeline, clip, FALSE, new_track,
just_added)) just_added, error)) {
no_errors = FALSE; no_errors = FALSE;
if (error)
goto done;
}
done:
g_list_free_full (tracks, gst_object_unref);
LOCK_DYN (timeline); LOCK_DYN (timeline);
gst_clear_object (&timeline->priv->new_track); 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 */ /* returns TRUE if selecting of tracks did not error */
gboolean gboolean
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip) ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error)
{ {
GESProject *project; GESProject *project;
gboolean ret; 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); 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 */ /* timeline-tree handles creation of auto-transitions */
ret = TRUE; ret = TRUE;
} else { } else {
ret = add_object_to_tracks (timeline, clip, NULL); ret = add_object_to_tracks (timeline, clip, NULL, error);
} }
GST_DEBUG ("Done"); GST_DEBUG ("Done");
@ -2215,7 +2332,7 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
/* add any existing clips to the timeline */ /* add any existing clips to the timeline */
objects = ges_layer_get_clips (layer); objects = ges_layer_get_clips (layer);
for (tmp = objects; tmp; tmp = tmp->next) 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); g_list_free_full (objects, gst_object_unref);
return TRUE; return TRUE;
@ -2361,7 +2478,7 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
objects = ges_layer_get_clips (tmp->data); objects = ges_layer_get_clips (tmp->data);
for (obj = objects; obj; obj = obj->next) 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); g_list_free_full (objects, gst_object_unref);
} }

View file

@ -630,6 +630,7 @@ _set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
{ {
GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTrackElement *object = GES_TRACK_ELEMENT (element);
GESTimelineElement *parent = element->parent; GESTimelineElement *parent = element->parent;
GError *error = NULL;
g_return_val_if_fail (object->priv->nleobject, FALSE); g_return_val_if_fail (object->priv->nleobject, FALSE);
if (inpoint && !object->priv->has_internal_source) { if (inpoint && !object->priv->has_internal_source) {
@ -640,10 +641,12 @@ _set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
if (GES_IS_CLIP (parent) if (GES_IS_CLIP (parent)
&& !ges_clip_can_set_inpoint_of_child (GES_CLIP (parent), object, && !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_WARNING_OBJECT (element, "Cannot set an in-point of %"
GST_TIME_FORMAT " because the parent clip %" GES_FORMAT 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; return FALSE;
} }
@ -678,6 +681,7 @@ _set_max_duration (GESTimelineElement * element, GstClockTime max_duration)
{ {
GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTrackElement *object = GES_TRACK_ELEMENT (element);
GESTimelineElement *parent = element->parent; GESTimelineElement *parent = element->parent;
GError *error = NULL;
if (GST_CLOCK_TIME_IS_VALID (max_duration) if (GST_CLOCK_TIME_IS_VALID (max_duration)
&& !object->priv->has_internal_source) { && !object->priv->has_internal_source) {
@ -688,10 +692,12 @@ _set_max_duration (GESTimelineElement * element, GstClockTime max_duration)
if (GES_IS_CLIP (parent) if (GES_IS_CLIP (parent)
&& !ges_clip_can_set_max_duration_of_child (GES_CLIP (parent), object, && !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_WARNING_OBJECT (element, "Cannot set a max-duration of %"
GST_TIME_FORMAT " because the parent clip %" GES_FORMAT 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; return FALSE;
} }
@ -704,6 +710,7 @@ _set_priority (GESTimelineElement * element, guint32 priority)
{ {
GESTrackElement *object = GES_TRACK_ELEMENT (element); GESTrackElement *object = GES_TRACK_ELEMENT (element);
GESTimelineElement *parent = element->parent; GESTimelineElement *parent = element->parent;
GError *error = NULL;
g_return_val_if_fail (object->priv->nleobject, FALSE); g_return_val_if_fail (object->priv->nleobject, FALSE);
@ -720,10 +727,12 @@ _set_priority (GESTimelineElement * element, guint32 priority)
if (GES_IS_CLIP (parent) if (GES_IS_CLIP (parent)
&& !ges_clip_can_set_priority_of_child (GES_CLIP (parent), object, && !ges_clip_can_set_priority_of_child (GES_CLIP (parent), object,
priority)) { priority, &error)) {
GST_WARNING_OBJECT (element, "Cannot set a priority of %" GST_WARNING_OBJECT (element, "Cannot set a priority of %"
G_GUINT32_FORMAT " because the parent clip %" GES_FORMAT 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; return FALSE;
} }
@ -751,6 +760,7 @@ gboolean
ges_track_element_set_active (GESTrackElement * object, gboolean active) ges_track_element_set_active (GESTrackElement * object, gboolean active)
{ {
GESTimelineElement *parent; GESTimelineElement *parent;
GError *error = NULL;
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
g_return_val_if_fail (object->priv->nleobject, 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); parent = GES_TIMELINE_ELEMENT_PARENT (object);
if (GES_IS_CLIP (parent) if (GES_IS_CLIP (parent)
&& !ges_clip_can_set_active_of_child (GES_CLIP (parent), object, active)) { && !ges_clip_can_set_active_of_child (GES_CLIP (parent), object, active,
GST_WARNING_OBJECT (object, "Cannot set active to %i because the parent " &error)) {
"clip %" GES_FORMAT " would not allow it", active, GES_ARGS (parent)); 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; return FALSE;
} }
@ -1057,7 +1071,8 @@ ges_track_element_add_children_props (GESTrackElement * self,
/* INTERNAL USAGE */ /* INTERNAL USAGE */
gboolean 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); 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); GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track);
if (GES_IS_CLIP (parent) if (GES_IS_CLIP (parent)
&& !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track)) { && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track,
GST_WARNING_OBJECT (object, "The parent clip %" GES_FORMAT " would " error)) {
"not allow the track to be set to %" GST_PTR_FORMAT, GST_INFO_OBJECT (object,
GES_ARGS (parent), track); "The parent clip %" GES_FORMAT " would not allow the track to be "
"set to %" GST_PTR_FORMAT, GES_ARGS (parent), track);
return FALSE; return FALSE;
} }

View file

@ -383,7 +383,7 @@ ges_track_get_composition (GESTrack * track)
*/ */
static gboolean static gboolean
remove_object_internal (GESTrack * track, GESTrackElement * object, remove_object_internal (GESTrack * track, GESTrackElement * object,
gboolean emit) gboolean emit, GError ** error)
{ {
GESTrackPrivate *priv; GESTrackPrivate *priv;
GstElement *nleobject; GstElement *nleobject;
@ -397,8 +397,8 @@ remove_object_internal (GESTrack * track, GESTrackElement * object,
return FALSE; return FALSE;
} }
if (!ges_track_element_set_track (object, NULL)) { if (!ges_track_element_set_track (object, NULL, error)) {
GST_WARNING_OBJECT (track, "Failed to unset the track for %" GES_FORMAT, GST_INFO_OBJECT (track, "Failed to unset the track for %" GES_FORMAT,
GES_ARGS (object)); GES_ARGS (object));
return FALSE; return FALSE;
} }
@ -426,7 +426,7 @@ remove_object_internal (GESTrack * track, GESTrackElement * object,
static void static void
dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track) dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
{ {
remove_object_internal (track, trackelement, TRUE); remove_object_internal (track, trackelement, TRUE, NULL);
} }
/* GstElement virtual methods */ /* GstElement virtual methods */
@ -1110,7 +1110,7 @@ notify:
static gboolean static gboolean
remove_element_internal (GESTrack * track, GESTrackElement * object, remove_element_internal (GESTrack * track, GESTrackElement * object,
gboolean emit) gboolean emit, GError ** error)
{ {
GSequenceIter *it; GSequenceIter *it;
GESTrackPrivate *priv = track->priv; GESTrackPrivate *priv = track->priv;
@ -1120,7 +1120,7 @@ remove_element_internal (GESTrack * track, GESTrackElement * object,
it = g_hash_table_lookup (priv->trackelements_iter, object); it = g_hash_table_lookup (priv->trackelements_iter, object);
g_sequence_remove (it); 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); ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
return TRUE; 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 * @track: A #GESTrack
* @object: (transfer floating): The element to add * @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 * Adds the given track element to the track, which takes ownership of the
* element. * element.
@ -1149,13 +1150,15 @@ remove_element_internal (GESTrack * track, GESTrackElement * object,
* Returns: %TRUE if @object was successfully added to @track. * Returns: %TRUE if @object was successfully added to @track.
*/ */
gboolean gboolean
ges_track_add_element (GESTrack * track, GESTrackElement * object) ges_track_add_element_full (GESTrack * track, GESTrackElement * object,
GError ** error)
{ {
GESTimeline *timeline; GESTimeline *timeline;
GESTimelineElement *el; GESTimelineElement *el;
g_return_val_if_fail (GES_IS_TRACK (track), FALSE); 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 (GES_IS_TRACK_ELEMENT (object), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
el = GES_TIMELINE_ELEMENT (object); el = GES_TIMELINE_ELEMENT (object);
@ -1170,8 +1173,8 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
return FALSE; return FALSE;
} }
if (!ges_track_element_set_track (object, track)) { if (!ges_track_element_set_track (object, track, error)) {
GST_WARNING_OBJECT (track, "Failed to set the track for %" GES_FORMAT, GST_INFO_OBJECT (track, "Failed to set the track for %" GES_FORMAT,
GES_ARGS (object)); GES_ARGS (object));
gst_object_ref_sink (object); gst_object_ref_sink (object);
gst_object_unref (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, if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition,
ges_track_element_get_nleobject (object)))) { ges_track_element_get_nleobject (object)))) {
GST_WARNING ("Couldn't add object to the NleComposition"); 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_ref_sink (object);
gst_object_unref (object); gst_object_unref (object);
return FALSE; return FALSE;
@ -1203,12 +1208,13 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
* element to the track */ * element to the track */
if (timeline if (timeline
&& !timeline_tree_can_move_element (timeline_get_tree (timeline), el, && !timeline_tree_can_move_element (timeline_get_tree (timeline), el,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration)) { GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration,
GST_WARNING_OBJECT (track, error)) {
GST_INFO_OBJECT (track,
"Could not add the track element %" GES_FORMAT "Could not add the track element %" GES_FORMAT
" to the track because it breaks the timeline " "configuration rules", " to the track because it breaks the timeline " "configuration rules",
GES_ARGS (el)); GES_ARGS (el));
remove_element_internal (track, object, FALSE); remove_element_internal (track, object, FALSE, NULL);
return FALSE; return FALSE;
} }
@ -1218,6 +1224,21 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
return TRUE; 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: * ges_track_get_elements:
* @track: A #GESTrack * @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 * @track: A #GESTrack
* @object: The element to remove * @object: The element to remove
* @error: (nullable): Return location for an error
* *
* Removes the given track element from the track, which revokes * Removes the given track element from the track, which revokes
* ownership of the element. * ownership of the element.
@ -1255,16 +1277,33 @@ ges_track_get_elements (GESTrack * track)
* Returns: %TRUE if @object was successfully removed from @track. * Returns: %TRUE if @object was successfully removed from @track.
*/ */
gboolean 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 (track), FALSE);
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (!track->priv->timeline if (!track->priv->timeline
|| !ges_timeline_is_disposed (track->priv->timeline)) || !ges_timeline_is_disposed (track->priv->timeline))
CHECK_THREAD (track); 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);
} }
/** /**

View file

@ -83,25 +83,41 @@ const GESTimeline* ges_track_get_timeline (GESTrack *track);
GES_API GES_API
gboolean ges_track_commit (GESTrack *track); gboolean ges_track_commit (GESTrack *track);
GES_API GES_API
void ges_track_set_timeline (GESTrack *track, GESTimeline *timeline); void ges_track_set_timeline (GESTrack *track,
GESTimeline *timeline);
GES_API GES_API
gboolean ges_track_add_element (GESTrack *track, GESTrackElement *object); gboolean ges_track_add_element (GESTrack *track,
GESTrackElement *object);
GES_API 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 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 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 GES_API
gboolean ges_track_get_mixing (GESTrack *track); gboolean ges_track_get_mixing (GESTrack *track);
GES_API 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 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 GES_API
GstCaps * ges_track_get_restriction_caps (GESTrack * track); GstCaps * ges_track_get_restriction_caps (GESTrack * track);
GES_API GES_API
GESTrack* ges_track_new (GESTrackType type, GstCaps * caps); GESTrack* ges_track_new (GESTrackType type,
GstCaps * caps);
G_END_DECLS G_END_DECLS

View file

@ -1249,6 +1249,7 @@ GST_START_TEST (test_adding_children_to_track)
GESTrackElement *source, *effect, *effect2, *added, *added2, *added3; GESTrackElement *source, *effect, *effect2, *added, *added2, *added3;
GstControlSource *ctrl_source; GstControlSource *ctrl_source;
guint selection_called = 0; guint selection_called = 0;
GError *error = NULL;
ges_init (); ges_init ();
@ -1257,7 +1258,6 @@ GST_START_TEST (test_adding_children_to_track)
track1 = GES_TRACK (ges_video_track_new ()); track1 = GES_TRACK (ges_video_track_new ());
track2 = GES_TRACK (ges_video_track_new ()); track2 = GES_TRACK (ges_video_track_new ());
/* only add two for now */ /* only add two for now */
fail_unless (ges_timeline_add_track (timeline, track1)); 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); gst_object_unref (ctrl_source);
/* can't add to a track that does not belong to the timeline */ /* 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); assert_num_children (clip, 3);
fail_unless (ges_track_element_get_track (source) == track1); fail_unless (ges_track_element_get_track (source) == track1);
assert_num_in_track (track1, 3); assert_num_in_track (track1, 3);
assert_num_in_track (track2, 0); 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 */ /* 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); assert_num_children (clip, 3);
fail_unless (ges_track_element_get_track (source) == track1); fail_unless (ges_track_element_get_track (source) == track1);
assert_num_in_track (track1, 3); assert_num_in_track (track1, 3);
assert_num_in_track (track2, 0); assert_num_in_track (track2, 0);
fail_if (error);
/* can't remove a core element from its track whilst a non-core sits /* can't remove a core element from its track whilst a non-core sits
* above it */ * above it */
@ -1347,10 +1350,11 @@ GST_START_TEST (test_adding_children_to_track)
assert_num_in_track (track2, 0); assert_num_in_track (track2, 0);
/* can not add to the same track as it is currently in */ /* 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); fail_unless (ges_track_element_get_track (effect) == track1);
assert_num_in_track (track1, 3); assert_num_in_track (track1, 3);
assert_num_in_track (track2, 0); assert_num_in_track (track2, 0);
fail_if (error);
/* adding another video track, select-tracks-for-object will do nothing /* adding another video track, select-tracks-for-object will do nothing
* since no each track element is already part of a track */ * 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); assert_num_in_track (track2, 0);
/* can not add effect to a track that does not contain a core child */ /* 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_children (clip, 3);
assert_num_in_track (track1, 3); assert_num_in_track (track1, 3);
assert_num_in_track (track2, 0); assert_num_in_track (track2, 0);
fail_if (error);
/* can add core */ /* 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); fail_unless (added);
assert_num_children (clip, 4); assert_num_children (clip, 4);
fail_unless (added != source); 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); fail_unless (ges_track_element_get_track (added) == track2);
assert_num_in_track (track1, 3); assert_num_in_track (track1, 3);
assert_num_in_track (track2, 1); assert_num_in_track (track2, 1);
fail_if (error);
assert_equal_children_properties (added, source); assert_equal_children_properties (added, source);
assert_equal_bindings (added, source); assert_equal_bindings (added, source);
@ -1385,8 +1391,9 @@ GST_START_TEST (test_adding_children_to_track)
assert_equals_int (1, assert_equals_int (1,
ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); 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_unless (added2);
fail_if (error);
assert_num_children (clip, 5); assert_num_children (clip, 5);
fail_unless (added2 != effect); fail_unless (added2 != effect);
fail_unless (ges_track_element_get_track (effect) == track1); 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, assert_equals_int (2,
ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2))); 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_unless (added3);
fail_if (error);
assert_num_children (clip, 6); assert_num_children (clip, 6);
fail_unless (added3 != effect2); fail_unless (added3 != effect2);
fail_unless (ges_track_element_get_track (effect2) == track1); 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 /* can not add the source to the track because it would overlap another
* source */ * 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_children (clip, 3);
assert_num_in_track (track1, 4); 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 /* can not add source at time 23 because it would result in three
* overlapping sources in the track */ * overlapping sources in the track */
assert_set_start (clip, 23); 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_children (clip, 3);
assert_num_in_track (track1, 4); assert_num_in_track (track1, 4);
assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
/* can add at 5, with overlap */ /* can add at 5, with overlap */
assert_set_start (clip, 5); 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 */ /* added is the source since it was not already in a track */
fail_unless (added == source); fail_unless (added == source);
fail_if (error);
assert_num_children (clip, 3); assert_num_children (clip, 3);
/* 4 sources + 2 transitions */ /* 4 sources + 2 transitions */
assert_num_in_track (track1, 6); assert_num_in_track (track1, 6);
/* also add effect */ /* 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 */ /* added is the source since it was not already in a track */
fail_unless (added == effect); fail_unless (added == effect);
fail_if (error);
assert_num_children (clip, 3); assert_num_children (clip, 3);
assert_num_in_track (track1, 7); 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 */ /* added is the source since it was not already in a track */
fail_unless (added == effect2); fail_unless (added == effect2);
fail_if (error);
assert_num_children (clip, 3); assert_num_children (clip, 3);
assert_num_in_track (track1, 8); assert_num_in_track (track1, 8);
@ -3071,6 +3084,7 @@ GST_START_TEST (test_can_set_duration_limit)
GESTrackElement *effect0, *effect1, *effect2; GESTrackElement *effect0, *effect1, *effect2;
GESTrack *track0, *track1; GESTrack *track0, *track1;
gint limit_notify_count = 0; gint limit_notify_count = 0;
GError *error = NULL;
ges_init (); 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 (source1, 10, 16, 20, 36);
CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 10); 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 */ /* set max-duration to 11 and we are fine to select a track */
assert_set_max_duration (effect0, 11); assert_set_max_duration (effect0, 11);
@ -3183,7 +3198,8 @@ GST_START_TEST (test_can_set_duration_limit)
_assert_duration_limit (clip, 20); _assert_duration_limit (clip, 20);
fail_unless (ges_clip_add_child_to_track (clip, effect0, track0, 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_equals_int (limit_notify_count, 2);
_assert_duration_limit (clip, 11); _assert_duration_limit (clip, 11);

View file

@ -404,4 +404,13 @@ G_STMT_START { \
free_children_properties (props2, num2); \ 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); void print_timeline(GESTimeline *timeline);

View file

@ -149,6 +149,21 @@ class GESTest(unittest.TestCase):
for effect in effects] for effect in effects]
self.assertEqual(indexes, list(range(len(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): class GESSimpleTimelineTest(GESTest):
@ -680,7 +695,7 @@ class GESTimelineConfigTest(GESTest):
def assertEdit(self, element, layer, mode, edge, position, snap, def assertEdit(self, element, layer, mode, edge, position, snap,
snap_froms, snap_tos, new_props, new_transitions, snap_froms, snap_tos, new_props, new_transitions,
lost_transitions): lost_transitions):
if not element.edit([], layer, mode, edge, position): if not element.edit_full(layer, mode, edge, position):
raise AssertionError( raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at position {} " "Edit of {} to layer {}, mode {}, edge {}, at position {} "
"failed when a success was expected".format( "failed when a success was expected".format(
@ -690,11 +705,31 @@ class GESTimelineConfigTest(GESTest):
snap_tos=snap_tos, new_transitions=new_transitions, snap_tos=snap_tos, new_transitions=new_transitions,
lost_transitions=lost_transitions) lost_transitions=lost_transitions)
def assertFailEdit(self, element, layer, mode, edge, position): def assertFailEdit(self, element, layer, mode, edge, position, err_code):
if element.edit([], layer, mode, edge, position): res = None
raise AssertionError( error = None
"Edit of {} to layer {}, mode {}, edge {}, at position {} " try:
"succeeded when a failure was expected".format( res = element.edit_full(layer, mode, edge, position)
element, 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 # should be no change or snapping if edit fails
self.assertTimelineConfig() self.assertTimelineConfig()

View file

@ -27,6 +27,7 @@ gi.require_version("GES", "1.0")
from gi.repository import Gst # noqa from gi.repository import Gst # noqa
from gi.repository import GES # noqa from gi.repository import GES # noqa
from gi.repository import GLib # noqa
import unittest # noqa import unittest # noqa
from unittest import mock from unittest import mock
@ -561,8 +562,12 @@ class TestEditing(common.GESSimpleTimelineTest):
self.assertTrue(clip.set_duration(10)) self.assertTrue(clip.set_duration(10))
# cannot trim to a 0 because effect0 would have a negative in-point # cannot trim to a 0 because effect0 would have a negative in-point
self.assertFalse( error = None
clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)) 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.start, 10)
self.assertEqual(clip.inpoint, 12) self.assertEqual(clip.inpoint, 12)
@ -945,11 +950,20 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest):
clip2 = self.add_clip(start=10, in_point=0, duration=4) clip2 = self.add_clip(start=10, in_point=0, duration=4)
clip3 = self.add_clip(start=12, in_point=0, duration=3) clip3 = self.add_clip(start=12, in_point=0, duration=3)
self.assertIsNone(clip1.split(13)) self.assertIsNone(clip1.split_full(13))
self.assertIsNone(clip1.split(8)) self.assertIsNone(clip1.split_full(8))
self.assertIsNone(clip3.split_full(12))
self.assertIsNone(clip3.split_full(15))
self.assertIsNone(clip3.split(12)) def _fail_split(self, clip, position):
self.assertIsNone(clip3.split(15)) 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): def test_split_with_transition(self):
self.track_types = [GES.TrackType.AUDIO] 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) clip0 = self.add_clip(start=0, in_point=0, duration=50)
clip1 = self.add_clip(start=20, 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([ self.assertTimelineTopology([
[ [
(GES.TestClip, 0, 50), (GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30), (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 # would be fully overlapping clip0
self.assertIsNone(clip1.split(40)) self._fail_split(clip1, 40)
self.assertTimelineTopology([ self.assertTimelineTopology([
[ [
(GES.TestClip, 0, 50), (GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30), (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): 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: if layer is None:
layer = self.layer layer = self.layer
asset = GES.Asset.request(GES.TestClip, None) asset = GES.Asset.request(GES.TestClip, None)
found_err = None
clip = None
# large inpoint to allow trims # large inpoint to allow trims
return layer.add_asset (asset, start, 1000, duration, try:
GES.TrackType.UNKNOWN) 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): def test_full_overlap_add(self):
clip1 = self._try_add_clip(50, 50) clip1 = self._try_add_clip(50, 50)
self.assertIsNotNone(clip1) self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(self._try_add_clip(50, 50)) self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(self._try_add_clip(49, 51)) self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(self._try_add_clip(51, 49))
def test_triple_overlap_add(self): def test_triple_overlap_add(self):
clip1 = self._try_add_clip(0, 50) clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(40, 50) clip2 = self._try_add_clip(40, 50)
self.assertIsNotNone(clip2) self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(self._try_add_clip(40, 10)) self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(self._try_add_clip(30, 30)) self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(self._try_add_clip(1, 88))
def test_full_overlap_move(self): def test_full_overlap_move(self):
clip1 = self._try_add_clip(0, 50) clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(50, 50) clip2 = self._try_add_clip(50, 50)
self.assertIsNotNone(clip2)
self.assertFalse(clip2.set_start(0)) self.assertFalse(clip2.set_start(0))
def test_triple_overlap_move(self): def test_triple_overlap_move(self):
clip1 = self._try_add_clip(0, 50) clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(40, 50) clip2 = self._try_add_clip(40, 50)
self.assertIsNotNone(clip2)
clip3 = self._try_add_clip(100, 60) clip3 = self._try_add_clip(100, 60)
self.assertIsNotNone(clip3)
self.assertFalse(clip3.set_start(30)) self.assertFalse(clip3.set_start(30))
def test_full_overlap_move_into_layer(self): def test_full_overlap_move_into_layer(self):
clip1 = self._try_add_clip(0, 50) clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
layer2 = self.timeline.append_layer() layer2 = self.timeline.append_layer()
clip2 = self._try_add_clip(0, 50, layer2) clip2 = self._try_add_clip(0, 50, layer2)
self.assertIsNotNone(clip2) res = None
self.assertFalse(clip2.move_to_layer(self.layer)) 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): def test_triple_overlap_move_into_layer(self):
clip1 = self._try_add_clip(0, 50) clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(40, 50) clip2 = self._try_add_clip(40, 50)
self.assertIsNotNone(clip2)
layer2 = self.timeline.append_layer() layer2 = self.timeline.append_layer()
clip3 = self._try_add_clip(30, 30, layer2) clip3 = self._try_add_clip(30, 30, layer2)
self.assertIsNotNone(clip3) res = None
self.assertFalse(clip3.move_to_layer(self.layer)) 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): def test_full_overlap_trim(self):
clip1 = self._try_add_clip(0, 50) clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(50, 50) clip2 = self._try_add_clip(50, 50)
self.assertIsNotNone(clip2)
self.assertFalse(clip2.trim(0)) self.assertFalse(clip2.trim(0))
self.assertFalse(clip1.set_duration(100)) self.assertFalse(clip1.set_duration(100))
def test_triple_overlap_trim(self): def test_triple_overlap_trim(self):
clip1 = self._try_add_clip(0, 20) clip1 = self._try_add_clip(0, 20)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(10, 30) clip2 = self._try_add_clip(10, 30)
self.assertIsNotNone(clip2)
clip3 = self._try_add_clip(30, 20) clip3 = self._try_add_clip(30, 20)
self.assertIsNotNone(clip3)
self.assertFalse(clip3.trim(19)) self.assertFalse(clip3.trim(19))
self.assertFalse(clip1.set_duration(31)) 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 # cannot move c0 up one layer because it would cause a triple
# overlap between c1, c2 and c3 when g0 moves # overlap between c1, c2 and c3 when g0 moves
self.assertFailEdit( 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 # cannot move c0, without moving g1, to 21 layer 1 because it
# would be completely overlapped by c2 # would be completely overlapped by c2
self.assertFailEdit( 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 # cannot move c1, without moving g1, with end 25 because it
# would be completely overlapped by c2 # would be completely overlapped by c2
self.assertFailEdit( 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 # cannot move g0 to layer 0 because it would make c0 go to a
# negative layer # negative layer
self.assertFailEdit( 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 # cannot move c1 for same reason
self.assertFalse( error = None
c1.move_to_layer(self.timeline.get_layer(0))) 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({}, []) self.assertTimelineConfig({}, [])
# failure with snapping # 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 # cannot move to 0 because end edge of c0 would snap with end of
# c3, making the new start become negative # c3, making the new start become negative
self.assertFailEdit( 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 # cannot move start of c1 to 14 because snapping causes a full
# overlap with c0 # overlap with c0
self.assertFailEdit( 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 # cannot move end of c2 to 21 because snapping causes a full
# overlap with c0 # overlap with c0
self.assertFailEdit( 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 # successes
self.timeline.set_snapping_distance(3) self.timeline.set_snapping_distance(3)
@ -1701,19 +1748,22 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# would cause negative layer priority for c0 # would cause negative layer priority for c0
self.assertFailEdit( 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 # would lead to c2 fully overlapping c3 since c2 does ripple
# but c3 does not(c3 shares a toplevel with c0, and # but c3 does not(c3 shares a toplevel with c0, and
# GES_EDGE_START, same as NORMAL mode, does not move the # GES_EDGE_START, same as NORMAL mode, does not move the
# toplevel # toplevel
self.assertFailEdit( 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 # would lead to c2 fully overlapping c3 since c2 does not
# ripple but c3 does # ripple but c3 does
self.assertFailEdit( 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 # 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 # cannot trim end of g0 to 16 because a0 and a1 would fully
# overlap # overlap
self.assertFailEdit( 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 # cannot edit to new layer because there would be triple overlaps
# between v2, v3, v4 and v5 # between v2, v3, v4 and v5
self.assertFailEdit( 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 # cannot trim g1 end to 14 because it would result in a negative
# duration for a2 and a4 # duration for a2 and a4
self.assertFailEdit( 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 # cannot trim end of v2 below its start
self.assertFailEdit( 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 # cannot trim end of g0 because a0's duration-limit would be
# exceeded # exceeded
self.assertFailEdit( 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 # cannot trim g0 to 12 because a0 and a1 would fully overlap
self.assertFailEdit( 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 # cannot trim start of v2 beyond its end point
self.assertFailEdit( 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 # with snapping
self.timeline.set_snapping_distance(4) 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 # cannot trim end of g2 to 19 because v1 and v2 would fully
# overlap after snapping to v5 start edge(18) # overlap after snapping to v5 start edge(18)
self.assertFailEdit( 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 # cannot trim g2 to 3 because it would snap to start edge of
# v4(0), causing v2's in-point to be negative # v4(0), causing v2's in-point to be negative
self.assertFailEdit( 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 # success
@ -2737,46 +2796,66 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# cannot roll c10 to 22, which snaps to 23, because it will # cannot roll c10 to 22, which snaps to 23, because it will
# extend c5 beyond its duration limit of 8 # extend c5 beyond its duration limit of 8
self.assertFailEdit( 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 # same with g2
self.assertFailEdit( 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 # cannot roll end c9 to 8, which snaps to 7, because it would
# cause c3's in-point to become negative # cause c3's in-point to become negative
self.assertFailEdit( 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 # same with g1
self.assertFailEdit( 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 # cannot roll c13 to 19, snap to 20, because it would cause
# c4 to fully overlap c5 # c4 to fully overlap c5
self.assertFailEdit( 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 # cannot roll c12 to 11, snap to 10, because it would cause
# c3 to fully overlap c4 # c3 to fully overlap c4
self.assertFailEdit( 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 # cannot roll c6 to 0 because it would cause c9 to be trimmed
# below its start # below its start
self.assertFailEdit( 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 # cannot roll end c7 to 30 because it would cause c10 to be
# trimmed beyond its end # trimmed beyond its end
self.assertFailEdit( 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 # moving layer is not supported
self.assertFailEdit( 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( 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 # successes
self.timeline.set_snapping_distance(0) self.timeline.set_snapping_distance(0)