errors: added edit errors
authorHenry Wilkes <hwilkes@igalia.com>
Fri, 15 May 2020 13:25:01 +0000 (14:25 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Fri, 22 May 2020 18:15:57 +0000 (19:15 +0100)
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>

19 files changed:
ges/ges-clip.c
ges/ges-clip.h
ges/ges-gerror.h
ges/ges-group.c
ges/ges-internal.h
ges/ges-layer.c
ges/ges-layer.h
ges/ges-timeline-element.c
ges/ges-timeline-element.h
ges/ges-timeline-tree.c
ges/ges-timeline-tree.h
ges/ges-timeline.c
ges/ges-track-element.c
ges/ges-track.c
ges/ges-track.h
tests/check/ges/clip.c
tests/check/ges/test-utils.h
tests/check/python/common.py
tests/check/python/test_timeline.py

index d1ff7a31f32a0a915c54564318f40633f4ecb6b2..9cd79f59acdfb03a8c4cfe50109b8e7902e1ea02 100644 (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));
index 1e2c5d9a07c1b6669597b272295f7c7d38410b78..bf62eac7700c3f0d43edeff5c2e132bed0910a31 100644 (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);
 GES_API
-gboolean  ges_clip_move_to_layer          (GESClip *clip, GESLayer  *layer);
+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,
index 1f4d8b4520b547873ff85f711b867adc0ef8177f..2e326e50f47101bddfe19fb49e60ca9223edccda 100644 (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
\ No newline at end of file
+G_END_DECLS
index 82a292d973479a5634a8ca1828a7a7d96b31f709..482be3003260790559848fbb811fcd5aa856d8ff 100644 (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
index 6b5a4f8f2e9a926ea509741f761d48f27127c982..76eb2606455d02156290158fe71a0dbef2db970c 100644 (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);
index c46ee97ff9741ce3c773477527492c4c8fd4e218..6cef4c8c572944f8b19c3aa45f82f8b64a7d65ca 100644 (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:
  *
index e2d6fa41d0c31b1b4d844f63cb934d22a0d3f974..c9d2d06e1e5b939d19b530e748a6e83679c53d50 100644 (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
+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);
+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
index e025694a2405f2db2b5c786ea028a53df37b7b2e..491564f8faafe78ce610a8939c6e9948699dbc05 100644 (file)
  * 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);
 }
 
 /**
index bb5ce2ed54898710d8b90733101b8b888678f512..e2b9abb250d6b294d3da726ac00714e4d1f4d16f 100644 (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
index 9d8aecd02c898310189e5aa6bc3367ac84666c20..edc9fbf3a1fb166a0308227463b162e461c7a7a6 100644 (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,12 +1201,25 @@ replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
       goto error;
     }
 
-    layer_offset = group_edit->layer_offset;
-    mode = group_edit->mode;
+    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 (!get_start_end_from_offset (group, mode, group_edit->offset,
-            &new_start, NULL, &new_end, NULL))
+    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;
 
     /* can traverse leaves to find all the clips since they are at _most_
      * one step above the track elements */
@@ -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;
   }
 
index 1e4c618c6ee787a2d4ca1637d7e3cbbb01411f6d..5fafba35a40b4343518fdb696cb459553bc335ee 100644 (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,
index e034c5c38ffb71d94b5bbdfa8433071bb27f71ad..94af1457d6fa64869171eed15d61f1c3f2d357db 100644 (file)
  * content prioritised in the tracks. This ordering can be changed using
  * ges_timeline_move_layer().
  *
- * ## 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
  *
+ * 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 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.
+ * 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.
  *
  * ## Playing
  *
@@ -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);
   }
index 2174bb521ffe79264b6994231d64b6c4ab1dad42..5461432f03924d4eae72bc68cb84e233ff3d8ef6 100644 (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;
   }
 
index 8896c3375b7296360fbdc5b17396a3c5bc8b2498..5dddb4d2f7952a2b729474d7c7590f9972b0bab0 100644 (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);
 }
 
 /**
index 4a51ba760dcc1eabe4800ce2be47b57cbf03e18a..46da9c8ccf1aed2ea388fe29e886a01164b69d57 100644 (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
index 37bfaa93bf249669ce90f2dea4aaeeff8795d1f2..e93f36d12d6841eeb2e988b9cb0426dbbf455d6f 100644 (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);
index 282f389742858ea37dd473bcfb6c0326be19ae11..85ee03a9e349aa01856426db7fdaa5dca9d5b1cd 100644 (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);
index 2ff3dc540ec223483022bd4352002a7b511445a4..18609ccbf4f27dc21c21a13f1e325c8099d1481f 100644 (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()
index 04d2aef53bc32471c01fc83b17a388a553901cec..d438afacb67c156d9b89de8426dc0476ef66960a 100644 (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(clip3.split(12))
-        self.assertIsNone(clip3.split(15))
+        self.assertIsNone(clip1.split_full(13))
+        self.assertIsNone(clip1.split_full(8))
+        self.assertIsNone(clip3.split_full(12))
+        self.assertIsNone(clip3.split_full(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.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.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)