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 (b) ? MIN (a, b) : a) : b) \
#define _CLOCK_TIME_IS_LESS(first, second) \
(GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \
|| first < second))
/****************************************************
* duration-limit *
****************************************************/
@ -366,7 +362,7 @@ _update_duration_limit (GESClip * self)
GST_INFO_OBJECT (self, "duration-limit for the clip is %"
GST_TIME_FORMAT, GST_TIME_ARGS (duration_limit));
if (_CLOCK_TIME_IS_LESS (duration_limit, element->duration)
if (GES_CLOCK_TIME_IS_LESS (duration_limit, element->duration)
&& !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) {
gboolean res;
@ -379,7 +375,7 @@ _update_duration_limit (GESClip * self)
if (element->timeline)
res = timeline_tree_trim (timeline_get_tree (element->timeline),
element, 0, GST_CLOCK_DIFF (duration_limit, element->duration),
GES_EDGE_END, 0);
GES_EDGE_END, 0, NULL);
else
res = ges_timeline_element_set_duration (element, duration_limit);
@ -396,18 +392,18 @@ _update_duration_limit (GESClip * self)
/* transfer full of child_data */
static gboolean
_can_update_duration_limit (GESClip * self, GList * child_data)
_can_update_duration_limit (GESClip * self, GList * child_data, GError ** error)
{
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
GstClockTime duration = _calculate_duration_limit (self, child_data);
GESTimelineElement *element = GES_TIMELINE_ELEMENT (self);
if (_CLOCK_TIME_IS_LESS (duration, element->duration)) {
if (GES_CLOCK_TIME_IS_LESS (duration, element->duration)) {
/* NOTE: timeline would normally not be NULL at this point */
if (timeline
&& !timeline_tree_can_move_element (timeline_get_tree (timeline),
element, ges_timeline_element_get_layer_priority (element),
element->start, duration)) {
element->start, duration, error)) {
return FALSE;
}
}
@ -446,7 +442,7 @@ _get_priority_range (GESContainer * container, guint32 * min_priority,
gboolean
ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child,
guint32 priority)
guint32 priority, GError ** error)
{
GList *child_data;
DurationLimitData *data;
@ -459,7 +455,7 @@ ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child,
child_data = _duration_limit_data_list_with_data (clip, data);
if (!_can_update_duration_limit (clip, child_data)) {
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from "
"priority %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " because "
"the duration-limit cannot be adjusted", GES_ARGS (child),
@ -489,7 +485,8 @@ _child_priority_changed (GESContainer * container, GESTimelineElement * child)
****************************************************/
static gboolean
_can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint)
_can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint,
GError ** error)
{
GList *tmp;
GList *child_data = NULL;
@ -505,13 +502,18 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint)
_duration_limit_data_new (GES_TRACK_ELEMENT (child));
if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) {
if (GST_CLOCK_TIME_IS_VALID (child->maxduration)
&& child->maxduration < inpoint) {
if (GES_CLOCK_TIME_IS_LESS (child->maxduration, inpoint)) {
GST_INFO_OBJECT (clip, "Cannot set the in-point from %"
G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT " because it would "
GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because it would "
"cause the in-point of its core child %" GES_FORMAT
" to exceed its max-duration", _INPOINT (clip), inpoint,
GES_ARGS (child));
" to exceed its max-duration", GST_TIME_ARGS (_INPOINT (clip)),
GST_TIME_ARGS (inpoint), GES_ARGS (child));
g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
"Cannot set the in-point of \"%s\" to %" GST_TIME_FORMAT
" because it would exceed the max-duration of %" GST_TIME_FORMAT
" for the child \"%s\"", GES_TIMELINE_ELEMENT_NAME (clip),
GST_TIME_ARGS (inpoint), GST_TIME_ARGS (child->maxduration),
child->name);
_duration_limit_data_free (data);
g_list_free_full (child_data, _duration_limit_data_free);
@ -524,10 +526,10 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint)
child_data = g_list_prepend (child_data, data);
}
if (!_can_update_duration_limit (clip, child_data)) {
GST_INFO_OBJECT (clip, "Cannot set the in-point from %" G_GUINT64_FORMAT
" to %" G_GUINT64_FORMAT " because the duration-limit cannot be "
"adjusted", _INPOINT (clip), inpoint);
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot set the in-point from %" GST_TIME_FORMAT
" to %" GST_TIME_FORMAT " because the duration-limit cannot be "
"adjusted", GST_TIME_ARGS (_INPOINT (clip)), GST_TIME_ARGS (inpoint));
return FALSE;
}
@ -538,7 +540,7 @@ _can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint)
* its children have a max-duration below it */
gboolean
ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child,
GstClockTime inpoint)
GstClockTime inpoint, GError ** error)
{
/* don't bother checking if we are setting the value */
if (clip->priv->setting_inpoint)
@ -555,11 +557,11 @@ ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child,
child_data = _duration_limit_data_list_with_data (clip, data);
if (!_can_update_duration_limit (clip, child_data)) {
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot set the in-point of non-core child %"
GES_FORMAT " from %" G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT
GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
" because the duration-limit cannot be adjusted", GES_ARGS (child),
_INPOINT (child), inpoint);
GST_TIME_ARGS (_INPOINT (child)), GST_TIME_ARGS (inpoint));
return FALSE;
}
@ -568,7 +570,7 @@ ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child,
/* setting the in-point of a core child will shift the in-point of all
* core children with an internal source */
return _can_set_inpoint_of_core_children (clip, inpoint);
return _can_set_inpoint_of_core_children (clip, inpoint, error);
}
/* returns TRUE if duration-limit needs to be updated */
@ -622,7 +624,7 @@ _update_max_duration (GESContainer * container)
gboolean
ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child,
GstClockTime max_duration)
GstClockTime max_duration, GError ** error)
{
GList *child_data;
DurationLimitData *data;
@ -635,11 +637,11 @@ ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child,
child_data = _duration_limit_data_list_with_data (clip, data);
if (!_can_update_duration_limit (clip, child_data)) {
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot set the max-duration of child %"
GES_FORMAT " from %" G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT
GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
" because the duration-limit cannot be adjusted", GES_ARGS (child),
_MAXDURATION (child), max_duration);
GST_TIME_ARGS (_MAXDURATION (child)), GST_TIME_ARGS (max_duration));
return FALSE;
}
@ -685,7 +687,7 @@ _child_has_internal_source_changed (GESClip * self, GESTimelineElement * child)
gboolean
ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child,
gboolean active)
gboolean active, GError ** error)
{
GESTrack *track = ges_track_element_get_track (child);
gboolean is_core = _IS_CORE_CHILD (child);
@ -722,7 +724,7 @@ ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child,
}
}
if (!_can_update_duration_limit (clip, child_data)) {
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot set the active of child %" GES_FORMAT
" from %i to %i because the duration-limit cannot be adjusted",
GES_ARGS (child), ges_track_element_is_active (child), active);
@ -803,7 +805,7 @@ _track_contains_non_core (GESClip * clip, GESTrack * track)
gboolean
ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
GESTrack * track)
GESTrack * track, GError ** error)
{
GList *child_data;
DurationLimitData *data;
@ -816,12 +818,14 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
if (current_track == track)
return TRUE;
/* NOTE: we consider the following error cases programming errors by
* the user */
if (current_track) {
/* can not remove a core element from a track if a non-core one sits
* above it */
if (_IS_CORE_CHILD (child)
&& _track_contains_non_core (clip, current_track)) {
GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because it has non-core "
"siblings above it in its current track %" GST_PTR_FORMAT,
GES_ARGS (child), track, current_track);
@ -833,13 +837,13 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
const GESTimeline *track_timeline = ges_track_get_timeline (track);
if (track_timeline == NULL) {
GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT
GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because it is not part "
"of a timeline", GES_ARGS (child), track);
return FALSE;
}
if (track_timeline != clip_timeline) {
GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT
GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because its timeline %"
GST_PTR_FORMAT " does not match the clip's timeline %"
GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline,
@ -852,7 +856,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
* placed in a track that already has a core child */
if (_IS_CORE_CHILD (child)) {
if (core) {
GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
" to the track %" GST_PTR_FORMAT " because it contains a "
"core sibling %" GES_FORMAT, GES_ARGS (child), track,
GES_ARGS (core));
@ -860,7 +864,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
}
} else {
if (!core) {
GST_INFO_OBJECT (clip, "Cannot move the non-core child %"
GST_WARNING_OBJECT (clip, "Cannot move the non-core child %"
GES_FORMAT " to the track %" GST_PTR_FORMAT " because it "
" does not contain a core sibling", GES_ARGS (child), track);
return FALSE;
@ -881,7 +885,7 @@ ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
child_data = _duration_limit_data_list_with_data (clip, data);
if (!_can_update_duration_limit (clip, child_data)) {
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from "
"track %" GST_PTR_FORMAT " to track %" GST_PTR_FORMAT " because "
"the duration-limit cannot be adjusted", GES_ARGS (child),
@ -1034,7 +1038,7 @@ _set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint,
static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
{
if (!_can_set_inpoint_of_core_children (GES_CLIP (element), inpoint)) {
if (!_can_set_inpoint_of_core_children (GES_CLIP (element), inpoint, NULL)) {
GST_WARNING_OBJECT (element, "Cannot set the in-point to %"
GST_TIME_FORMAT, GST_TIME_ARGS (inpoint));
return FALSE;
@ -1097,10 +1101,11 @@ _set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
child_data = g_list_prepend (child_data, data);
}
if (!_can_update_duration_limit (self, child_data)) {
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot set the max-duration from %"
G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT " because the "
"duration-limit cannot be adjusted", element->maxduration, maxduration);
GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the "
"duration-limit cannot be adjusted",
GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (maxduration));
return FALSE;
}
@ -1357,7 +1362,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
child_data = _duration_limit_data_list_with_data (self, data);
if (!_can_update_duration_limit (self, child_data)) {
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot add core %" GES_FORMAT " as a "
"child because the duration-limit cannot be adjusted",
GES_ARGS (element));
@ -1418,7 +1423,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
data->priority++;
}
if (!_can_update_duration_limit (self, child_data)) {
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot add effect %" GES_FORMAT " as "
"a child because the duration-limit cannot be adjusted",
GES_ARGS (element));
@ -1502,7 +1507,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
g_list_prepend (child_data, _duration_limit_data_new (child));
}
if (!_can_update_duration_limit (self, child_data)) {
if (!_can_update_duration_limit (self, child_data, NULL)) {
GST_WARNING_OBJECT (self, "Cannot remove the child %" GES_FORMAT
" because the duration-limit cannot be adjusted", GES_ARGS (el));
return FALSE;
@ -2338,9 +2343,10 @@ ges_clip_is_moving_from_layer (GESClip * clip)
}
/**
* ges_clip_move_to_layer:
* ges_clip_move_to_layer_full:
* @clip: A #GESClip
* @layer: The new layer
* @error: (nullable): Return location for an error
*
* Moves a clip to a new layer. If the clip already exists in a layer, it
* is first removed from its current layer before being added to the new
@ -2349,7 +2355,7 @@ ges_clip_is_moving_from_layer (GESClip * clip)
* Returns: %TRUE if @clip was successfully moved to @layer.
*/
gboolean
ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
ges_clip_move_to_layer_full (GESClip * clip, GESLayer * layer, GError ** error)
{
gboolean ret = FALSE;
GESLayer *current_layer;
@ -2357,6 +2363,7 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
element = GES_TIMELINE_ELEMENT (clip);
current_layer = clip->priv->layer;
@ -2387,7 +2394,7 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
/* move to new layer, also checks moving of toplevel */
return timeline_tree_move (timeline_get_tree (layer->timeline),
element, (gint64) ges_layer_get_priority (current_layer) -
(gint64) ges_layer_get_priority (layer), 0, GES_EDGE_NONE, 0);
(gint64) ges_layer_get_priority (layer), 0, GES_EDGE_NONE, 0, error);
}
gst_object_ref (clip);
@ -2418,6 +2425,21 @@ done:
return ret && (clip->priv->layer == layer);
}
/**
* ges_clip_move_to_layer:
* @clip: A #GESClip
* @layer: The new layer
*
* See ges_clip_move_to_layer_full(), which also gives an error.
*
* Returns: %TRUE if @clip was successfully moved to @layer.
*/
gboolean
ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
{
return ges_clip_move_to_layer_full (clip, layer, NULL);
}
/**
* ges_clip_find_track_element:
* @clip: A #GESClip
@ -2603,10 +2625,11 @@ ges_clip_set_top_effect_priority (GESClip * clip,
}
/**
* ges_clip_set_top_effect_index:
* ges_clip_set_top_effect_index_full:
* @clip: A #GESClip
* @effect: An effect within @clip to move
* @newindex: The index for @effect in @clip
* @error: (nullable): Return location for an error
*
* Set the index of an effect within the clip. See
* ges_clip_get_top_effect_index(). The new index must be an existing
@ -2617,8 +2640,8 @@ ges_clip_set_top_effect_priority (GESClip * clip,
* Returns: %TRUE if @effect was successfully moved to @newindex.
*/
gboolean
ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
guint newindex)
ges_clip_set_top_effect_index_full (GESClip * clip, GESBaseEffect * effect,
guint newindex, GError ** error)
{
gint inc;
GList *top_effects, *tmp;
@ -2628,6 +2651,7 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (!_is_added_effect (clip, effect))
return FALSE;
@ -2670,8 +2694,8 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
child_data = g_list_prepend (child_data, data);
}
if (!_can_update_duration_limit (clip, child_data)) {
GST_WARNING_OBJECT (clip, "Cannot move top effect %" GES_FORMAT
if (!_can_update_duration_limit (clip, child_data, error)) {
GST_INFO_OBJECT (clip, "Cannot move top effect %" GES_FORMAT
" to index %i because the duration-limit cannot adjust",
GES_ARGS (effect), newindex);
return FALSE;
@ -2707,9 +2731,28 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
}
/**
* ges_clip_split:
* ges_clip_set_top_effect_index:
* @clip: A #GESClip
* @effect: An effect within @clip to move
* @newindex: The index for @effect in @clip
*
* See ges_clip_set_top_effect_index_full(), which also gives an error.
*
* Returns: %TRUE if @effect was successfully moved to @newindex.
*/
gboolean
ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
guint newindex)
{
return ges_clip_set_top_effect_index_full (clip, effect, newindex, NULL);
}
/**
* ges_clip_split_full:
* @clip: The #GESClip to split
* @position: The timeline position at which to perform the split
* @position: The timeline position at which to perform the split, between
* the start and end of the clip
* @error: (nullable): Return location for an error
*
* Splits a clip at the given timeline position into two clips. The clip
* must already have a #GESClip:layer.
@ -2743,7 +2786,7 @@ ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
* from the splitting @clip, or %NULL if @clip can't be split.
*/
GESClip *
ges_clip_split (GESClip * clip, guint64 position)
ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
{
GList *tmp, *transitions = NULL;
GESClip *new_object;
@ -2752,10 +2795,12 @@ ges_clip_split (GESClip * clip, guint64 position)
GESTimelineElement *element;
GESTimeline *timeline;
GHashTable *track_for_copy;
guint32 layer_prio;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (clip->priv->layer, NULL);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL);
g_return_val_if_fail (!error || !*error, NULL);
element = GES_TIMELINE_ELEMENT (clip);
timeline = element->timeline;
@ -2770,26 +2815,26 @@ ges_clip_split (GESClip * clip, guint64 position)
return NULL;
}
layer_prio = ges_timeline_element_get_layer_priority (element);
old_duration = position - start;
if (timeline && !timeline_tree_can_move_element (timeline_get_tree
(timeline), element,
ges_timeline_element_get_layer_priority (element),
start, old_duration)) {
GST_WARNING_OBJECT (clip,
if (timeline
&& !timeline_tree_can_move_element (timeline_get_tree (timeline), element,
layer_prio, start, old_duration, error)) {
GST_INFO_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would be in an illegal" " state.", GES_ARGS (clip),
" as timeline would be in an illegal state.", GES_ARGS (clip),
GST_TIME_ARGS (position));
return NULL;
}
new_duration = duration + start - position;
if (timeline && !timeline_tree_can_move_element (timeline_get_tree
(timeline), element,
ges_timeline_element_get_layer_priority (element),
position, new_duration)) {
GST_WARNING_OBJECT (clip,
if (timeline
&& !timeline_tree_can_move_element (timeline_get_tree (timeline), element,
layer_prio, position, new_duration, error)) {
GST_INFO_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would end up in an illegal" " state.", GES_ARGS (clip),
" as timeline would be in an illegal state.", GES_ARGS (clip),
GST_TIME_ARGS (position));
return NULL;
}
@ -2879,6 +2924,22 @@ ges_clip_split (GESClip * clip, guint64 position)
return new_object;
}
/**
* ges_clip_split:
* @clip: The #GESClip to split
* @position: The timeline position at which to perform the split
*
* See ges_clip_split_full(), which also gives an error.
*
* Returns: (transfer none) (nullable): The newly created clip resulting
* from the splitting @clip, or %NULL if @clip can't be split.
*/
GESClip *
ges_clip_split (GESClip * clip, guint64 position)
{
return ges_clip_split_full (clip, position, NULL);
}
/**
* ges_clip_set_supported_formats:
* @clip: A #GESClip
@ -3073,7 +3134,7 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
* @clip: A #GESClip
* @child: A child of @clip
* @track: The track to add @child to
* @err: Return location for an error
* @error: (nullable): Return location for an error
*
* Adds the track element child of the clip to a specific track.
*
@ -3098,14 +3159,12 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
* such as causing three sources to overlap at a single time, or causing
* a source to completely overlap another in the same track.
*
* Note that @err will not always be set upon failure.
*
* Returns: (transfer none): The element that was added to @track, either
* @child or a copy of child, or %NULL if the element could not be added.
*/
GESTrackElement *
ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
GESTrack * track, GError ** err)
GESTrack * track, GError ** error)
{
GESTimeline *timeline;
GESTrackElement *el;
@ -3114,7 +3173,7 @@ ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL);
g_return_val_if_fail (GES_IS_TRACK (track), NULL);
g_return_val_if_fail (!err || !*err, NULL);
g_return_val_if_fail (!error || !*error, NULL);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
@ -3164,11 +3223,8 @@ ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
el = child;
}
/* FIXME: set error if can not be added to track:
* Either breaks the track rules for the clip, or the timeline
* configuration rules */
if (!ges_track_add_element (track, el)) {
GST_WARNING_OBJECT (clip, "Could not add the track element %"
if (!ges_track_add_element_full (track, el, error)) {
GST_INFO_OBJECT (clip, "Could not add the track element %"
GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track);
if (el != child)
ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el));

View file

@ -145,48 +145,75 @@ struct _GESClipClass
GES_API
GESTrackType ges_clip_get_supported_formats (GESClip *clip);
GES_API
void ges_clip_set_supported_formats (GESClip *clip, GESTrackType supportedformats);
void ges_clip_set_supported_formats (GESClip *clip,
GESTrackType supportedformats);
GES_API
GESTrackElement* ges_clip_add_asset (GESClip *clip, GESAsset *asset);
GESTrackElement* ges_clip_add_asset (GESClip *clip,
GESAsset *asset);
GES_API
GESTrackElement* ges_clip_find_track_element (GESClip *clip, GESTrack *track,
GESTrackElement* ges_clip_find_track_element (GESClip *clip,
GESTrack *track,
GType type);
GES_API
GList * ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
GESTrackType track_type, GType type);
GList * ges_clip_find_track_elements (GESClip * clip,
GESTrack * track,
GESTrackType track_type,
GType type);
GES_API
GESTrackElement * ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child, GESTrack * track, GError **err);
GESTrackElement * ges_clip_add_child_to_track (GESClip * clip,
GESTrackElement * child,
GESTrack * track,
GError ** error);
/****************************************************
* Layer *
****************************************************/
GES_API
GESLayer* ges_clip_get_layer (GESClip *clip);
GESLayer* ges_clip_get_layer (GESClip * clip);
GES_API
gboolean ges_clip_move_to_layer (GESClip *clip, GESLayer *layer);
gboolean ges_clip_move_to_layer (GESClip * clip,
GESLayer * layer);
GES_API
gboolean ges_clip_move_to_layer_full (GESClip * clip,
GESLayer * layer,
GError ** error);
/****************************************************
* Effects *
****************************************************/
GES_API
GList* ges_clip_get_top_effects (GESClip *clip);
GList* ges_clip_get_top_effects (GESClip * clip);
GES_API
gint ges_clip_get_top_effect_position (GESClip *clip, GESBaseEffect *effect);
gint ges_clip_get_top_effect_position (GESClip * clip,
GESBaseEffect * effect);
GES_API
gint ges_clip_get_top_effect_index (GESClip *clip, GESBaseEffect *effect);
gint ges_clip_get_top_effect_index (GESClip * clip,
GESBaseEffect * effect);
GES_API
gboolean ges_clip_set_top_effect_priority (GESClip *clip, GESBaseEffect *effect,
gboolean ges_clip_set_top_effect_priority (GESClip * clip,
GESBaseEffect * effect,
guint newpriority);
GES_API
gboolean ges_clip_set_top_effect_index (GESClip *clip, GESBaseEffect *effect,
gboolean ges_clip_set_top_effect_index (GESClip * clip,
GESBaseEffect * effect,
guint newindex);
GES_API
gboolean ges_clip_set_top_effect_index_full (GESClip * clip,
GESBaseEffect * effect,
guint newindex,
GError ** error);
/****************************************************
* Editing *
****************************************************/
GES_API
GESClip* ges_clip_split (GESClip *clip, guint64 position);
GESClip* ges_clip_split (GESClip *clip,
guint64 position);
GES_API
GESClip* ges_clip_split_full (GESClip *clip,
guint64 position,
GError ** error);
GES_API
GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip,

View file

@ -38,6 +38,17 @@ G_BEGIN_DECLS
* @GES_ERROR_ASSET_WRONG_ID: The ID passed is malformed
* @GES_ERROR_ASSET_LOADING: An error happened while loading the asset
* @GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE: The formatted files was malformed
* @GES_ERROR_INVALID_FRAME_NUMBER: The frame number is invalid
* @GES_ERROR_NEGATIVE_LAYER: The operation would lead to a negative
* #GES_TIMELINE_ELEMENT_LAYER_PRIORITY
* @GES_ERROR_NEGATIVE_TIME: The operation would lead to a negative time.
* E.g. for the #GESTimelineElement:start #GESTimelineElement:duration or
* #GESTimelineElement:in-point.
* @GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT: Some #GESTimelineElement does
* not have a large enough #GESTimelineElement:max-duration to cover the
* desired operation
* @GES_ERROR_INVALID_OVERLAP_IN_TRACK: The operation would break one of
* the overlap conditions for the #GESTimeline
*/
typedef enum
{
@ -45,6 +56,10 @@ typedef enum
GES_ERROR_ASSET_LOADING,
GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE,
GES_ERROR_INVALID_FRAME_NUMBER,
GES_ERROR_NEGATIVE_LAYER,
GES_ERROR_NEGATIVE_TIME,
GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
GES_ERROR_INVALID_OVERLAP_IN_TRACK,
} GESError;
G_END_DECLS
G_END_DECLS

View file

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

View file

@ -61,6 +61,10 @@ GstDebugCategory * _ges_debug (void);
#define _set_duration0 ges_timeline_element_set_duration
#define _set_priority0 ges_timeline_element_set_priority
#define GES_CLOCK_TIME_IS_LESS(first, second) \
(GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \
|| (first) < (second)))
#define DEFAULT_FRAMERATE_N 30
#define DEFAULT_FRAMERATE_D 1
#define DEFAULT_WIDTH 1280
@ -110,8 +114,8 @@ G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline);
G_GNUC_INTERNAL gboolean
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position);
gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position, GError ** error);
G_GNUC_INTERNAL void
timeline_add_group (GESTimeline *timeline,
@ -152,7 +156,7 @@ G_GNUC_INTERNAL void
ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving);
G_GNUC_INTERNAL gboolean
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip);
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error);
G_GNUC_INTERNAL void
ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip);
@ -403,11 +407,11 @@ G_GNUC_INTERNAL gboolean ges_clip_is_moving_from_layer (GESClip *clip
G_GNUC_INTERNAL void ges_clip_set_moving_from_layer (GESClip *clip, gboolean is_moving);
G_GNUC_INTERNAL GESTrackElement* ges_clip_create_track_element (GESClip *clip, GESTrackType type);
G_GNUC_INTERNAL GList* ges_clip_create_track_elements (GESClip *clip, GESTrackType type);
G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, GstClockTime inpoint);
G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, GstClockTime max_duration);
G_GNUC_INTERNAL gboolean ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active);
G_GNUC_INTERNAL gboolean ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority);
G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack);
G_GNUC_INTERNAL gboolean ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, GstClockTime inpoint, GError ** error);
G_GNUC_INTERNAL gboolean ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, GstClockTime max_duration, GError ** error);
G_GNUC_INTERNAL gboolean ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active, GError ** error);
G_GNUC_INTERNAL gboolean ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority, GError ** error);
G_GNUC_INTERNAL gboolean ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack, GError ** error);
G_GNUC_INTERNAL void ges_clip_empty_from_track (GESClip * clip, GESTrack * track);
/****************************************************
@ -420,7 +424,7 @@ G_GNUC_INTERNAL void layer_set_priority (GESLayer * layer, guint p
* GESTrackElement *
****************************************************/
#define NLE_OBJECT_TRACK_ELEMENT_QUARK (g_quark_from_string ("nle_object_track_element_quark"))
G_GNUC_INTERNAL gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track);
G_GNUC_INTERNAL gboolean ges_track_element_set_track (GESTrackElement * object, GESTrack * track, GError ** error);
G_GNUC_INTERNAL void ges_track_element_copy_properties (GESTimelineElement * element,
GESTimelineElement * elementcopy);
G_GNUC_INTERNAL void ges_track_element_set_layer_active (GESTrackElement *element, gboolean active);

View file

@ -237,24 +237,8 @@ ges_layer_class_init (GESLayerClass * klass)
* GESLayer:auto-transition:
*
* Whether to automatically create a #GESTransitionClip whenever two
* #GESClip-s overlap in the layer. Specifically, if this is set to
* %TRUE, then wherever two #GESSource-s (that belong to some clip in
* the layer) share the same #GESTrackElement:track and the end time of
* one source exceeds the #GESTimelineElement:start time of the other,
* there will exist a corresponding #GESTransitionClip in the same
* layer. These automatic transitions will be created and removed
* accordingly. Their temporal extent will cover the overlap of the
* two sources (their #GESTimelineElement:start will be set to the
* #GESTimelineElement:start of the later source, and their
* #GESTimelineElement:duration will be the difference between the
* #GESTimelineElement:start of the later source, and the end time of
* the earlier source).
*
* It may be difficult to use this feature if your timeline has multiple
* tracks of the same #GESTrack:track-type and you use the
* #GESTimeline::select-tracks-for-object signal. You will have to
* ensure that any #GESTransition that belongs to a newly created
* transition clip is able to arrive in the correct track.
* #GESSource-s that both belong to a #GESClip in the layer overlap.
* See #GESTimeline for what counts as an overlap.
*
* When a layer is added to a #GESTimeline, if this property is left as
* %FALSE, but the timeline's #GESTimeline:auto-transition is %TRUE, it
@ -667,9 +651,10 @@ ges_layer_is_empty (GESLayer * layer)
}
/**
* ges_layer_add_clip:
* ges_layer_add_clip_full:
* @layer: The #GESLayer
* @clip: (transfer floating): The clip to add
* @error: (nullable): Return location for an error
*
* Adds the given clip to the layer. If the method succeeds, the layer
* will take ownership of the clip.
@ -682,17 +667,19 @@ ges_layer_is_empty (GESLayer * layer)
* if @layer refused to add @clip.
*/
gboolean
ges_layer_add_clip (GESLayer * layer, GESClip * clip)
ges_layer_add_clip_full (GESLayer * layer, GESClip * clip, GError ** error)
{
GList *tmp, *prev_children;
GList *tmp, *prev_children, *new_children;
GESAsset *asset;
GESLayerPrivate *priv;
GESLayer *current_layer;
GESTimeline *timeline;
GESContainer *container;
GError *timeline_error = NULL;
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
container = GES_CONTAINER (clip);
@ -786,9 +773,23 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
prev_children = ges_container_get_children (container, FALSE);
if (timeline && !ges_timeline_add_clip (timeline, clip)) {
if (timeline && !ges_timeline_add_clip (timeline, clip, &timeline_error)) {
GST_INFO_OBJECT (layer, "Could not add the clip %" GES_FORMAT
" to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), timeline);
if (timeline_error) {
if (error) {
*error = timeline_error;
} else {
GST_WARNING_OBJECT (timeline, "Adding the clip %" GES_FORMAT
" to the timeline failed: %s", GES_ARGS (clip),
timeline_error->message);
g_error_free (timeline_error);
}
}
/* remove any track elements that were newly created */
GList *new_children = ges_container_get_children (container, FALSE);
new_children = ges_container_get_children (container, FALSE);
for (tmp = new_children; tmp; tmp = tmp->next) {
if (!g_list_find (prev_children, tmp->data))
ges_container_remove (container, tmp->data);
@ -796,8 +797,6 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
g_list_free_full (prev_children, gst_object_unref);
g_list_free_full (new_children, gst_object_unref);
GST_WARNING_OBJECT (layer, "Could not add the clip %" GES_FORMAT
" to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), timeline);
/* FIXME: change emit signal to FALSE once we are able to delay the
* "clip-added" signal until after ges_timeline_add_clip */
ges_layer_remove_clip_internal (layer, clip, TRUE);
@ -818,7 +817,23 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
}
/**
* ges_layer_add_asset:
* ges_layer_add_clip:
* @layer: The #GESLayer
* @clip: (transfer floating): The clip to add
*
* See ges_layer_add_clip_full(), which also gives an error.
*
* Returns: %TRUE if @clip was properly added to @layer, or %FALSE
* if @layer refused to add @clip.
*/
gboolean
ges_layer_add_clip (GESLayer * layer, GESClip * clip)
{
return ges_layer_add_clip_full (layer, clip, NULL);
}
/**
* ges_layer_add_asset_full:
* @layer: The #GESLayer
* @asset: The asset to extract the new clip from
* @start: The #GESTimelineElement:start value to set on the new clip
@ -830,6 +845,7 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
* clip
* @track_types: The #GESClip:supported-formats to set on the the new
* clip, or #GES_TRACK_TYPE_UNKNOWN to use the default
* @error: (nullable): Return location for an error
*
* Extracts a new clip from an asset and adds it to the layer with
* the given properties.
@ -837,14 +853,15 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
* Returns: (transfer none): The newly created clip.
*/
GESClip *
ges_layer_add_asset (GESLayer * layer,
ges_layer_add_asset_full (GESLayer * layer,
GESAsset * asset, GstClockTime start, GstClockTime inpoint,
GstClockTime duration, GESTrackType track_types)
GstClockTime duration, GESTrackType track_types, GError ** error)
{
GESClip *clip;
g_return_val_if_fail (GES_IS_LAYER (layer), NULL);
g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
g_return_val_if_fail (!error || !*error, NULL);
g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type
(asset), GES_TYPE_CLIP), NULL);
@ -873,13 +890,40 @@ ges_layer_add_asset (GESLayer * layer,
_set_duration0 (GES_TIMELINE_ELEMENT (clip), duration);
}
if (!ges_layer_add_clip (layer, clip)) {
if (!ges_layer_add_clip_full (layer, clip, error)) {
return NULL;
}
return clip;
}
/**
* ges_layer_add_asset:
* @layer: The #GESLayer
* @asset: The asset to extract the new clip from
* @start: The #GESTimelineElement:start value to set on the new clip
* If `start == #GST_CLOCK_TIME_NONE`, it will be added to the end
* of @layer, i.e. it will be set to @layer's duration
* @inpoint: The #GESTimelineElement:in-point value to set on the new
* clip
* @duration: The #GESTimelineElement:duration value to set on the new
* clip
* @track_types: The #GESClip:supported-formats to set on the the new
* clip, or #GES_TRACK_TYPE_UNKNOWN to use the default
*
* See ges_layer_add_asset_full(), which also gives an error.
*
* Returns: (transfer none): The newly created clip.
*/
GESClip *
ges_layer_add_asset (GESLayer * layer,
GESAsset * asset, GstClockTime start, GstClockTime inpoint,
GstClockTime duration, GESTrackType track_types)
{
return ges_layer_add_asset_full (layer, asset, start, inpoint, duration,
track_types, NULL);
}
/**
* ges_layer_new:
*

View file

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

View file

@ -48,12 +48,9 @@
* similar changes in neighbouring or later elements in the timeline.
*
* However, a timeline may refuse a change in these properties if they
* would place the timeline in an unsupported configuration. For example,
* it is not possible for three #GESSourceClip-s in the same layer and
* with the same track types to overlap at any given position in the
* timeline (only two may overlap, which corresponds to a single
* #GESTransition). Similarly, a #GESSourceClip may not entirely cover
* another #GESSourceClip in the same layer and with the same track types.
* would place the timeline in an unsupported configuration. See
* #GESTimeline for its overlap rules.
*
* Additionally, an edit may be refused if it would place one of the
* timing properties out of bounds (such as a negative time value for
* #GESTimelineElement:start, or having insufficient internal
@ -1153,8 +1150,7 @@ ges_timeline_element_set_inpoint (GESTimelineElement * self,
if (G_UNLIKELY (inpoint == self->inpoint))
return TRUE;
if (GST_CLOCK_TIME_IS_VALID (self->maxduration)
&& inpoint > self->maxduration) {
if (GES_CLOCK_TIME_IS_LESS (self->maxduration, inpoint)) {
GST_WARNING_OBJECT (self, "Can not set an in-point of %" GST_TIME_FORMAT
" because it exceeds the element's max-duration: %" GST_TIME_FORMAT,
GST_TIME_ARGS (inpoint), GST_TIME_ARGS (self->maxduration));
@ -1210,7 +1206,7 @@ ges_timeline_element_set_max_duration (GESTimelineElement * self,
if (G_UNLIKELY (maxduration == self->maxduration))
return TRUE;
if (GST_CLOCK_TIME_IS_VALID (maxduration) && self->inpoint > maxduration) {
if (GES_CLOCK_TIME_IS_LESS (maxduration, self->inpoint)) {
GST_WARNING_OBJECT (self, "Can not set a max-duration of %"
GST_TIME_FORMAT " because it lies below the element's in-point: %"
GST_TIME_FORMAT, GST_TIME_ARGS (maxduration),
@ -2328,17 +2324,15 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self)
}
/**
* ges_timeline_element_edit:
* ges_timeline_element_edit_full:
* @self: The #GESTimelineElement to edit
* @layers: (element-type GESLayer) (nullable): A whitelist of layers
* where the edit can be performed, %NULL allows all layers in the
* timeline
* @new_layer_priority: The priority/index of the layer @self should be
* moved to. -1 means no move
* @mode: The edit mode
* @edge: The edge of @self where the edit should occur
* @position: The edit position: a new location for the edge of @self
* (in nanoseconds)
* @error: (nullable): Return location for an error
*
* Edits the element within its timeline by adjusting its
* #GESTimelineElement:start, #GESTimelineElement:duration or
@ -2363,28 +2357,20 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self)
* the corresponding layer priority/index does not yet exist for the
* timeline.
*
* @layers can be used as a whitelist to limit changes to elements that
* exist in the corresponding layers. If you intend to also switch
* elements between layers, then you must ensure that all the involved
* layers are included for the switch to succeed (with the exception of
* layers may be newly created).
*
* Returns: %TRUE if the edit of @self completed, %FALSE on failure.
*
* Since: 1.18
*/
/* FIXME: handle the layers argument. Currently we always treat it as if
* it is NULL in the ges-timeline code */
gboolean
ges_timeline_element_edit (GESTimelineElement * self, GList * layers,
gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
ges_timeline_element_edit_full (GESTimelineElement * self,
gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position,
GError ** error)
{
GESTimeline *timeline;
guint32 layer_prio;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
g_return_val_if_fail (timeline, FALSE);
@ -2399,8 +2385,41 @@ ges_timeline_element_edit (GESTimelineElement * self, GList * layers,
self->name, ges_edge_name (edge), GST_TIME_ARGS (position),
ges_edit_mode_name (mode), new_layer_priority);
return ges_timeline_edit (timeline, self, layers, new_layer_priority, mode,
edge, position);
return ges_timeline_edit (timeline, self, new_layer_priority, mode,
edge, position, error);
}
/**
* ges_timeline_element_edit:
* @self: The #GESTimelineElement to edit
* @layers: (element-type GESLayer) (nullable): A whitelist of layers
* where the edit can be performed, %NULL allows all layers in the
* timeline.
* @new_layer_priority: The priority/index of the layer @self should be
* moved to. -1 means no move
* @mode: The edit mode
* @edge: The edge of @self where the edit should occur
* @position: The edit position: a new location for the edge of @self
* (in nanoseconds) in the timeline coordinates
*
* See ges_timeline_element_edit_full(), which also gives an error.
*
* Note that the @layers argument is currently ignored, so you should
* just pass %NULL.
*
* Returns: %TRUE if the edit of @self completed, %FALSE on failure.
*
* Since: 1.18
*/
/* FIXME: handle the layers argument. Currently we always treat it as if
* it is NULL in the ges-timeline code */
gboolean
ges_timeline_element_edit (GESTimelineElement * self, GList * layers,
gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
{
return ges_timeline_element_edit_full (self, new_layer_priority, mode, edge,
position, NULL);
}
/**

View file

@ -401,4 +401,12 @@ gboolean ges_timeline_element_edit (GESTimeli
GESEditMode mode,
GESEdge edge,
guint64 position);
GES_API
gboolean ges_timeline_element_edit_full (GESTimelineElement * self,
gint64 new_layer_priority,
GESEditMode mode,
GESEdge edge,
guint64 position,
GError ** error);
G_END_DECLS

View file

@ -79,6 +79,8 @@ struct _TreeIterationData
{
GNode *root;
gboolean res;
/* an error to set */
GError **error;
/* The element we are visiting */
GESTimelineElement *element;
@ -299,7 +301,7 @@ _clock_time_plus (GstClockTime time, GstClockTime add)
return GST_CLOCK_TIME_NONE;
if (time >= (G_MAXUINT64 - add)) {
GST_INFO ("The time %" G_GUINT64_FORMAT " would overflow when "
GST_ERROR ("The time %" G_GUINT64_FORMAT " would overflow when "
"adding %" G_GUINT64_FORMAT, time, add);
return GST_CLOCK_TIME_NONE;
}
@ -355,7 +357,7 @@ _abs_clock_time_distance (GstClockTime time1, GstClockTime time2)
return time2 - time1;
}
static gboolean
static void
get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start,
GstClockTime * end, gboolean * negative_end)
@ -388,22 +390,6 @@ get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
*start = new_start;
if (end)
*end = new_end;
if (start && !GST_CLOCK_TIME_IS_VALID (new_start)) {
GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT " with "
"offset %" G_GINT64_FORMAT " because it would result in an invalid "
"start", GES_ARGS (element), offset);
return FALSE;
}
if (end && !GST_CLOCK_TIME_IS_VALID (new_end)) {
GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT " with "
"offset %" G_GINT64_FORMAT " because it would result in an invalid "
"end", GES_ARGS (element), offset);
return FALSE;
}
return TRUE;
}
/****************************************************
@ -571,9 +557,22 @@ timeline_tree_snap (GNode * root, GESTimelineElement * element,
/* Allow negative start/end positions in case a snap makes them valid!
* But we can still only snap to an existing edge in the timeline,
* which should be a valid time */
if (!get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode,
*offset, &start, &negative_start, &end, &negative_end))
get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, *offset,
&start, &negative_start, &end, &negative_end);
if (!GST_CLOCK_TIME_IS_VALID (start)) {
GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in "
"an invalid start", GES_ARGS (element), *offset);
goto done;
}
if (!GST_CLOCK_TIME_IS_VALID (end)) {
GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in "
"an invalid end", GES_ARGS (element), *offset);
goto done;
}
switch (mode) {
case EDIT_MOVE:
@ -619,6 +618,41 @@ done:
* Check Overlaps *
****************************************************/
#define _SOURCE_FORMAT "\"%s\"%s%s%s"
#define _SOURCE_ARGS(element) \
element->name, element->parent ? " (parent: \"" : "", \
element->parent ? element->parent->name : "", \
element->parent ? "\")" : ""
static void
set_full_overlap_error (GError ** error, GESTimelineElement * super,
GESTimelineElement * sub, GESTrack * track)
{
if (error) {
gchar *track_name = gst_object_get_name (GST_OBJECT (track));
g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK,
"The source " _SOURCE_FORMAT " would totally overlap the "
"source " _SOURCE_FORMAT " in the track \"%s\"", _SOURCE_ARGS (super),
_SOURCE_ARGS (sub), track_name);
g_free (track_name);
}
}
static void
set_triple_overlap_error (GError ** error, GESTimelineElement * first,
GESTimelineElement * second, GESTimelineElement * third, GESTrack * track)
{
if (error) {
gchar *track_name = gst_object_get_name (GST_OBJECT (track));
g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK,
"The sources " _SOURCE_FORMAT ", " _SOURCE_FORMAT " and "
_SOURCE_FORMAT " would all overlap at the same point in the "
"track \"%s\"", _SOURCE_ARGS (first), _SOURCE_ARGS (second),
_SOURCE_ARGS (third), track_name);
g_free (track_name);
}
}
#define _ELEMENT_FORMAT \
"%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \
"(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")"
@ -695,10 +729,19 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
return FALSE;
}
if ((cmp_start <= start && cmp_end >= end) ||
(cmp_start >= start && cmp_end <= end)) {
if (cmp_start <= start && cmp_end >= end) {
/* cmp fully overlaps e */
GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap",
_CMP_ARGS, _E_ARGS);
set_full_overlap_error (data->error, cmp, e, track);
goto error;
}
if (cmp_start >= start && cmp_end <= end) {
/* e fully overlaps cmp */
GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap",
_CMP_ARGS, _E_ARGS);
set_full_overlap_error (data->error, e, cmp, track);
goto error;
}
@ -710,6 +753,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
if (data->overlaping_on_start) {
GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start",
_CMP_ARGS, data->overlaping_on_start->name, e->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start,
track);
goto error;
}
if (GST_CLOCK_TIME_IS_VALID (data->overlap_end_first_time) &&
@ -717,6 +762,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
GST_INFO (_ELEMENT_FORMAT " overlaps %s on its start and %s on its "
"end, but they already overlap each other", _CMP_ARGS, e->name,
data->overlaping_on_end->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end,
track);
goto error;
}
/* record the time at which the overlapped ends */
@ -733,6 +780,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
if (data->overlaping_on_end) {
GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end",
_CMP_ARGS, data->overlaping_on_end->name, e->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end,
track);
goto error;
}
if (GST_CLOCK_TIME_IS_VALID (data->overlap_start_final_time) &&
@ -740,6 +789,8 @@ check_overlap_with_element (GNode * node, TreeIterationData * data)
GST_INFO (_ELEMENT_FORMAT " overlaps %s on its end and %s on its "
"start, but they already overlap each other", _CMP_ARGS, e->name,
data->overlaping_on_start->name);
set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start,
track);
goto error;
}
/* record the time at which the overlapped starts */
@ -789,12 +840,14 @@ check_moving_overlaps (GNode * node, TreeIterationData * data)
/* whether the elements in moving can be moved to their corresponding
* PositionData */
static gboolean
timeline_tree_can_move_elements (GNode * root, GHashTable * moving)
timeline_tree_can_move_elements (GNode * root, GHashTable * moving,
GError ** error)
{
TreeIterationData data = tree_iteration_data_init;
data.moving = moving;
data.root = root;
data.res = TRUE;
data.error = error;
/* sufficient to check the leaves, which is all the track elements or
* empty clips
* should also be sufficient to only check the moving elements */
@ -808,8 +861,56 @@ timeline_tree_can_move_elements (GNode * root, GHashTable * moving)
* Setting Edit Data *
****************************************************/
static void
set_negative_start_error (GError ** error, GESTimelineElement * element,
GstClockTime neg_start)
{
g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
"The element \"%s\" would have a negative start of -%"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_start));
}
static void
set_negative_duration_error (GError ** error, GESTimelineElement * element,
GstClockTime neg_duration)
{
g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
"The element \"%s\" would have a negative duration of -%"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_duration));
}
static void
set_negative_inpoint_error (GError ** error, GESTimelineElement * element,
GstClockTime neg_inpoint)
{
g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
"The element \"%s\" would have a negative in-point of -%"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_inpoint));
}
static void
set_negative_layer_error (GError ** error, GESTimelineElement * element,
gint64 neg_layer)
{
g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_LAYER,
"The element \"%s\" would have a negative layer priority of -%"
G_GINT64_FORMAT, element->name, neg_layer);
}
static void
set_breaks_duration_limit_error (GError ** error, GESClip * clip,
GstClockTime duration, GstClockTime duration_limit)
{
g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
"The clip \"%s\" would have a duration of %" GST_TIME_FORMAT
" that would break its duration-limit of %" GST_TIME_FORMAT,
GES_TIMELINE_ELEMENT_NAME (clip), GST_TIME_ARGS (duration),
GST_TIME_ARGS (duration_limit));
}
static gboolean
set_layer_priority (GESTimelineElement * element, EditData * data)
set_layer_priority (GESTimelineElement * element, EditData * data,
GError ** error)
{
gint64 layer_offset = data->layer_offset;
guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
@ -827,10 +928,12 @@ set_layer_priority (GESTimelineElement * element, EditData * data)
GST_INFO_OBJECT (element, "%s would have a negative layer priority (%"
G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name,
layer_prio, layer_offset);
set_negative_layer_error (error, element,
layer_offset - (gint64) layer_prio);
return FALSE;
}
if ((layer_prio - (gint64) layer_offset) >= G_MAXUINT32) {
GST_INFO_OBJECT (element, "%s would have an overflowing layer priority",
GST_ERROR_OBJECT (element, "%s would have an overflowing layer priority",
element->name);
return FALSE;
}
@ -851,34 +954,45 @@ set_layer_priority (GESTimelineElement * element, EditData * data)
}
static gboolean
set_edit_move_values (GESTimelineElement * element, EditData * data)
set_edit_move_values (GESTimelineElement * element, EditData * data,
GError ** error)
{
gboolean negative = FALSE;
GstClockTime new_start =
_clock_time_minus_diff (element->start, data->offset, NULL);
if (!GST_CLOCK_TIME_IS_VALID (new_start)) {
_clock_time_minus_diff (element->start, data->offset, &negative);
if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) {
GST_INFO_OBJECT (element, "Cannot move %" GES_FORMAT " with offset %"
G_GINT64_FORMAT " because it would result in an invalid start",
GES_ARGS (element), data->offset);
if (negative)
set_negative_start_error (error, element, new_start);
return FALSE;
}
_CHECK_END (element, new_start, element->duration);
data->start = new_start;
if (GES_IS_GROUP (element))
return TRUE;
GST_INFO_OBJECT (element, "%s will move by setting start to %"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start));
return set_layer_priority (element, data);
return set_layer_priority (element, data, error);
}
static gboolean
set_edit_trim_start_inpoint_value (GESTimelineElement * element,
EditData * data)
EditData * data, GError ** error)
{
gboolean negative = FALSE;
GstClockTime new_inpoint = _clock_time_minus_diff (element->inpoint,
data->offset, NULL);
if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) {
data->offset, &negative);
if (negative || !GST_CLOCK_TIME_IS_VALID (new_inpoint)) {
GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid in-point", GES_ARGS (element), data->offset);
if (negative)
set_negative_inpoint_error (error, element, new_inpoint);
return FALSE;
}
data->inpoint = new_inpoint;
@ -887,7 +1001,7 @@ set_edit_trim_start_inpoint_value (GESTimelineElement * element,
static gboolean
set_edit_trim_start_non_core_children (GESTimelineElement * clip,
GstClockTimeDiff offset, GHashTable * edit_table)
GstClockTimeDiff offset, GHashTable * edit_table, GError ** error)
{
GList *tmp;
GESTimelineElement *child;
@ -916,7 +1030,7 @@ set_edit_trim_start_non_core_children (GESTimelineElement * clip,
data = new_edit_data (EDIT_TRIM_START, offset, 0);
g_hash_table_insert (edit_table, child, data);
if (!set_edit_trim_start_inpoint_value (child, data))
if (!set_edit_trim_start_inpoint_value (child, data, error))
return FALSE;
}
}
@ -926,40 +1040,52 @@ set_edit_trim_start_non_core_children (GESTimelineElement * clip,
/* trim the start of a clip or a track element */
static gboolean
set_edit_trim_start_values (GESTimelineElement * element, EditData * data,
GHashTable * edit_table)
GHashTable * edit_table, GError ** error)
{
gboolean negative = FALSE;
GstClockTime new_duration;
GstClockTime new_start =
_clock_time_minus_diff (element->start, data->offset, NULL);
GstClockTime new_duration =
_clock_time_minus_diff (element->duration, -data->offset, NULL);
_clock_time_minus_diff (element->start, data->offset, &negative);
if (!GST_CLOCK_TIME_IS_VALID (new_start)) {
if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) {
GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid start", GES_ARGS (element), data->offset);
if (negative)
set_negative_start_error (error, element, new_start);
return FALSE;
}
if (!GST_CLOCK_TIME_IS_VALID (new_duration)) {
new_duration =
_clock_time_minus_diff (element->duration, -data->offset, &negative);
if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) {
GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid duration", GES_ARGS (element), data->offset);
if (negative)
set_negative_duration_error (error, element, new_duration);
return FALSE;
}
_CHECK_END (element, new_start, new_duration);
data->start = new_start;
data->duration = new_duration;
if (GES_IS_GROUP (element))
return TRUE;
if (GES_IS_CLIP (element)) {
if (!set_edit_trim_start_inpoint_value (element, data))
if (!set_edit_trim_start_inpoint_value (element, data, error))
return FALSE;
if (!set_edit_trim_start_non_core_children (element, data->offset,
edit_table))
edit_table, error))
return FALSE;
} else if (GES_IS_TRACK_ELEMENT (element)
&& ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) {
if (!set_edit_trim_start_inpoint_value (element, data))
if (!set_edit_trim_start_inpoint_value (element, data, error))
return FALSE;
}
data->start = new_start;
data->duration = new_duration;
/* NOTE: without time effects, the duration-limit will increase with
* a decrease in in-point by the same amount that duration increases,
@ -971,53 +1097,64 @@ set_edit_trim_start_values (GESTimelineElement * element, EditData * data,
"to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start),
GST_TIME_ARGS (data->inpoint), GST_TIME_ARGS (data->duration));
return set_layer_priority (element, data);
return set_layer_priority (element, data, error);
}
/* trim the end of a clip or a track element */
static gboolean
set_edit_trim_end_values (GESTimelineElement * element, EditData * data)
set_edit_trim_end_values (GESTimelineElement * element, EditData * data,
GError ** error)
{
gboolean negative = FALSE;
GstClockTime new_duration =
_clock_time_minus_diff (element->duration, data->offset, NULL);
if (!GST_CLOCK_TIME_IS_VALID (new_duration)) {
_clock_time_minus_diff (element->duration, data->offset, &negative);
if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) {
GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because it would result in an "
"invalid duration", GES_ARGS (element), data->offset);
if (negative)
set_negative_duration_error (error, element, new_duration);
return FALSE;
}
_CHECK_END (element, element->start, new_duration);
if (GES_IS_CLIP (element)) {
GstClockTime limit = ges_clip_get_duration_limit (GES_CLIP (element));
if (GST_CLOCK_TIME_IS_VALID (limit) && new_duration > limit) {
GESClip *clip = GES_CLIP (element);
GstClockTime limit = ges_clip_get_duration_limit (clip);
if (GES_CLOCK_TIME_IS_LESS (limit, new_duration)) {
GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
" with offset %" G_GINT64_FORMAT " because the duration would "
"exceed the clip's duration-limit %" G_GINT64_FORMAT,
GES_ARGS (element), data->offset, limit);
set_breaks_duration_limit_error (error, clip, new_duration, limit);
return FALSE;
}
}
data->duration = new_duration;
if (GES_IS_GROUP (element))
return TRUE;
GST_INFO_OBJECT (element, "%s will trim end by setting duration to %"
GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->duration));
return set_layer_priority (element, data);
return set_layer_priority (element, data, error);
}
/* handles clips and track elements with no parents */
static gboolean
set_clip_edit_values (GESTimelineElement * element, EditData * data,
GHashTable * edit_table)
set_edit_values (GESTimelineElement * element, EditData * data,
GHashTable * edit_table, GError ** error)
{
switch (data->mode) {
case EDIT_MOVE:
return set_edit_move_values (element, data);
return set_edit_move_values (element, data, error);
case EDIT_TRIM_START:
return set_edit_trim_start_values (element, data, edit_table);
return set_edit_trim_start_values (element, data, edit_table, error);
case EDIT_TRIM_END:
return set_edit_trim_end_values (element, data);
return set_edit_trim_end_values (element, data, error);
}
return FALSE;
}
@ -1041,7 +1178,7 @@ add_clips_to_list (GNode * node, GList ** list)
static gboolean
replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
GHashTable * edit_table)
GHashTable * edit_table, GError ** err)
{
gboolean ret = TRUE;
GList *tmp, *clips = NULL;
@ -1064,13 +1201,26 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
goto error;
}
group_edit->start = group->start;
group_edit->duration = group->duration;
/* should only set the start and duration fields, table should not be
* needed, so we pass NULL */
if (!set_edit_values (group, group_edit, NULL, err))
goto error;
new_start = group_edit->start;
new_end = _clock_time_plus (group_edit->start, group_edit->duration);
if (!GST_CLOCK_TIME_IS_VALID (new_start)
|| !GST_CLOCK_TIME_IS_VALID (new_end)) {
GST_ERROR_OBJECT (group, "Edit data gave an invalid start or end");
goto error;
}
layer_offset = group_edit->layer_offset;
mode = group_edit->mode;
if (!get_start_end_from_offset (group, mode, group_edit->offset,
&new_start, NULL, &new_end, NULL))
goto error;
/* can traverse leaves to find all the clips since they are at _most_
* one step above the track elements */
g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
@ -1148,7 +1298,7 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
}
clip_data = new_edit_data (clip_mode, offset, layer_offset);
g_hash_table_insert (edit_table, clip, clip_data);
if (!set_clip_edit_values (clip, clip_data, edit_table))
if (!set_edit_values (clip, clip_data, edit_table, err))
goto error;
}
}
@ -1165,7 +1315,8 @@ error:
/* set the edit values for the entries in @edits
* any groups in @edits will be replaced by their clip children */
static gboolean
timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits)
timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits,
GError ** err)
{
gboolean ret = TRUE;
GESTimelineElement *element;
@ -1183,9 +1334,9 @@ timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits)
goto error;
}
if (GES_IS_GROUP (element))
res = replace_group_with_clip_edits (root, element, edits);
res = replace_group_with_clip_edits (root, element, edits, err);
else
res = set_clip_edit_values (element, edit_data, edits);
res = set_edit_values (element, edit_data, edits, err);
if (!res)
goto error;
}
@ -1368,7 +1519,7 @@ add_element_edit (GHashTable * edits, GESTimelineElement * element,
gboolean
timeline_tree_can_move_element (GNode * root,
GESTimelineElement * element, guint32 priority, GstClockTime start,
GstClockTime duration)
GstClockTime duration, GError ** error)
{
gboolean ret = FALSE;
guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
@ -1431,10 +1582,10 @@ timeline_tree_can_move_element (GNode * root,
/* assume both edits can be performed if each could occur individually */
/* should not effect duration or in-point */
if (!timeline_tree_set_element_edit_values (root, move_edits))
if (!timeline_tree_set_element_edit_values (root, move_edits, error))
goto done;
/* should not effect start or in-point or layer */
if (!timeline_tree_set_element_edit_values (root, trim_edits))
if (!timeline_tree_set_element_edit_values (root, trim_edits, error))
goto done;
/* merge the two edits into moving positions */
@ -1483,7 +1634,7 @@ timeline_tree_can_move_element (GNode * root,
}
/* check overlaps */
if (!timeline_tree_can_move_elements (root, moving))
if (!timeline_tree_can_move_elements (root, moving, error))
goto done;
ret = TRUE;
@ -1624,7 +1775,7 @@ timeline_tree_perform_edits (GNode * root, GHashTable * edits)
gboolean
timeline_tree_ripple (GNode * root, GESTimelineElement * element,
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
GstClockTime snapping_distance)
GstClockTime snapping_distance, GError ** error)
{
gboolean res = TRUE;
GNode *node;
@ -1697,12 +1848,12 @@ timeline_tree_ripple (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */
give_edits_same_offset (edits, offset, layer_priority_offset);
if (!timeline_tree_set_element_edit_values (root, edits))
if (!timeline_tree_set_element_edit_values (root, edits, error))
goto error;
/* check overlaps */
set_moving_positions_from_edits (moving, edits);
if (!timeline_tree_can_move_elements (root, moving))
if (!timeline_tree_can_move_elements (root, moving, error))
goto error;
/* emit snapping now. Edits should only fail if a programming error
@ -1731,7 +1882,7 @@ error:
gboolean
timeline_tree_trim (GNode * root, GESTimelineElement * element,
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
GstClockTime snapping_distance)
GstClockTime snapping_distance, GError ** error)
{
gboolean res = TRUE;
GHashTable *edits = new_edit_table ();
@ -1778,12 +1929,12 @@ timeline_tree_trim (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */
give_edits_same_offset (edits, offset, layer_priority_offset);
if (!timeline_tree_set_element_edit_values (root, edits))
if (!timeline_tree_set_element_edit_values (root, edits, error))
goto error;
/* check overlaps */
set_moving_positions_from_edits (moving, edits);
if (!timeline_tree_can_move_elements (root, moving)) {
if (!timeline_tree_can_move_elements (root, moving, error)) {
goto error;
}
@ -1813,7 +1964,7 @@ error:
gboolean
timeline_tree_move (GNode * root, GESTimelineElement * element,
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
GstClockTime snapping_distance)
GstClockTime snapping_distance, GError ** error)
{
gboolean res = TRUE;
GHashTable *edits = new_edit_table ();
@ -1861,12 +2012,12 @@ timeline_tree_move (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */
give_edits_same_offset (edits, offset, layer_priority_offset);
if (!timeline_tree_set_element_edit_values (root, edits))
if (!timeline_tree_set_element_edit_values (root, edits, error))
goto error;
/* check overlaps */
set_moving_positions_from_edits (moving, edits);
if (!timeline_tree_can_move_elements (root, moving)) {
if (!timeline_tree_can_move_elements (root, moving, error)) {
goto error;
}
@ -1960,7 +2111,8 @@ find_sources_at_position (GNode * node, TreeIterationData * data)
gboolean
timeline_tree_roll (GNode * root, GESTimelineElement * element,
GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance)
GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance,
GError ** error)
{
gboolean res = TRUE;
GList *tmp;
@ -2043,12 +2195,12 @@ timeline_tree_roll (GNode * root, GESTimelineElement * element,
/* check and set edits using snapped values */
give_edits_same_offset (edits, offset, 0);
if (!timeline_tree_set_element_edit_values (root, edits))
if (!timeline_tree_set_element_edit_values (root, edits, error))
goto error;
/* check overlaps */
set_moving_positions_from_edits (moving, edits);
if (!timeline_tree_can_move_elements (root, moving)) {
if (!timeline_tree_can_move_elements (root, moving, error)) {
goto error;
}

View file

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

View file

@ -52,20 +52,74 @@
* content prioritised in the tracks. This ordering can be changed using
* ges_timeline_move_layer().
*
* ## Editing
*
* See #GESTimelineElement for the various ways the elements of a timeline
* can be edited.
*
* If you change the timing or ordering of a timeline's
* #GESTimelineElement-s, then these changes will not actually be taken
* into account in the output of the timeline's tracks until the
* ges_timeline_commit() method is called. This allows you to move its
* elements around, say, in response to an end user's mouse dragging, with
* little expense before finalising their effect on the produced data.
*
* ## Overlaps and Auto-Transitions
*
* There are certain restrictions placed on how #GESSource-s may overlap
* in a #GESTrack that belongs to a timeline. These will be enforced by
* GES, so the user will not need to keep track of them, but they should
* be aware that certain edits will be refused as a result if the overlap
* rules would be broken.
*
* Consider two #GESSource-s, `A` and `B`, with start times `startA` and
* `startB`, and end times `endA` and `endB`, respectively. The start
* time refers to their #GESTimelineElement:start, and the end time is
* their #GESTimelineElement:start + #GESTimelineElement:duration. These
* two sources *overlap* if:
*
* + they share the same #GESTrackElement:track (non %NULL), which belongs
* to the timeline;
* + they share the same #GES_TIMELINE_ELEMENT_LAYER_PRIORITY; and
* + `startA < endB` and `startB < endA `.
*
* Note that when `startA = endB` or `startB = endA` then the two sources
* will *touch* at their edges, but are not considered overlapping.
*
* If, in addition, `startA < startB < endA`, then we can say that the
* end of `A` overlaps the start of `B`.
*
* If, instead, `startA <= startB` and `endA >= endB`, then we can say
* that `A` fully overlaps `B`.
*
* The overlap rules for a timeline are that:
*
* 1. One source cannot fully overlap another source.
* 2. A source can only overlap the end of up to one other source at its
* start.
* 3. A source can only overlap the start of up to one other source at its
* end.
*
* The last two rules combined essentially mean that at any given timeline
* position, only up to two #GESSource-s may overlap at that position. So
* triple or more overlaps are not allowed.
*
* If you switch on #GESTimeline:auto-transition, then at any moment when
* the end of one source (the first source) overlaps the start of another
* (the second source), a #GESTransitionClip will be automatically created
* for the pair in the same layer and it will cover their overlap. If the
* two elements are edited in a way such that the end of the first source
* no longer overlaps the start of the second, the transition will be
* automatically removed from the timeline. However, if the two sources
* still overlap at the same edges after the edit, then the same
* transition object will be kept, but with its timing and layer adjusted
* accordingly.
*
* ## Saving
*
* To save/load a timeline, you can use the ges_timeline_load_from_uri()
* and ges_timeline_save_to_uri() methods that use the default format.
*
* ## Editing
*
* If you change the timing or ordering of a timeline's
* #GESTimelineElement-s, then these changes will not actually be taken
* into account in the timeline until the ges_timeline_commit() method is
* called. This allows you to move its elements around, say, in
* response to an end user's mouse dragging, with little expense before
* finalising their effect.
*
* ## Playing
*
* A timeline is a #GstBin with a source #GstPad for each of its
@ -152,7 +206,11 @@ struct _GESTimelinePrivate
/* While we are creating and adding the TrackElements for a clip, we need to
* ignore the child-added signal */
gboolean track_elements_moving;
gboolean track_selection_error;
/* whether any error occurred during track selection, including
* programming or usage errors */
gboolean has_any_track_selection_error;
/* error set for non-programming/usage errors */
GError *track_selection_error;
GList *groups;
guint stream_start_group_id;
@ -355,6 +413,8 @@ ges_timeline_dispose (GObject * object)
gst_clear_object (&priv->auto_transition_track);
gst_clear_object (&priv->new_track);
g_clear_error (&priv->track_selection_error);
priv->track_selection_error = NULL;
G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
}
@ -553,8 +613,10 @@ ges_timeline_class_init (GESTimelineClass * klass)
/**
* GESTimeline:auto-transition:
*
* Whether to automatically create a transition whenever two clips
* overlap in the timeline. See #GESLayer:auto-transition.
* Whether to automatically create a transition whenever two
* #GESSource-s overlap in a track of the timeline. See
* #GESLayer:auto-transition if you want this to only happen in some
* layers.
*/
g_object_class_install_property (object_class, PROP_AUTO_TRANSITION,
g_param_spec_boolean ("auto-transition", "Auto-Transition",
@ -1168,8 +1230,8 @@ ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze)
static gint
_edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
GstClockTime position)
gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
GstClockTime position, GError ** error)
{
GList *tmp;
guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
@ -1210,8 +1272,8 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place of "
"trimming the corresponding auto-transition", GES_ARGS (replace));
return ges_timeline_element_edit (replace, layers, -1, mode, edge,
position);
return ges_timeline_element_edit_full (replace, -1, mode, edge,
position, error);
}
}
@ -1220,8 +1282,8 @@ _edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
gboolean
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position)
gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position, GError ** error)
{
GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ?
GST_CLOCK_DIFF (position, element->start + element->duration) :
@ -1231,8 +1293,8 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
gint res = -1;
if ((GES_IS_TRANSITION (element) || GES_IS_TRANSITION_CLIP (element)))
res = _edit_auto_transition (timeline, element, layers, new_layer_priority,
mode, edge, position);
res = _edit_auto_transition (timeline, element, new_layer_priority, mode,
edge, position, error);
if (res != -1)
return res;
@ -1240,20 +1302,20 @@ ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
switch (mode) {
case GES_EDIT_MODE_RIPPLE:
return timeline_tree_ripple (timeline->priv->tree, element, prio_diff,
edge_diff, edge, timeline->priv->snapping_distance);
edge_diff, edge, timeline->priv->snapping_distance, error);
case GES_EDIT_MODE_TRIM:
return timeline_tree_trim (timeline->priv->tree, element, prio_diff,
edge_diff, edge, timeline->priv->snapping_distance);
edge_diff, edge, timeline->priv->snapping_distance, error);
case GES_EDIT_MODE_NORMAL:
return timeline_tree_move (timeline->priv->tree, element, prio_diff,
edge_diff, edge, timeline->priv->snapping_distance);
edge_diff, edge, timeline->priv->snapping_distance, error);
case GES_EDIT_MODE_ROLL:
if (prio_diff != 0) {
GST_WARNING_OBJECT (element, "Cannot roll an element to a new layer");
return FALSE;
}
return timeline_tree_roll (timeline->priv->tree, element,
edge_diff, edge, timeline->priv->snapping_distance);
edge_diff, edge, timeline->priv->snapping_distance, error);
case GES_EDIT_MODE_SLIDE:
GST_ERROR_OBJECT (element, "Sliding not implemented.");
return FALSE;
@ -1420,7 +1482,7 @@ _get_selected_tracks (GESTimeline * timeline, GESClip * clip,
* selected tracks */
static gboolean
_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element)
GESTrackElement * track_element, GError ** error)
{
guint i;
gboolean ret = TRUE;
@ -1428,8 +1490,11 @@ _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
for (i = 0; i < tracks->len; i++) {
GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
if (!ges_clip_add_child_to_track (clip, track_element, track, NULL))
if (!ges_clip_add_child_to_track (clip, track_element, track, error)) {
ret = FALSE;
if (error)
break;
}
}
g_ptr_array_unref (tracks);
@ -1439,7 +1504,7 @@ _add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
static gboolean
_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip,
GESTrackElement * track_element, GESTrack * track)
GESTrackElement * track_element, GESTrack * track, GError ** error)
{
gboolean no_error = TRUE;
GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
@ -1449,7 +1514,7 @@ _try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip,
* if it does we add the track element to the track, or add a copy if
* the track element is already in a track */
if (g_ptr_array_find (tracks, track, NULL)) {
if (!ges_clip_add_child_to_track (clip, track_element, track, NULL))
if (!ges_clip_add_child_to_track (clip, track_element, track, error))
no_error = FALSE;
}
@ -1469,21 +1534,46 @@ ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving)
}
static void
_set_track_selection_error (GESTimeline * timeline, gboolean error)
ges_timeline_set_track_selection_error (GESTimeline * timeline,
gboolean was_error, GError * error)
{
GESTimelinePrivate *priv;
LOCK_DYN (timeline);
timeline->priv->track_selection_error = error;
priv = timeline->priv;
g_clear_error (&priv->track_selection_error);
priv->track_selection_error = error;
priv->has_any_track_selection_error = was_error;
UNLOCK_DYN (timeline);
}
static gboolean
_get_track_selection_error (GESTimeline * timeline)
ges_timeline_take_track_selection_error (GESTimeline * timeline,
GError ** error)
{
gboolean ret;
GESTimelinePrivate *priv;
LOCK_DYN (timeline);
ret = timeline->priv->track_selection_error;
timeline->priv->track_selection_error = FALSE;
priv = timeline->priv;
if (error) {
if (*error) {
GST_ERROR_OBJECT (timeline, "Error not handled %s", (*error)->message);
g_error_free (*error);
}
*error = priv->track_selection_error;
} else if (priv->track_selection_error) {
GST_WARNING_OBJECT (timeline, "Got track selection error: %s",
priv->track_selection_error->message);
g_error_free (priv->track_selection_error);
}
priv->track_selection_error = NULL;
ret = priv->has_any_track_selection_error;
priv->has_any_track_selection_error = FALSE;
UNLOCK_DYN (timeline);
return ret;
@ -1494,7 +1584,8 @@ clip_track_element_added_cb (GESClip * clip,
GESTrackElement * track_element, GESTimeline * timeline)
{
GESTrack *auto_trans_track, *new_track;
gboolean error = FALSE;
GError *error = NULL;
gboolean success = FALSE;
if (timeline->priv->track_elements_moving) {
GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT
@ -1521,21 +1612,24 @@ clip_track_element_added_cb (GESClip * clip,
if (auto_trans_track) {
/* don't use track-selection */
if (!ges_clip_add_child_to_track (clip, track_element, auto_trans_track,
NULL))
error = TRUE;
success = ! !ges_clip_add_child_to_track (clip, track_element,
auto_trans_track, &error);
gst_object_unref (auto_trans_track);
} else {
if (new_track)
error =
!_try_add_track_element_to_track (timeline, clip, track_element,
new_track);
success = _try_add_track_element_to_track (timeline, clip, track_element,
new_track, &error);
else
error = !_add_track_element_to_tracks (timeline, clip, track_element);
success = _add_track_element_to_tracks (timeline, clip, track_element,
&error);
}
if (error)
_set_track_selection_error (timeline, TRUE);
if (error || !success) {
if (!error)
GST_WARNING_OBJECT (timeline, "Track selection failed for %" GES_FORMAT,
GES_ARGS (track_element));
ges_timeline_set_track_selection_error (timeline, TRUE, error);
}
}
static void
@ -1573,7 +1667,7 @@ track_element_added_cb (GESTrack * track, GESTrackElement * element,
/* returns TRUE if no errors in adding to tracks */
static gboolean
_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip,
gboolean add_core, GESTrack * new_track, GList * blacklist)
gboolean add_core, GESTrack * new_track, GList * blacklist, GError ** error)
{
GList *tmp, *children;
gboolean no_errors = TRUE;
@ -1589,13 +1683,19 @@ _add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip,
if (ges_track_element_get_track (el) == NULL) {
gboolean res;
if (new_track)
res = _try_add_track_element_to_track (timeline, clip, el, new_track);
res = _try_add_track_element_to_track (timeline, clip, el, new_track,
error);
else
res = _add_track_element_to_tracks (timeline, clip, el);
if (!res)
res = _add_track_element_to_tracks (timeline, clip, el, error);
if (!res) {
no_errors = FALSE;
if (error)
goto done;
}
}
}
done:
g_list_free_full (children, gst_object_unref);
return no_errors;
@ -1604,12 +1704,10 @@ _add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip,
/* returns TRUE if no errors in adding to tracks */
static gboolean
add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
GESTrack * new_track)
GESTrack * new_track, GError ** error)
{
GList *tracks, *tmp, *list, *created, *just_added = NULL;
gboolean no_errors = TRUE;
/* TODO: extend with GError ** argument, which is accepted by
* ges_clip_add_child_to_track */
GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
" trackelements and adding them to our tracks", clip);
@ -1619,12 +1717,19 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
timeline->priv->new_track = new_track ? gst_object_ref (new_track) : NULL;
UNLOCK_DYN (timeline);
/* create core elements */
for (tmp = tracks; tmp; tmp = tmp->next) {
GESTrack *track = GES_TRACK (tmp->data);
if (new_track && track != new_track)
continue;
list = ges_clip_create_track_elements (clip, track->type);
/* just_added only used for pointer comparison, so safe to include
* elements that may be destroyed because they fail to be added to
* the clip */
just_added = g_list_concat (just_added, list);
for (created = list; created; created = created->next) {
GESTimelineElement *el = created->data;
@ -1640,21 +1745,25 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
* released. And when a child is selected for multiple tracks, their
* copy will be added to the clip before the track is selected, so
* the track will not be set in the child-added signal */
_set_track_selection_error (timeline, FALSE);
if (!ges_container_add (GES_CONTAINER (clip), el))
ges_timeline_set_track_selection_error (timeline, FALSE, NULL);
if (!ges_container_add (GES_CONTAINER (clip), el)) {
no_errors = FALSE;
GST_ERROR_OBJECT (clip, "Could not add the core element %s "
"to the clip", el->name);
if (_get_track_selection_error (timeline))
no_errors = FALSE;
}
gst_object_unref (el);
if (error && !no_errors)
goto done;
if (ges_timeline_take_track_selection_error (timeline, error)) {
no_errors = FALSE;
if (error)
goto done;
/* else, carry on as much as we can */
}
}
/* just_added only used for pointer comparison, so safe to include
* elements that are now destroyed because they failed to be added to
* the clip */
just_added = g_list_concat (just_added, list);
}
g_list_free_full (tracks, gst_object_unref);
/* set the tracks for the other children, with core elements first to
* make sure the non-core can be placed above them in the track (a
@ -1662,11 +1771,21 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
/* include just_added as a blacklist to ensure we do not try the track
* selection a second time when track selection returns no tracks */
if (!_add_clip_children_to_tracks (timeline, clip, TRUE, new_track,
just_added))
just_added, error)) {
no_errors = FALSE;
if (error)
goto done;
}
if (!_add_clip_children_to_tracks (timeline, clip, FALSE, new_track,
just_added))
just_added, error)) {
no_errors = FALSE;
if (error)
goto done;
}
done:
g_list_free_full (tracks, gst_object_unref);
LOCK_DYN (timeline);
gst_clear_object (&timeline->priv->new_track);
@ -1721,12 +1840,10 @@ layer_auto_transition_changed_cb (GESLayer * layer,
/* returns TRUE if selecting of tracks did not error */
gboolean
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip)
ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error)
{
GESProject *project;
gboolean ret;
/* TODO: extend with GError ** argument, which is accepted by
* ges_clip_add_child_to_track */
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), timeline);
@ -1754,7 +1871,7 @@ ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip)
/* timeline-tree handles creation of auto-transitions */
ret = TRUE;
} else {
ret = add_object_to_tracks (timeline, clip, NULL);
ret = add_object_to_tracks (timeline, clip, NULL, error);
}
GST_DEBUG ("Done");
@ -2215,7 +2332,7 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
/* add any existing clips to the timeline */
objects = ges_layer_get_clips (layer);
for (tmp = objects; tmp; tmp = tmp->next)
ges_timeline_add_clip (timeline, tmp->data);
ges_timeline_add_clip (timeline, tmp->data, NULL);
g_list_free_full (objects, gst_object_unref);
return TRUE;
@ -2361,7 +2478,7 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
objects = ges_layer_get_clips (tmp->data);
for (obj = objects; obj; obj = obj->next)
add_object_to_tracks (timeline, obj->data, track);
add_object_to_tracks (timeline, obj->data, track, NULL);
g_list_free_full (objects, gst_object_unref);
}

View file

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

View file

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

View file

@ -83,25 +83,41 @@ const GESTimeline* ges_track_get_timeline (GESTrack *track);
GES_API
gboolean ges_track_commit (GESTrack *track);
GES_API
void ges_track_set_timeline (GESTrack *track, GESTimeline *timeline);
void ges_track_set_timeline (GESTrack *track,
GESTimeline *timeline);
GES_API
gboolean ges_track_add_element (GESTrack *track, GESTrackElement *object);
gboolean ges_track_add_element (GESTrack *track,
GESTrackElement *object);
GES_API
gboolean ges_track_remove_element (GESTrack *track, GESTrackElement *object);
gboolean ges_track_add_element_full (GESTrack *track,
GESTrackElement *object,
GError ** error);
GES_API
void ges_track_set_create_element_for_gap_func (GESTrack *track, GESCreateElementForGapFunc func);
gboolean ges_track_remove_element (GESTrack *track,
GESTrackElement *object);
GES_API
void ges_track_set_mixing (GESTrack *track, gboolean mixing);
gboolean ges_track_remove_element_full (GESTrack *track,
GESTrackElement *object,
GError ** error);
GES_API
void ges_track_set_create_element_for_gap_func (GESTrack *track,
GESCreateElementForGapFunc func);
GES_API
void ges_track_set_mixing (GESTrack *track,
gboolean mixing);
GES_API
gboolean ges_track_get_mixing (GESTrack *track);
GES_API
void ges_track_set_restriction_caps (GESTrack *track, const GstCaps *caps);
void ges_track_set_restriction_caps (GESTrack *track,
const GstCaps *caps);
GES_API
void ges_track_update_restriction_caps (GESTrack *track, const GstCaps *caps);
void ges_track_update_restriction_caps (GESTrack *track,
const GstCaps *caps);
GES_API
GstCaps * ges_track_get_restriction_caps (GESTrack * track);
GES_API
GESTrack* ges_track_new (GESTrackType type, GstCaps * caps);
GESTrack* ges_track_new (GESTrackType type,
GstCaps * caps);
G_END_DECLS

View file

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

View file

@ -404,4 +404,13 @@ G_STMT_START { \
free_children_properties (props2, num2); \
}
#define assert_GESError(error, error_code) \
{ \
fail_unless (error); \
fail_unless (error->domain == GES_ERROR); \
assert_equals_int (error->code, error_code); \
g_error_free (error); \
error = NULL; \
}
void print_timeline(GESTimeline *timeline);

View file

@ -149,6 +149,21 @@ class GESTest(unittest.TestCase):
for effect in effects]
self.assertEqual(indexes, list(range(len(effects))))
def assertGESError(self, error, code, message=""):
if error is None:
raise AssertionError(
"{}{}Received no error".format(message, message and ": "))
if error.domain != "GES_ERROR":
raise AssertionError(
"{}{}Received error ({}) in domain {} rather than "
"GES_ERROR".format(
message, message and ": ", error.message, error.domain))
err_code = GES.Error(error.code)
if err_code != code:
raise AssertionError(
"{}{}Received {} error ({}) rather than {}".format(
message, message and ": ", err_code.value_name,
error.message, code.value_name))
class GESSimpleTimelineTest(GESTest):
@ -680,7 +695,7 @@ class GESTimelineConfigTest(GESTest):
def assertEdit(self, element, layer, mode, edge, position, snap,
snap_froms, snap_tos, new_props, new_transitions,
lost_transitions):
if not element.edit([], layer, mode, edge, position):
if not element.edit_full(layer, mode, edge, position):
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at position {} "
"failed when a success was expected".format(
@ -690,11 +705,31 @@ class GESTimelineConfigTest(GESTest):
snap_tos=snap_tos, new_transitions=new_transitions,
lost_transitions=lost_transitions)
def assertFailEdit(self, element, layer, mode, edge, position):
if element.edit([], layer, mode, edge, position):
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at position {} "
"succeeded when a failure was expected".format(
element, layer, mode, edge, position))
def assertFailEdit(self, element, layer, mode, edge, position, err_code):
res = None
error = None
try:
res = element.edit_full(layer, mode, edge, position)
except GLib.Error as exception:
error = exception
if err_code is None:
if res is not False:
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at "
"position {} succeeded when a failure was expected"
"".format(
element, layer, mode, edge, position))
if error is not None:
raise AssertionError(
"Edit of {} to layer {}, mode {}, edge {}, at "
"position {} did produced an error when none was "
"expected".format(
element, layer, mode, edge, position))
else:
self.assertGESError(
error, err_code,
"Edit of {} to layer {}, mode {}, edge {}, at "
"position {}".format(element, layer, mode, edge, position))
# should be no change or snapping if edit fails
self.assertTimelineConfig()

View file

@ -27,6 +27,7 @@ gi.require_version("GES", "1.0")
from gi.repository import Gst # noqa
from gi.repository import GES # noqa
from gi.repository import GLib # noqa
import unittest # noqa
from unittest import mock
@ -561,8 +562,12 @@ class TestEditing(common.GESSimpleTimelineTest):
self.assertTrue(clip.set_duration(10))
# cannot trim to a 0 because effect0 would have a negative in-point
self.assertFalse(
clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0))
error = None
try:
clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)
except GLib.Error as err:
error = err
self.assertGESError(error, GES.Error.NEGATIVE_TIME)
self.assertEqual(clip.start, 10)
self.assertEqual(clip.inpoint, 12)
@ -945,11 +950,20 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest):
clip2 = self.add_clip(start=10, in_point=0, duration=4)
clip3 = self.add_clip(start=12, in_point=0, duration=3)
self.assertIsNone(clip1.split(13))
self.assertIsNone(clip1.split(8))
self.assertIsNone(clip1.split_full(13))
self.assertIsNone(clip1.split_full(8))
self.assertIsNone(clip3.split_full(12))
self.assertIsNone(clip3.split_full(15))
self.assertIsNone(clip3.split(12))
self.assertIsNone(clip3.split(15))
def _fail_split(self, clip, position):
split = None
error = None
try:
split = clip.split_full(position)
except GLib.Error as err:
error = err
self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(split)
def test_split_with_transition(self):
self.track_types = [GES.TrackType.AUDIO]
@ -958,22 +972,41 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest):
clip0 = self.add_clip(start=0, in_point=0, duration=50)
clip1 = self.add_clip(start=20, in_point=0, duration=50)
clip2 = self.add_clip(start=60, in_point=0, duration=20)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30),
(GES.TestClip, 20, 50)
(GES.TestClip, 20, 50),
(GES.TransitionClip, 60, 10),
(GES.TestClip, 60, 20),
]
])
# Split should file as the first part of the split
# Split should fail as the first part of the split
# would be fully overlapping clip0
self.assertIsNone(clip1.split(40))
self._fail_split(clip1, 40)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30),
(GES.TestClip, 20, 50)
(GES.TestClip, 20, 50),
(GES.TransitionClip, 60, 10),
(GES.TestClip, 60, 20),
]
])
# same with end of the clip
self._fail_split(clip1, 65)
self.assertTimelineTopology([
[
(GES.TestClip, 0, 50),
(GES.TransitionClip, 20, 30),
(GES.TestClip, 20, 50),
(GES.TransitionClip, 60, 10),
(GES.TestClip, 60, 20),
]
])
@ -1277,79 +1310,82 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest):
class TestConfigurationRules(common.GESSimpleTimelineTest):
def _try_add_clip(self, start, duration, layer=None):
def _try_add_clip(self, start, duration, layer=None, error=None):
if layer is None:
layer = self.layer
asset = GES.Asset.request(GES.TestClip, None)
found_err = None
clip = None
# large inpoint to allow trims
return layer.add_asset (asset, start, 1000, duration,
GES.TrackType.UNKNOWN)
try:
clip = layer.add_asset_full(
asset, start, 1000, duration, GES.TrackType.UNKNOWN)
except GLib.Error as err:
found_err = err
if error is None:
self.assertIsNotNone(clip)
else:
self.assertIsNone(clip)
self.assertGESError(found_err, error)
return clip
def test_full_overlap_add(self):
clip1 = self._try_add_clip(50, 50)
self.assertIsNotNone(clip1)
self.assertIsNone(self._try_add_clip(50, 50))
self.assertIsNone(self._try_add_clip(49, 51))
self.assertIsNone(self._try_add_clip(51, 49))
self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
def test_triple_overlap_add(self):
clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(40, 50)
self.assertIsNotNone(clip2)
self.assertIsNone(self._try_add_clip(40, 10))
self.assertIsNone(self._try_add_clip(30, 30))
self.assertIsNone(self._try_add_clip(1, 88))
self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
def test_full_overlap_move(self):
clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(50, 50)
self.assertIsNotNone(clip2)
self.assertFalse(clip2.set_start(0))
def test_triple_overlap_move(self):
clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(40, 50)
self.assertIsNotNone(clip2)
clip3 = self._try_add_clip(100, 60)
self.assertIsNotNone(clip3)
self.assertFalse(clip3.set_start(30))
def test_full_overlap_move_into_layer(self):
clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
layer2 = self.timeline.append_layer()
clip2 = self._try_add_clip(0, 50, layer2)
self.assertIsNotNone(clip2)
self.assertFalse(clip2.move_to_layer(self.layer))
res = None
try:
res = clip2.move_to_layer_full(self.layer)
except GLib.Error as error:
self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(res)
def test_triple_overlap_move_into_layer(self):
clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(40, 50)
self.assertIsNotNone(clip2)
layer2 = self.timeline.append_layer()
clip3 = self._try_add_clip(30, 30, layer2)
self.assertIsNotNone(clip3)
self.assertFalse(clip3.move_to_layer(self.layer))
res = None
try:
res = clip3.move_to_layer_full(self.layer)
except GLib.Error as error:
self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
self.assertIsNone(res)
def test_full_overlap_trim(self):
clip1 = self._try_add_clip(0, 50)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(50, 50)
self.assertIsNotNone(clip2)
self.assertFalse(clip2.trim(0))
self.assertFalse(clip1.set_duration(100))
def test_triple_overlap_trim(self):
clip1 = self._try_add_clip(0, 20)
self.assertIsNotNone(clip1)
clip2 = self._try_add_clip(10, 30)
self.assertIsNotNone(clip2)
clip3 = self._try_add_clip(30, 20)
self.assertIsNotNone(clip3)
self.assertFalse(clip3.trim(19))
self.assertFalse(clip1.set_duration(31))
@ -1506,26 +1542,34 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# cannot move c0 up one layer because it would cause a triple
# overlap between c1, c2 and c3 when g0 moves
self.assertFailEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23)
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move c0, without moving g1, to 21 layer 1 because it
# would be completely overlapped by c2
self.assertFailEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20)
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move c1, without moving g1, with end 25 because it
# would be completely overlapped by c2
self.assertFailEdit(
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25)
c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move g0 to layer 0 because it would make c0 go to a
# negative layer
self.assertFailEdit(
g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10)
g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10,
GES.Error.NEGATIVE_LAYER)
# cannot move c1 for same reason
self.assertFalse(
c1.move_to_layer(self.timeline.get_layer(0)))
error = None
try:
c1.move_to_layer_full(self.timeline.get_layer(0))
except GLib.Error as err:
error = err
self.assertGESError(error, GES.Error.NEGATIVE_LAYER)
self.assertTimelineConfig({}, [])
# failure with snapping
@ -1534,17 +1578,20 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# cannot move to 0 because end edge of c0 would snap with end of
# c3, making the new start become negative
self.assertFailEdit(
g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0)
g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0,
GES.Error.NEGATIVE_TIME)
# cannot move start of c1 to 14 because snapping causes a full
# overlap with c0
self.assertFailEdit(
c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14)
c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot move end of c2 to 21 because snapping causes a full
# overlap with c0
self.assertFailEdit(
c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21)
c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# successes
self.timeline.set_snapping_distance(3)
@ -1701,19 +1748,22 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# would cause negative layer priority for c0
self.assertFailEdit(
c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5)
c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5,
GES.Error.NEGATIVE_LAYER)
# would lead to c2 fully overlapping c3 since c2 does ripple
# but c3 does not(c3 shares a toplevel with c0, and
# GES_EDGE_START, same as NORMAL mode, does not move the
# toplevel
self.assertFailEdit(
c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25)
c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# would lead to c2 fully overlapping c3 since c2 does not
# ripple but c3 does
self.assertFailEdit(
c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13)
c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# add two more clips
@ -2160,34 +2210,41 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# cannot trim end of g0 to 16 because a0 and a1 would fully
# overlap
self.assertFailEdit(
g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15)
g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot edit to new layer because there would be triple overlaps
# between v2, v3, v4 and v5
self.assertFailEdit(
g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20)
g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot trim g1 end to 14 because it would result in a negative
# duration for a2 and a4
self.assertFailEdit(
g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14)
g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14,
GES.Error.NEGATIVE_TIME)
# cannot trim end of v2 below its start
self.assertFailEdit(
v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2)
v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2,
GES.Error.NEGATIVE_TIME)
# cannot trim end of g0 because a0's duration-limit would be
# exceeded
self.assertFailEdit(
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23)
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23,
GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
# cannot trim g0 to 12 because a0 and a1 would fully overlap
self.assertFailEdit(
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12)
g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot trim start of v2 beyond its end point
self.assertFailEdit(
v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20)
v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20,
GES.Error.NEGATIVE_TIME)
# with snapping
self.timeline.set_snapping_distance(4)
@ -2195,12 +2252,14 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# cannot trim end of g2 to 19 because v1 and v2 would fully
# overlap after snapping to v5 start edge(18)
self.assertFailEdit(
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19)
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot trim g2 to 3 because it would snap to start edge of
# v4(0), causing v2's in-point to be negative
self.assertFailEdit(
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3)
g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3,
GES.Error.NEGATIVE_TIME)
# success
@ -2737,46 +2796,66 @@ class TestComplexEditing(common.GESTimelineConfigTest):
# cannot roll c10 to 22, which snaps to 23, because it will
# extend c5 beyond its duration limit of 8
self.assertFailEdit(
c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22)
c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22,
GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
# same with g2
self.assertFailEdit(
g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22)
g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22,
GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
# cannot roll end c9 to 8, which snaps to 7, because it would
# cause c3's in-point to become negative
self.assertFailEdit(
c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8)
c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8,
GES.Error.NEGATIVE_TIME)
# same with g1
self.assertFailEdit(
g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8)
g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8,
GES.Error.NEGATIVE_TIME)
# cannot roll c13 to 19, snap to 20, because it would cause
# c4 to fully overlap c5
self.assertFailEdit(
c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19)
c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# cannot roll c12 to 11, snap to 10, because it would cause
# c3 to fully overlap c4
self.assertFailEdit(
c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11)
c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11,
GES.Error.INVALID_OVERLAP_IN_TRACK)
# give c6 a bit more allowed duration so we can focus on c9
self.assertTrue(c6.set_inpoint(10))
self.assertTimelineConfig({ c6 : {"in-point": 10}})
# cannot roll c6 to 0 because it would cause c9 to be trimmed
# below its start
self.assertFailEdit(
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0)
c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0,
GES.Error.NEGATIVE_TIME)
# set back
self.assertTrue(c6.set_inpoint(7))
self.assertTimelineConfig({ c6 : {"in-point": 7}})
# give c7 a bit more allowed duration so we can focus on c10
self.assertTrue(c7.set_inpoint(0))
self.assertTimelineConfig({ c7 : {"in-point": 0}})
# cannot roll end c7 to 30 because it would cause c10 to be
# trimmed beyond its end
self.assertFailEdit(
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30)
c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30,
GES.Error.NEGATIVE_TIME)
# set back
self.assertTrue(c7.set_inpoint(1))
self.assertTimelineConfig({ c7 : {"in-point": 1}})
# moving layer is not supported
self.assertFailEdit(
c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7)
c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, None)
self.assertFailEdit(
c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23)
c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, None)
# successes
self.timeline.set_snapping_distance(0)