timeline-tree: simplify and fix editing

Editing has been simplified by breaking down each edit into a
combination of three basic single-element edits: MOVE, TRIM_START, and
TRIM_END.

Each edit follows these steps:
+ Determine which elements are to be edited and under which basic mode
+ Determine which track elements will move as a result
+ Snap the edit position to one of the edges of the main edited element,
  (or the edge of one of its descendants, in the case of MOVE), avoiding
  moving elements.
  NOTE: in particular, we can *not* snap to the edge of a neighbouring
  element in a roll edit. This was previously possible, even though the
  neighbour was moving!
+ Determine the edit positions for clips (or track elements with no
  parent) using the snapped value. In addition, we replace any edits of
  a group with an edit of its descendant clips. If any value would be
  out of bounds (e.g. negative start) we do not edit.
  NOTE: this is now done *after* checking the snapping. This allows the
  edit to succeed if snapping would cause it to go from being invalid to
  valid!
+ Determine whether the collection of edits would result in a valid
  timeline-configuration which does not break the rules for sources
  overlapping.
+ If all this succeeds, we emit snapping-started on the timeline.
+ We then perform all the edits. At this point they should all succeed.

The simplification/unification should make it easier to make other
changes.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/97
Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/98

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
This commit is contained in:
Henry Wilkes 2020-04-27 13:58:38 +01:00
parent a6b13ce619
commit 39097f5574
14 changed files with 1928 additions and 1213 deletions

View file

@ -296,7 +296,8 @@ _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 (_CLOCK_TIME_IS_LESS (duration_limit, element->duration)
&& !ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
gboolean res;
GST_INFO_OBJECT (self, "Automatically reducing duration to %"
@ -1849,11 +1850,12 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
{
gboolean ret = FALSE;
GESLayer *current_layer;
GESTimeline *current_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
GESTimelineElement *element;
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
element = GES_TIMELINE_ELEMENT (clip);
current_layer = clip->priv->layer;
if (current_layer == layer) {
@ -1861,39 +1863,34 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
return TRUE;
}
gst_object_ref (clip);
if (current_layer == NULL) {
GST_DEBUG ("Not moving %p, only adding it to %p", clip, layer);
ret = ges_layer_add_clip (layer, clip);
goto done;
return ges_layer_add_clip (layer, clip);
}
ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
if (current_timeline != layer->timeline) {
if (element->timeline != layer->timeline) {
/* make sure we can perform the can_move_element_check in the timeline
* of the layer */
GST_WARNING_OBJECT (layer, "Cannot move clip %" GES_FORMAT " into "
"the layer because its timeline %" GST_PTR_FORMAT " does not "
"match the timeline of the layer %" GST_PTR_FORMAT,
GES_ARGS (clip), current_timeline, layer->timeline);
ret = FALSE;
goto done;
GES_ARGS (clip), element->timeline, layer->timeline);
return FALSE;
}
if (layer->timeline
&& !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
GES_TIMELINE_ELEMENT (clip),
ges_layer_get_priority (layer),
GES_TIMELINE_ELEMENT_START (clip),
GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT " can't move to layer %d",
GES_ARGS (clip), ges_layer_get_priority (layer));
goto done;
&& !ELEMENT_FLAG_IS_SET (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
/* 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);
}
gst_object_ref (clip);
ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer,
ges_layer_get_priority (layer));
@ -2250,7 +2247,7 @@ ges_clip_split (GESClip * clip, guint64 position)
if (timeline && !timeline_tree_can_move_element (timeline_get_tree
(timeline), element,
ges_timeline_element_get_layer_priority (element),
start, old_duration, NULL)) {
start, old_duration)) {
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would be in an illegal" " state.", GES_ARGS (clip),
@ -2262,7 +2259,7 @@ ges_clip_split (GESClip * clip, guint64 position)
if (timeline && !timeline_tree_can_move_element (timeline_get_tree
(timeline), element,
ges_timeline_element_get_layer_priority (element),
position, new_duration, NULL)) {
position, new_duration)) {
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would end up in an illegal" " state.", GES_ARGS (clip),

View file

@ -144,6 +144,25 @@ register_ges_edit_mode (GType * id)
*id = g_enum_register_static ("GESEditMode", edit_mode);
}
const gchar *
ges_edit_mode_name (GESEditMode mode)
{
switch (mode) {
case GES_EDIT_MODE_NORMAL:
return "normal";
case GES_EDIT_MODE_RIPPLE:
return "ripple";
case GES_EDIT_MODE_ROLL:
return "roll";
case GES_EDIT_MODE_TRIM:
return "trim";
case GES_EDIT_MODE_SLIDE:
return "slide";
default:
return "unknown";
}
}
GType
ges_edit_mode_get_type (void)
{

View file

@ -365,73 +365,150 @@ GType ges_pipeline_flags_get_type (void);
/**
* GESEditMode:
* @GES_EDIT_MODE_NORMAL: The element is edited the normal way (default).
* This only moves a single element. If acting on the start edge of the
* element, the element's start time is set to the edit position.
* If acting on end edge of the element, the element's duration time
* is set such that its end time matches the edit position.
* @GES_EDIT_MODE_RIPPLE: The element is edited in ripple mode. This
* shifts the element and all later elements (those with equal or later
* start times) in the timeline by the same amount. If acting on the
* element as a whole, the element's start time is shifted to the edit
* position, and later elements are also shifted by the same amount. If
* acting on the end edge of the element, the element's **duration time**
* is shifted so that the element's end time matches the edit position,
* and later elements have their **start time** shifted by the same
* amount.
* @GES_EDIT_MODE_ROLL: The element is edited in roll mode. This trims
* the edge of an element and neighbouring edges (opposite edges of other
* elements in the timeline with the same corresponding time value), such
* that the edges remain in contact. If acting on the start edge of the
* element, the start edge is trimmed to the edit position (see
* #GES_EDIT_MODE_TRIM), and any other elements in the timeline whose end
* time matches the edited element's start time (evaluated before the
* edit) will have their **end** edge trimmed to the same edit position.
* Similarly, if acting on the end edge of the element, the end edge is
* trimmed to the edit position, and any other elements in the timeline
* whose start time matches the edited element's end time will have
* their start edge trimmed to the same edit position.
* @GES_EDIT_MODE_TRIM: The element is edited in trim mode. This shifts
* the edge of a single element while maintaining the timing of
* its internal content in the timeline, so the samples/frames/etc of a
* source would still appear at the same timeline time when it is played.
* If acting on the start edge of the element, the element's start time
* will be shifted to the edit position and the element's in-point time
* will be shifted by the same amount. Additionally, the element's
* duration time will be shifted the other way such that the element's
* end time remains the same. If acting on end edge of the element, the
* element's duration time is set such that its end time matches the edit
* position.
* If acting on the element as a whole (#GES_EDGE_NONE), this will MOVE
* the element by MOVING its toplevel. When acting on the start of the
* element (#GES_EDGE_START), this will only MOVE the element, but not
* its toplevel parent. This can allow you to move a #GESClip or
* #GESGroup to a new start time or layer within its container group,
* without effecting other members of the group. When acting on the end
* of the element (#GES_EDGE_END), this will END-TRIM the element,
* leaving its toplevel unchanged.
* @GES_EDIT_MODE_RIPPLE: The element is edited in ripple mode: moving
* itself as well as later elements, keeping their relative times. This
* edits the element the same as #GES_EDIT_MODE_NORMAL. In addition, if
* acting on the element as a whole, or the start of the element, any
* toplevel element in the same timeline (including different layers)
* whose start time is later than the *current* start time of the MOVED
* element will also be MOVED by the same shift as the edited element.
* If acting on the end of the element, any toplevel element whose start
* time is later than the *current* end time of the edited element will
* also be MOVED by the same shift as the change in the end of the
* edited element. These additional elements will also be shifted by
* the same shift in layers as the edited element.
* @GES_EDIT_MODE_ROLL: The element is edited in roll mode: swapping its
* content for its neighbour's, or vis versa, in the timeline output.
* This edits the element the same as #GES_EDIT_MODE_TRIM. In addition,
* any neighbours are also TRIMMED at their opposite edge to the same
* timeline position. When acting on the start of the element, a
* neighbour is any earlier element in the timeline whose end time
* matches the *current* start time of the edited element. When acting on
* the end of the element, a neighbour is any later element in the
* timeline whose start time matches the *current* start time of the
* edited element. In addition, a neighbour have a #GESSource at its
* end/start edge that shares a track with a #GESSource at the start/end
* edge of the edited element. Basically, a neighbour is an element that
* can be extended, or cut, to have its content replace, or be replaced
* by, the content of the edited element. Acting on the element as a
* whole (#GES_EDGE_NONE) is not defined. The element can not shift
* layers under this mode.
* @GES_EDIT_MODE_TRIM: The element is edited in trim mode. When acting
* on the start of the element, this will START-TRIM it. When acting on
* the end of the element, this will END-TRIM it. Acting on the element
* as a whole (#GES_EDGE_NONE) is not defined.
* @GES_EDIT_MODE_SLIDE: The element is edited in slide mode (not yet
* implemented). This shifts the element and will trim the edges of
* neighbouring edges on either side accordingly. If acting on the
* element as a whole, the element's start time is shifted to the edit
* position. Any other elements in the timeline whose end time matches
* the edited element's start time (evaluated before the edit) will have
* their end edge trimmed to the same edit position. Additionally, any
* other elements in the timeline whose start time matches the edited
* element's end time will have their start edge trimmed to match the
* edited element's **new** end time.
* implemented): moving the element replacing or consuming content on
* each end. When acting on the element as a whole, this will MOVE the
* element, and TRIM any neighbours on either side. A neighbour is
* defined in the same way as in #GES_EDIT_MODE_ROLL, but they may be on
* either side of the edited elements. Elements at the end with be
* START-TRIMMED to the new end position of the edited element. Elements
* at the start will be END-TRIMMED to the new start position of the
* edited element. Acting on the start or end of the element
* (#GES_EDGE_START and #GES_EDGE_END) is not defined. The element can
* not shift layers under this mode.
*
* When a single timeline element is edited within its timeline, using
* ges_timeline_element_edit(), depending on the edit mode, its
* #GESTimelineElement:start, #GESTimelineElement:duration or
* When a single timeline element is edited within its timeline at some
* position, using ges_timeline_element_edit(), depending on the edit
* mode, its #GESTimelineElement:start, #GESTimelineElement:duration or
* #GESTimelineElement:in-point will be adjusted accordingly. In addition,
* other elements in the timeline may also have their properties adjusted.
* any clips may change #GESClip:layer.
*
* In fact, the edit is actually performed on the toplevel of the edited
* element (usually a #GESClip), which is responsible for moving its
* children along with it. For simplicity, in the descriptions we will
* use "element" to exclusively refer to toplevel elements.
* Each edit can be broken down into a combination of three basic edits:
*
* In the edit mode descriptions, the "start edge", "end edge" and the
* "element as a whole" correspond to using #GES_EDGE_START, #GES_EDGE_END
* and #GES_EDGE_NONE as part of the edit, respectively. The "start time",
* "duration time" and "in-point time" correspond to the
* #GESTimelineElement:start, #GESTimelineElement:duration and
* #GESTimelineElement:in-point properties, respectively. Moreover, the
* "end time" refers to the final time of the element:
* #GESTimelineElement:start + #GESTimelineElement:duration. Finally,
* the "edit position" is the timeline time used as part of the edit.
* + MOVE: This moves the start of the element to the edit position.
* + START-TRIM: This cuts or grows the start of the element, whilst
* maintaining the time at which its internal content appears in the
* timeline data output. If the element is made shorter, the data that
* appeared at the edit position and later will still appear in the
* timeline at the same time. If the element is made longer, the data
* that appeared at the previous start of the element and later will
* still appear in the timeline at the same time.
* + END-TRIM: Similar to START-TRIM, but the end of the element is cut or
* grown.
*
* In particular, when editing a #GESClip:
*
* + MOVE: This will set the #GESTimelineElement:start of the clip to the
* edit position.
* + START-TRIM: This will set the #GESTimelineElement:start of the clip
* to the edit position. To keep the end time the same, the
* #GESTimelineElement:duration of the clip will be adjusted in the
* opposite direction. In addition, the #GESTimelineElement:in-point of
* the clip will be shifted such that the content that appeared at the
* new or previous start time, whichever is latest, still appears at the
* same timeline time. For example, if a frame appeared at the start of
* the clip, and the start of the clip is reduced, the in-point of the
* clip will also reduce such that the frame will appear later within
* the clip, but at the same timeline position.
* + END-TRIM: This will set the #GESTimelineElement:duration of the clip
* such that its end time will match the edit position.
*
* When editing a #GESGroup:
*
* + MOVE: This will set the #GESGroup:start of the clip to the edit
* position by shifting all of its children by the same amount. So each
* child will maintain their relative positions.
* + START-TRIM: If the group is made shorter, this will START-TRIM any
* clips under the group that start after the edit position to the same
* edit position. If the group is made longer, this will START-TRIM any
* clip under the group whose start matches the start of the group to
* the same edit position.
* + END-TRIM: If the group is made shorter, this will END-TRIM any clips
* under the group that end after the edit position to the same edit
* position. If the group is made longer, this will END-TRIM any clip
* under the group whose end matches the end of the group to the same
* edit position.
*
* When editing a #GESTrackElement, if it has a #GESClip parent, this
* will be edited instead. Otherwise it is edited in the same way as a
* #GESClip.
*
* The layer priority of a #GESGroup is the lowest layer priority of any
* #GESClip underneath it. When a group is edited to a new layer
* priority, it will shift all clips underneath it by the same amount,
* such that their relative layers stay the same.
*
* If the #GESTimeline has a #GESTimeline:snapping-distance, then snapping
* may occur for some of the edges of the **main** edited element:
*
* + MOVE: The start or end edge of *any* #GESSource under the element may
* be snapped.
* + START-TRIM: The start edge of a #GESSource whose start edge touches
* the start edge of the element may snap.
* + END-TRIM: The end edge of a #GESSource whose end edge touches the end
* edge of the element may snap.
*
* These edges may snap with either the start or end edge of *any* other
* #GESSource in the timeline that is not also being moved by the element,
* including those in different layers, if they are within the
* #GESTimeline:snapping-distance. During an edit, only up to one snap can
* occur. This will shift the edit position such that the snapped edges
* will touch once the edit has completed.
*
* Note that snapping can cause an edit to fail where it would have
* otherwise succeeded because it may push the edit position such that the
* edit would result in an unsupported timeline configuration. Similarly,
* snapping can cause an edit to succeed where it would have otherwise
* failed.
*
* For example, in #GES_EDIT_MODE_RIPPLE acting on #GES_EDGE_NONE, the
* main element is the MOVED toplevel of the edited element. Any source
* under the main MOVED toplevel may have its start or end edge snapped.
* Note, these sources cannot snap with each other. The edit may also
* push other elements, but any sources under these elements cannot snap,
* nor can they be snapped with. If a snap does occur, the MOVE of the
* toplevel *and* all other elements pushed by the ripple will be shifted
* by the same amount such that the snapped edges will touch.
*
* You can also find more explanation about the behaviour of those modes at:
* [trim, ripple and roll](http://pitivi.org/manual/trimming.html)
@ -445,6 +522,9 @@ typedef enum {
GES_EDIT_MODE_SLIDE
} GESEditMode;
GES_API
const gchar * ges_edit_mode_name (GESEditMode mode);
#define GES_TYPE_EDIT_MODE ges_edit_mode_get_type()
GES_API

View file

@ -436,12 +436,6 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element,
duration);
if (element->timeline
&& !timeline_tree_can_move_element (timeline_get_tree (element->timeline),
element, _PRIORITY (element), element->start, duration, NULL)) {
return FALSE;
}
if (container->initiated_move == NULL) {
gboolean expending = (_DURATION (element) < duration);

View file

@ -88,36 +88,10 @@ GstDebugCategory * _ges_debug (void);
#define SUPRESS_UNUSED_WARNING(a) (void)a
G_GNUC_INTERNAL gboolean
timeline_ripple_object (GESTimeline *timeline, GESTimelineElement *obj,
gint new_layer_priority,
GList * layers, GESEdge edge,
guint64 position);
G_GNUC_INTERNAL gboolean
timeline_slide_object (GESTimeline *timeline, GESTrackElement *obj,
GList * layers, GESEdge edge, guint64 position);
G_GNUC_INTERNAL gboolean
timeline_roll_object (GESTimeline *timeline, GESTimelineElement *obj,
GList * layers, GESEdge edge, guint64 position);
G_GNUC_INTERNAL gboolean
timeline_trim_object (GESTimeline *timeline, GESTimelineElement * object,
guint32 new_layer_priority, GList * layers, GESEdge edge,
guint64 position);
G_GNUC_INTERNAL gboolean
ges_timeline_trim_object_simple (GESTimeline * timeline, GESTimelineElement * obj,
guint32 new_layer_priority, GList * layers, GESEdge edge,
guint64 position, gboolean snapping);
G_GNUC_INTERNAL gboolean
ges_timeline_move_object_simple (GESTimeline * timeline, GESTimelineElement * object,
GList * layers, GESEdge edge, guint64 position);
G_GNUC_INTERNAL gboolean
timeline_move_object (GESTimeline *timeline, GESTimelineElement * object,
guint32 new_layer_priority, GList * layers, GESEdge edge,
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position);
G_GNUC_INTERNAL void

View file

@ -1045,21 +1045,21 @@ ges_timeline_element_get_timeline (GESTimelineElement * self)
* @self: A #GESTimelineElement
* @start: The desired start position of the element in its timeline
*
* Sets #GESTimelineElement:start for the element. This may fail if it
* would place the timeline in an unsupported configuration.
* Sets #GESTimelineElement:start for the element. If the element has a
* parent, this will also move its siblings with the same shift.
*
* Note that if the element's timeline has a
* #GESTimeline:snapping-distance set, then the element's
* #GESTimelineElement:start may instead be set to the edge of some other
* element in the neighbourhood of @start. In such a case, the return
* value will still be %TRUE on success.
* Whilst the element is part of a #GESTimeline, this is the same as
* editing the element with ges_timeline_element_edit() under
* #GES_EDIT_MODE_NORMAL with #GES_EDGE_NONE. In particular, the
* #GESTimelineElement:start of the element may be snapped to a different
* timeline time from the one given. In addition, setting may fail if it
* would place the timeline in an unsupported configuration.
*
* Returns: %TRUE if @start could be set for @self.
*/
gboolean
ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
{
gboolean emit_notify = TRUE;
GESTimelineElementClass *klass;
GESTimelineElement *toplevel_container, *parent;
@ -1078,18 +1078,18 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
&& !ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE)
&& !ELEMENT_FLAG_IS_SET (toplevel_container,
GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
if (!ges_timeline_move_object_simple (self->timeline, self, NULL,
GES_EDGE_NONE, start)) {
gst_object_unref (toplevel_container);
return FALSE;
}
emit_notify = FALSE;
gst_object_unref (toplevel_container);
return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_NORMAL,
GES_EDGE_NONE, start);
}
parent = self->parent;
/* FIXME This should not belong to GESTimelineElement */
if (toplevel_container &&
/* only check if no timeline, otherwise the timeline-tree will handle this
* check */
if (!self->timeline && toplevel_container &&
((gint64) (_START (toplevel_container) + start - _START (self))) < 0 &&
parent
&& GES_CONTAINER (parent)->children_control_mode == GES_CHILDREN_UPDATE) {
@ -1107,7 +1107,7 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
gint res = klass->set_start (self, start);
if (res == FALSE)
return FALSE;
if (res == TRUE && emit_notify) {
if (res == TRUE) {
self->start = start;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]);
}
@ -1238,17 +1238,16 @@ ges_timeline_element_set_max_duration (GESTimelineElement * self,
* @self: A #GESTimelineElement
* @duration: The desired duration in its timeline
*
* Sets #GESTimelineElement:duration for the element. This may fail if it
* would place the timeline in an unsupported configuration, or if the
* element does not have enough internal content to last for the desired
* @duration.
* Sets #GESTimelineElement:duration for the element.
*
* Note that if the element's timeline has a
* #GESTimeline:snapping-distance set, then the element's
* #GESTimelineElement:duration may instead be adjusted around @duration
* such that the edge of @self matches the edge of some other element in
* the neighbourhood. In such a case, the return value will still be %TRUE
* on success.
* Whilst the element is part of a #GESTimeline, this is the same as
* editing the element with ges_timeline_element_edit() under
* #GES_EDIT_MODE_TRIM with #GES_EDGE_END. In particular, the
* #GESTimelineElement:duration of the element may be snapped to a
* different timeline time difference from the one given. In addition,
* setting may fail if it would place the timeline in an unsupported
* configuration, or the element does not have enough internal content to
* last the desired duration.
*
* Returns: %TRUE if @duration could be set for @self.
*/
@ -1257,7 +1256,6 @@ ges_timeline_element_set_duration (GESTimelineElement * self,
GstClockTime duration)
{
GESTimelineElementClass *klass;
gboolean emit_notify = TRUE;
GESTimelineElement *toplevel;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
@ -1269,19 +1267,9 @@ ges_timeline_element_set_duration (GESTimelineElement * self,
if (self->timeline &&
!ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE) &&
!ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
gboolean res;
res = timeline_trim_object (self->timeline, self,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self), NULL,
GES_EDGE_END, self->start + duration);
if (!res) {
gst_object_unref (toplevel);
return FALSE;
}
emit_notify = res == -1;
return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM,
GES_EDGE_END, self->start + duration);
}
gst_object_unref (toplevel);
@ -1295,7 +1283,7 @@ ges_timeline_element_set_duration (GESTimelineElement * self,
gint res = klass->set_duration (self, duration);
if (res == FALSE)
return FALSE;
if (res == TRUE && emit_notify) {
if (res == TRUE) {
self->duration = duration;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
}
@ -2428,9 +2416,15 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self)
* out of bounds, or if it would place the timeline in an unsupported
* configuration.
*
* Note that if you act on a #GESTrackElement, this will edit its parent
* #GESClip instead. Moreover, for any #GESTimelineElement, if you select
* #GES_EDGE_NONE for #GES_EDIT_MODE_NORMAL or #GES_EDIT_MODE_RIPPLE, this
* will edit the toplevel instead, but still in such a way as to make the
* #GESTimelineElement:start of @self reach the edit @position.
*
* Note that if the element's timeline has a
* #GESTimeline:snapping-distance set, then the edit position may be set
* to the edge of some element in the neighbourhood of @position.
* #GESTimeline:snapping-distance set, then the edit position may be
* snapped to the edge of some element under the edited element.
*
* @new_layer_priority can be used to switch @self, and other elements
* moved by the edit, to a new layer. New layers may be be created if the
@ -2455,35 +2449,26 @@ ges_timeline_element_edit (GESTimelineElement * self, GList * layers,
gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
{
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);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
/* FIXME: handle a NULL timeline! */
switch (mode) {
case GES_EDIT_MODE_RIPPLE:
return timeline_ripple_object (timeline, self,
new_layer_priority <
0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self) :
new_layer_priority, layers, edge, position);
case GES_EDIT_MODE_TRIM:
return timeline_trim_object (timeline, self,
new_layer_priority <
0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self) :
new_layer_priority, layers, edge, position);
case GES_EDIT_MODE_NORMAL:
return timeline_move_object (timeline, self,
new_layer_priority <
0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self) :
new_layer_priority, layers, edge, position);
case GES_EDIT_MODE_ROLL:
return timeline_roll_object (timeline, self, layers, edge, position);
case GES_EDIT_MODE_SLIDE:
GST_ERROR_OBJECT (self, "Sliding not implemented.");
return FALSE;
}
return FALSE;
g_return_val_if_fail (timeline, FALSE);
layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self);
if (new_layer_priority < 0)
new_layer_priority = layer_prio;
GST_DEBUG_OBJECT (self, "Editing %s at edge %s to position %"
GST_TIME_FORMAT " under %s mode, and to layer %" G_GINT64_FORMAT,
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);
}
/**

File diff suppressed because it is too large Load diff

View file

@ -13,19 +13,18 @@ gboolean timeline_tree_can_move_element (GNode *root,
GESTimelineElement *element,
guint32 priority,
GstClockTime start,
GstClockTime duration,
GList *moving_track_elements);
GstClockTime duration);
gboolean timeline_tree_ripple (GNode *root,
GESTimelineElement *element,
gint64 layer_priority_offset,
GstClockTimeDiff offset,
GESTimelineElement *rippled_element,
GESEdge moving_edge,
GESEdge edge,
GstClockTime snapping_distance);
void ges_timeline_emit_snapping (GESTimeline * timeline,
GESTimelineElement * elem1,
GESTimelineElement * elem2,
GESTrackElement * elem1,
GESTrackElement * elem2,
GstClockTime snap_time);
gboolean timeline_tree_trim (GNode *root,

View file

@ -146,10 +146,6 @@ struct _GESTimelinePrivate
GESTrackElement *last_snaped1;
GESTrackElement *last_snaped2;
/* This variable is set to %TRUE when it makes sense to update the transitions,
* and %FALSE otherwize */
gboolean needs_transitions_update;
GESTrack *auto_transition_track;
GESTrack *new_track;
@ -559,9 +555,9 @@ ges_timeline_class_init (GESTimelineClass * klass)
* GESTimeline:snapping-distance:
*
* The distance (in nanoseconds) at which a #GESTimelineElement being
* moved within the timeline should snap to its neighbours. Note that
* such a neighbour includes any element in the timeline, including
* across separate layers. 0 means no snapping.
* moved within the timeline should snap one of its #GESSource-s with
* another #GESSource-s edge. See #GESEditMode for which edges can
* snap during an edit. 0 means no snapping.
*/
properties[PROP_SNAPPING_DISTANCE] =
g_param_spec_uint64 ("snapping-distance", "Snapping distance",
@ -674,9 +670,14 @@ ges_timeline_class_init (GESTimelineClass * klass)
* @position: The position where the two objects will snap to
*
* Will be emitted whenever an element's movement invokes a snapping
* event (usually by its controlling #GESClip being moved) because its
* event during an edit (usually of one of its ancestors) because its
* start or end point lies within the #GESTimeline:snapping-distance of
* another element's start or end point.
*
* See #GESEditMode to see what can snap during an edit.
*
* Note that only up to one snapping-started signal will be emitted per
* element edit within a timeline.
*/
ges_timeline_signals[SNAPING_STARTED] =
g_signal_new ("snapping-started", G_TYPE_FROM_CLASS (klass),
@ -692,11 +693,10 @@ ges_timeline_class_init (GESTimelineClass * klass)
* @position: The position where the two objects were to be snapped to
*
* Will be emitted whenever a snapping event ends. After a snap event
* has started (see #GESTimeline::snapping-started), it can end because
* the element whose movement created the snap event has since moved
* outside of the #GESTimeline:snapping-distance before its position was
* committed. It can also end because the element's movement was ended
* by a timeline being committed.
* has started (see #GESTimeline::snapping-started), it can later end
* because either another timeline edit has occurred (which may or may
* not have created a new snapping event), or because the timeline has
* been committed.
*/
ges_timeline_signals[SNAPING_ENDED] =
g_signal_new ("snapping-ended", G_TYPE_FROM_CLASS (klass),
@ -805,7 +805,6 @@ ges_timeline_init (GESTimeline * self)
self->priv->last_snap_ts = GST_CLOCK_TIME_NONE;
priv->priv_tracks = NULL;
priv->needs_transitions_update = TRUE;
priv->all_elements =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, gst_object_unref);
@ -1029,8 +1028,8 @@ _create_auto_transition_from_transitions (GESTimeline * timeline,
}
void
ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1,
GESTimelineElement * elem2, GstClockTime snap_time)
ges_timeline_emit_snapping (GESTimeline * timeline, GESTrackElement * elem1,
GESTrackElement * elem2, GstClockTime snap_time)
{
GESTimelinePrivate *priv = timeline->priv;
GstClockTime last_snap_ts = timeline->priv->last_snap_ts;
@ -1048,15 +1047,6 @@ ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1,
}
g_assert (elem1 != elem2);
if (GES_IS_CLIP (elem1)) {
g_assert (GES_CONTAINER_CHILDREN (elem1));
elem1 = GES_CONTAINER_CHILDREN (elem1)->data;
}
if (GES_IS_CLIP (elem2)) {
g_assert (GES_CONTAINER_CHILDREN (elem2));
elem2 = GES_CONTAINER_CHILDREN (elem2)->data;
}
if (last_snap_ts != snap_time) {
g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
@ -1067,10 +1057,9 @@ ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1,
}
if (!GST_CLOCK_TIME_IS_VALID (timeline->priv->last_snap_ts)) {
priv->last_snaped1 = (GESTrackElement *) elem1;
priv->last_snaped2 = (GESTrackElement *) elem2;
priv->last_snaped1 = elem1;
priv->last_snaped2 = elem2;
timeline->priv->last_snap_ts = snap_time;
g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
elem1, elem2, snap_time);
}
@ -1129,91 +1118,14 @@ done:
}
gboolean
ges_timeline_trim_object_simple (GESTimeline * timeline,
GESTimelineElement * element, guint32 new_layer_priority,
GList * layers, GESEdge edge, guint64 position, gboolean snapping)
{
return timeline_trim_object (timeline, element, new_layer_priority, layers,
edge, position);
}
gboolean
timeline_ripple_object (GESTimeline * timeline, GESTimelineElement * obj,
gint new_layer_priority, GList * layers, GESEdge edge, guint64 position)
{
gboolean res = TRUE;
guint64 new_duration;
GstClockTimeDiff diff;
switch (edge) {
case GES_EDGE_NONE:
GST_DEBUG ("Simply rippling");
diff = GST_CLOCK_DIFF (position, _START (obj));
timeline->priv->needs_transitions_update = FALSE;
res = timeline_tree_ripple (timeline->priv->tree,
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) -
(gint64) new_layer_priority, diff, obj,
GES_EDGE_NONE, timeline->priv->snapping_distance);
timeline->priv->needs_transitions_update = TRUE;
break;
case GES_EDGE_END:
GST_DEBUG ("Rippling end");
timeline->priv->needs_transitions_update = FALSE;
new_duration =
CLAMP (GST_CLOCK_DIFF (obj->start, position), 0,
GST_CLOCK_TIME_IS_VALID (obj->
maxduration) ? GST_CLOCK_DIFF (obj->inpoint,
obj->maxduration) : GST_CLOCK_TIME_NONE);
res =
timeline_tree_ripple (timeline->priv->tree,
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) -
(gint64) new_layer_priority, _DURATION (obj) - new_duration, obj,
GES_EDGE_END, timeline->priv->snapping_distance);
timeline->priv->needs_transitions_update = TRUE;
GST_DEBUG ("Done Rippling end");
break;
case GES_EDGE_START:
GST_INFO ("Ripple start doesn't make sense, trimming instead");
if (!timeline_trim_object (timeline, obj, -1, layers, edge, position))
goto error;
break;
default:
GST_DEBUG ("Can not ripple edge: %i", edge);
break;
}
return res;
error:
return FALSE;
}
gboolean
timeline_slide_object (GESTimeline * timeline, GESTrackElement * obj,
GList * layers, GESEdge edge, guint64 position)
{
/* FIXME implement me! */
GST_FIXME_OBJECT (timeline, "Slide mode editing not implemented yet");
return FALSE;
}
static gboolean
_trim_transition (GESTimeline * timeline, GESTimelineElement * element,
GESEdge edge, GstClockTime position)
static gint
_edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
GstClockTime position)
{
GList *tmp;
GESLayer *layer = ges_timeline_get_layer (timeline,
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element));
guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
GESLayer *layer = ges_timeline_get_layer (timeline, layer_prio);
if (!ges_layer_get_auto_transition (layer)) {
gst_object_unref (layer);
@ -1222,86 +1134,83 @@ _trim_transition (GESTimeline * timeline, GESTimelineElement * element,
gst_object_unref (layer);
for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
GESTimelineElement *replace;
GESAutoTransition *auto_transition = tmp->data;
if (GES_TIMELINE_ELEMENT (auto_transition->transition) == element ||
GES_TIMELINE_ELEMENT (auto_transition->transition_clip) == element) {
/* Trimming an auto transition means trimming its neighboors */
if (!auto_transition->positioning) {
if (edge == GES_EDGE_END) {
ges_container_edit (GES_CONTAINER (auto_transition->previous_clip),
NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, position);
} else {
ges_container_edit (GES_CONTAINER (auto_transition->next_clip),
NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, position);
}
return TRUE;
}
if (auto_transition->positioning) {
GST_ERROR_OBJECT (element, "Trying to edit an auto-transition "
"whilst it is being positioned");
return FALSE;
}
if (new_layer_priority != layer_prio) {
GST_WARNING_OBJECT (element, "Cannot edit an auto-transition to a "
"new layer");
return FALSE;
}
if (mode != GES_EDIT_MODE_TRIM) {
GST_WARNING_OBJECT (element, "Cannot edit an auto-transition "
"under the edit mode %i", mode);
return FALSE;
}
gboolean
timeline_trim_object (GESTimeline * timeline, GESTimelineElement * object,
guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position)
{
if ((GES_IS_TRANSITION (object) || GES_IS_TRANSITION_CLIP (object)) &&
!ELEMENT_FLAG_IS_SET (object, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
return _trim_transition (timeline, object, edge, position);
if (edge == GES_EDGE_END)
replace = GES_TIMELINE_ELEMENT (auto_transition->previous_clip);
else
replace = GES_TIMELINE_ELEMENT (auto_transition->next_clip);
GST_INFO_OBJECT (element, "Trimming clip %" 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 timeline_tree_trim (timeline->priv->tree,
GES_TIMELINE_ELEMENT (object), (gint64)
ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) -
(gint64) new_layer_priority,
edge == GES_EDGE_END ? GST_CLOCK_DIFF (position,
_START (object) + _DURATION (object)) : GST_CLOCK_DIFF (position,
GES_TIMELINE_ELEMENT_START (object)), edge,
timeline->priv->snapping_distance);
return -1;
}
gboolean
timeline_roll_object (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, GESEdge edge, guint64 position)
{
return timeline_tree_roll (timeline->priv->tree,
element,
(edge == GES_EDGE_END) ?
GST_CLOCK_DIFF (position, _END (element)) :
GST_CLOCK_DIFF (position, _START (element)),
edge, timeline->priv->snapping_distance);
}
gboolean
timeline_move_object (GESTimeline * timeline, GESTimelineElement * object,
guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position)
{
gboolean ret = FALSE;
GstClockTimeDiff offset = edge == GES_EDGE_END ?
GST_CLOCK_DIFF (position, _START (object) + _DURATION (object)) :
GST_CLOCK_DIFF (position, GES_TIMELINE_ELEMENT_START (object));
ret = timeline_tree_move (timeline->priv->tree,
GES_TIMELINE_ELEMENT (object), (gint64)
ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) -
(gint64) new_layer_priority, offset, edge,
timeline->priv->snapping_distance);
return ret;
}
gboolean
ges_timeline_move_object_simple (GESTimeline * timeline,
GESTimelineElement * element, GList * layers, GESEdge edge,
ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
guint64 position)
{
return timeline_move_object (timeline, element,
ges_timeline_element_get_layer_priority (element), NULL, edge, position);
GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ?
GST_CLOCK_DIFF (position, element->start + element->duration) :
GST_CLOCK_DIFF (position, element->start));
gint64 prio_diff = (gint64) ges_timeline_element_get_layer_priority (element)
- new_layer_priority;
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);
if (res != -1)
return res;
switch (mode) {
case GES_EDIT_MODE_RIPPLE:
return timeline_tree_ripple (timeline->priv->tree, element, prio_diff,
edge_diff, edge, timeline->priv->snapping_distance);
case GES_EDIT_MODE_TRIM:
return timeline_tree_trim (timeline->priv->tree, element, prio_diff,
edge_diff, edge, timeline->priv->snapping_distance);
case GES_EDIT_MODE_NORMAL:
return timeline_tree_move (timeline->priv->tree, element, prio_diff,
edge_diff, edge, timeline->priv->snapping_distance);
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);
case GES_EDIT_MODE_SLIDE:
GST_ERROR_OBJECT (element, "Sliding not implemented.");
return FALSE;
}
return FALSE;
}
void

View file

@ -1199,10 +1199,11 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
timeline = track->priv->timeline;
ges_timeline_element_set_timeline (el, timeline);
/* check that we haven't broken the timeline configuration by adding this
* 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,
NULL)) {
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration)) {
GST_WARNING_OBJECT (track,
"Could not add the track element %" GES_FORMAT
" to the track because it breaks the timeline " "configuration rules",

View file

@ -395,8 +395,7 @@ GST_START_TEST (test_single_layer_automatic_transition)
transition = objects->next->next->data;
assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
assert_equals_int (_START (transition), 500);
assert_equals_uint64 (_DURATION (transition), 750);
CHECK_OBJECT_PROPS (transition, 500, 0, 750);
g_list_free_full (objects, gst_object_unref);
fail_if (ges_timeline_element_set_start (src1, 250));

View file

@ -376,8 +376,6 @@ GST_START_TEST (test_snapping)
*/
fail_unless (ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip2),
5));
fail_unless (ges_timeline_element_roll_start (GES_TIMELINE_ELEMENT (clip2),
60));
DEEP_CHECK (clip, 30, 5, 32);
DEEP_CHECK (clip1, 20, 0, 10);
DEEP_CHECK (clip2, 62, 5, 60);
@ -912,9 +910,6 @@ GST_START_TEST (test_timeline_edition_mode)
CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47);
CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50);
fail_if (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_ROLL,
GES_EDGE_START, 59));
ges_deinit ();
}
@ -1010,8 +1005,6 @@ GST_START_TEST (test_groups)
fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 40) == TRUE);
fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 30) == TRUE);
CHECK_CLIP (c, 10, 0, 10, 1);
CHECK_CLIP (c1, 20, 0, 10, 2);
CHECK_CLIP (c2, 30, 0, 10, 2);

View file

@ -504,14 +504,14 @@ class TestEditing(common.GESSimpleTimelineTest):
def test_trim_start(self):
clip = self.append_clip()
self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10))
self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 10))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 10, 10),
]
])
self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_NONE, 0))
self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0))
self.assertTimelineTopology([
[ # Unique layer
(GES.TestClip, 10, 10),

View file

@ -13,11 +13,11 @@ check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=100
edit, element-name=clip, edit-mode=normal, position=1.0
edit, element-name=clip, edit-mode=edit_trim, source-frame=60
edit, element-name=clip, edit-mode=edit_trim, edge=start, source-frame=60
edit, element-name=clip, position=0
commit;
check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=60
edit, element-name=clip, edit-mode=edit_trim, edit-edge=end, source-frame=120
edit, element-name=clip, edit-mode=edit_trim, edge=start, source-frame=120
check-ges-properties, element-name=clip, start=0.5
stop