mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 15:51:11 +00:00
a46390ff56
This is implemented on top of a Tree that represents the whole timeline. SourceClips can not fully overlap anymore and the tests have been updated to take that into account. Some new tests were added to verify that behaviour in greater details
1256 lines
39 KiB
C
1256 lines
39 KiB
C
/* GStreamer Editing Services
|
|
* Copyright (C) 2019 Igalia S.L
|
|
* Author: 2019 Thibault Saunier <tsaunier@igalia.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "ges-timeline-tree.h"
|
|
#include "ges-internal.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (tree_debug);
|
|
#undef GST_CAT_DEFAULT
|
|
#define GST_CAT_DEFAULT tree_debug
|
|
|
|
#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? ((GstClockTimeDiff) _END (e)) : ((GstClockTimeDiff) _START (e)))
|
|
typedef struct
|
|
{
|
|
GstClockTime distance;
|
|
gboolean on_end_only;
|
|
gboolean on_start_only;
|
|
|
|
GESEdge edge;
|
|
GESTimelineElement *element;
|
|
|
|
GESTimelineElement *moving_element;
|
|
GESEdge moving_edge;
|
|
GstClockTimeDiff diff;
|
|
} SnappingData;
|
|
|
|
/* *INDENT-OFF* */
|
|
struct _TreeIterationData
|
|
{
|
|
GNode *root;
|
|
gboolean res;
|
|
|
|
GstClockTimeDiff start_diff;
|
|
GstClockTimeDiff inpoint_diff;
|
|
GstClockTimeDiff duration_diff;
|
|
gint64 priority_diff;
|
|
|
|
/* The element we are visiting */
|
|
GESTimelineElement *element;
|
|
|
|
/* All the TrackElement currently moving */
|
|
GList *movings;
|
|
|
|
/* Elements overlaping on the start/end of @element */
|
|
GESTimelineElement *overlaping_on_start;
|
|
GESTimelineElement *overlaping_on_end;
|
|
|
|
/* Timestamp after which elements will be rippled */
|
|
GstClockTime ripple_time;
|
|
|
|
SnappingData *snapping;
|
|
|
|
/* The edge being trimmed or rippled */
|
|
GESEdge edge;
|
|
GHashTable *moved_clips;
|
|
|
|
GList *neighbours;
|
|
} tree_iteration_data_init = {
|
|
.root = NULL,
|
|
.res = TRUE,
|
|
.start_diff = 0,
|
|
.inpoint_diff = 0,
|
|
.duration_diff = 0,
|
|
.priority_diff = 0,
|
|
.element = NULL,
|
|
.movings = NULL,
|
|
.overlaping_on_start = NULL,
|
|
.overlaping_on_end = NULL,
|
|
.ripple_time = GST_CLOCK_TIME_NONE,
|
|
.snapping = NULL,
|
|
.edge = GES_EDGE_NONE,
|
|
.moved_clips = NULL,
|
|
.neighbours = NULL,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
typedef struct _TreeIterationData TreeIterationData;
|
|
|
|
static void
|
|
clean_iteration_data (TreeIterationData * data)
|
|
{
|
|
g_list_free (data->neighbours);
|
|
g_list_free (data->movings);
|
|
if (data->moved_clips)
|
|
g_hash_table_unref (data->moved_clips);
|
|
}
|
|
|
|
void
|
|
timeline_tree_init_debug (void)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (tree_debug, "gestree",
|
|
GST_DEBUG_FG_YELLOW, "timeline tree");
|
|
}
|
|
|
|
|
|
static gboolean
|
|
print_node (GNode * node, gpointer unused_data)
|
|
{
|
|
if (G_NODE_IS_ROOT (node)) {
|
|
g_print ("Timeline: %p\n", node->data);
|
|
return FALSE;
|
|
}
|
|
|
|
g_print ("%*c- %" GES_FORMAT " - layer %" G_GINT32_FORMAT "\n",
|
|
2 * g_node_depth (node), ' ', GES_ARGS (node->data),
|
|
ges_timeline_element_get_layer_priority (node->data));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
timeline_tree_debug (GNode * root)
|
|
{
|
|
g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
|
|
(GNodeTraverseFunc) print_node, NULL);
|
|
}
|
|
|
|
static inline GESTimelineElement *
|
|
get_toplevel_container (gpointer element)
|
|
{
|
|
GESTimelineElement *ret =
|
|
ges_timeline_element_get_toplevel_parent ((GESTimelineElement
|
|
*) (element));
|
|
|
|
/* We own a ref to the elements ourself */
|
|
gst_object_unref (ret);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
timeline_tree_can_move_element_internal (GNode * root,
|
|
GESTimelineElement * element,
|
|
gint64 priority,
|
|
GstClockTimeDiff start,
|
|
GstClockTimeDiff inpoint,
|
|
GstClockTimeDiff duration,
|
|
GList * moving_track_elements,
|
|
GstClockTime ripple_time, SnappingData * snapping, GESEdge edge);
|
|
|
|
static GNode *
|
|
find_node (GNode * root, gpointer element)
|
|
{
|
|
return g_node_find (root, G_IN_ORDER, G_TRAVERSE_ALL, element);
|
|
}
|
|
|
|
static void
|
|
timeline_element_parent_cb (GESTimelineElement * child, GParamSpec * arg
|
|
G_GNUC_UNUSED, GNode * root)
|
|
{
|
|
GNode *new_parent_node = NULL, *node = find_node (root, child);
|
|
|
|
if (child->parent)
|
|
new_parent_node = find_node (root, child->parent);
|
|
|
|
if (!new_parent_node)
|
|
new_parent_node = root;
|
|
|
|
g_node_unlink (node);
|
|
g_node_prepend (new_parent_node, node);
|
|
}
|
|
|
|
void
|
|
timeline_tree_track_element (GNode * root, GESTimelineElement * element)
|
|
{
|
|
GNode *node;
|
|
GNode *parent;
|
|
GESTimelineElement *toplevel;
|
|
|
|
if (find_node (root, element)) {
|
|
return;
|
|
}
|
|
|
|
g_signal_connect (element, "notify::parent",
|
|
G_CALLBACK (timeline_element_parent_cb), root);
|
|
|
|
toplevel = get_toplevel_container (element);
|
|
if (toplevel == element) {
|
|
GST_DEBUG ("Tracking toplevel element %" GES_FORMAT, GES_ARGS (element));
|
|
|
|
node = g_node_prepend_data (root, element);
|
|
} else {
|
|
parent = find_node (root, element->parent);
|
|
GST_LOG ("%" GES_FORMAT "parent is %" GES_FORMAT, GES_ARGS (element),
|
|
GES_ARGS (element->parent));
|
|
g_assert (parent);
|
|
node = g_node_prepend_data (parent, element);
|
|
}
|
|
|
|
if (GES_IS_CONTAINER (element)) {
|
|
GList *tmp;
|
|
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
|
|
GNode *child_node = find_node (root, tmp->data);
|
|
|
|
if (child_node) {
|
|
g_node_unlink (child_node);
|
|
g_node_prepend (node, child_node);
|
|
} else {
|
|
timeline_tree_track_element (root, tmp->data);
|
|
}
|
|
}
|
|
}
|
|
|
|
timeline_update_duration (root->data);
|
|
}
|
|
|
|
void
|
|
timeline_tree_stop_tracking_element (GNode * root, GESTimelineElement * element)
|
|
{
|
|
GNode *node = find_node (root, element);
|
|
|
|
node = find_node (root, element);
|
|
|
|
/* Move children to the parent */
|
|
while (node->children) {
|
|
GNode *tmp = node->children;
|
|
g_node_unlink (tmp);
|
|
g_node_prepend (node->parent, tmp);
|
|
}
|
|
|
|
g_assert (node);
|
|
GST_DEBUG ("Stop tracking %" GES_FORMAT, GES_ARGS (element));
|
|
g_signal_handlers_disconnect_by_func (element, timeline_element_parent_cb,
|
|
root);
|
|
|
|
g_node_destroy (node);
|
|
timeline_update_duration (root->data);
|
|
}
|
|
|
|
static inline gboolean
|
|
check_can_move_to_layer (GESTimelineElement * element,
|
|
gint layer_priority_offset)
|
|
{
|
|
return (((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
|
|
layer_priority_offset) >= 0);
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
#define CHECK_AND_SNAP(diff_val,moving_edge_,edge_) \
|
|
if (snapping->distance >= ABS(diff_val) && ABS(diff_val) <= ABS(snapping->diff)) { \
|
|
snapping->element = element; \
|
|
snapping->edge = edge_; \
|
|
snapping->moving_element = moving_elem; \
|
|
snapping->moving_edge = moving_edge_; \
|
|
snapping->diff = (diff_val); \
|
|
GST_LOG("Snapping %" GES_FORMAT "with %" GES_FORMAT " - diff: %" G_GINT64_FORMAT "", GES_ARGS (moving_elem), GES_ARGS(element), (diff_val)); \
|
|
}
|
|
|
|
static void
|
|
check_snapping (GESTimelineElement * element, GESTimelineElement * moving_elem,
|
|
SnappingData * snapping, GstClockTime start, GstClockTime end,
|
|
GstClockTime moving_start, GstClockTime moving_end)
|
|
{
|
|
GstClockTimeDiff snap_end_end_diff;
|
|
GstClockTimeDiff snap_end_start_diff;
|
|
|
|
if (element == moving_elem)
|
|
return;
|
|
|
|
if (!snapping || (
|
|
GES_IS_CLIP (element->parent) && element->parent == moving_elem->parent))
|
|
return;
|
|
|
|
snap_end_end_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) end;
|
|
snap_end_start_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) start;
|
|
|
|
GST_DEBUG("Moving [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "] element [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "]", moving_start, moving_end, start, end);
|
|
/* Prefer snapping end */
|
|
if (!snapping->on_start_only) {
|
|
CHECK_AND_SNAP(snap_end_end_diff, GES_EDGE_END, GES_EDGE_END)
|
|
else CHECK_AND_SNAP(snap_end_start_diff, GES_EDGE_END, GES_EDGE_START)
|
|
}
|
|
|
|
if (!snapping->on_end_only) {
|
|
GstClockTimeDiff snap_start_end_diff = GST_CLOCK_DIFF(end, moving_start);
|
|
GstClockTimeDiff snap_start_start_diff = GST_CLOCK_DIFF(start, moving_start);
|
|
|
|
CHECK_AND_SNAP(snap_start_end_diff, GES_EDGE_START, GES_EDGE_END)
|
|
else CHECK_AND_SNAP(snap_start_start_diff, GES_EDGE_START, GES_EDGE_START)
|
|
}
|
|
}
|
|
#undef CHECK_AND_SNAP
|
|
/* *INDENT-ON* */
|
|
|
|
static gboolean
|
|
check_track_elements_overlaps_and_values (GNode * node,
|
|
TreeIterationData * data)
|
|
{
|
|
GESTimelineElement *e = (GESTimelineElement *) node->data;
|
|
GstClockTimeDiff moving_start, moving_end, start, inpoint, end, duration;
|
|
gint64 priority = ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e));
|
|
gint64 moving_priority =
|
|
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (data->element) -
|
|
data->priority_diff;
|
|
|
|
gboolean can_overlap = e != data->element, in_movings, rippling, moving;
|
|
|
|
if (!GES_IS_SOURCE (e))
|
|
return FALSE;
|
|
|
|
in_movings = ! !g_list_find (data->movings, e);
|
|
rippling = e != data->element && !in_movings
|
|
&& (GST_CLOCK_TIME_IS_VALID (data->ripple_time)
|
|
&& e->start >= data->ripple_time);
|
|
moving = in_movings || rippling || e == data->element;
|
|
|
|
start = (GstClockTimeDiff) e->start;
|
|
inpoint = (GstClockTimeDiff) e->inpoint;
|
|
end = (GstClockTimeDiff) e->start + e->duration;
|
|
duration = e->duration;
|
|
moving_start = GST_CLOCK_DIFF (data->start_diff, data->element->start);
|
|
moving_end =
|
|
GST_CLOCK_DIFF (data->duration_diff,
|
|
moving_start + data->element->duration);
|
|
|
|
if (moving) {
|
|
if (rippling) {
|
|
if (data->edge == GES_EDGE_END) {
|
|
/* Moving as rippled from the end of a previous element */
|
|
start -= data->duration_diff;
|
|
} else
|
|
start -= data->start_diff;
|
|
} else {
|
|
start -= data->start_diff;
|
|
if (GES_TIMELINE_ELEMENT_GET_CLASS (e)->set_inpoint)
|
|
inpoint -= data->inpoint_diff;
|
|
duration -= data->duration_diff;
|
|
}
|
|
end = start + duration;
|
|
priority -= data->priority_diff;
|
|
|
|
GST_DEBUG ("%s %" GES_FORMAT "to [%" G_GINT64_FORMAT "(%"
|
|
G_GINT64_FORMAT ") - %" G_GINT64_FORMAT "] - layer: %" G_GINT64_FORMAT,
|
|
rippling ? "Rippling" : "Moving", GES_ARGS (e), start, inpoint,
|
|
duration, priority);
|
|
}
|
|
|
|
/* Not in the same track */
|
|
if (ges_track_element_get_track ((GESTrackElement *) node->data) !=
|
|
ges_track_element_get_track ((GESTrackElement *) data->element)) {
|
|
GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT
|
|
" are not in the same track", GES_ARGS (node->data),
|
|
GES_ARGS (data->element));
|
|
can_overlap = FALSE;
|
|
}
|
|
|
|
/* Not in the same layer */
|
|
if (priority != moving_priority) {
|
|
GST_LOG ("%" GST_PTR_FORMAT " and %" GST_PTR_FORMAT
|
|
" are not on the same layer (%d != %" G_GINT64_FORMAT ")", node->data,
|
|
data->element, GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e),
|
|
moving_priority);
|
|
can_overlap = FALSE;
|
|
}
|
|
|
|
if (start < 0) {
|
|
GST_INFO ("%" GES_FORMAT "start would be %" G_GINT64_FORMAT " < 0",
|
|
GES_ARGS (e), start);
|
|
goto error;
|
|
}
|
|
|
|
if (duration < 0) {
|
|
GST_INFO ("%" GES_FORMAT "duration would be %" G_GINT64_FORMAT " < 0",
|
|
GES_ARGS (e), duration);
|
|
goto error;
|
|
}
|
|
|
|
if (priority < 0) {
|
|
GST_INFO ("%" GES_FORMAT "priority would be %" G_GINT64_FORMAT " < 0",
|
|
GES_ARGS (e), priority);
|
|
goto error;
|
|
}
|
|
|
|
if (inpoint < 0) {
|
|
GST_INFO ("%" GES_FORMAT " can't set inpoint %" G_GINT64_FORMAT,
|
|
GES_ARGS (e), inpoint);
|
|
goto error;
|
|
}
|
|
|
|
if (inpoint + duration > e->maxduration) {
|
|
GST_INFO ("%" GES_FORMAT " inpoint + duration %" G_GINT64_FORMAT
|
|
" > max_duration %" G_GINT64_FORMAT,
|
|
GES_ARGS (e), inpoint + duration, e->maxduration);
|
|
goto error;
|
|
}
|
|
|
|
if (!moving)
|
|
check_snapping (e, data->element, data->snapping, start, end, moving_start,
|
|
moving_end);
|
|
|
|
if (!can_overlap)
|
|
return FALSE;
|
|
|
|
if (start > moving_end || moving_start > end) {
|
|
/* They do not overlap at all */
|
|
GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT
|
|
" do not overlap at all.",
|
|
GES_ARGS (node->data), GES_ARGS (data->element));
|
|
return FALSE;
|
|
}
|
|
|
|
if ((moving_start <= start && moving_end >= end) ||
|
|
(moving_start >= start && moving_end <= end)) {
|
|
GST_INFO ("Fully overlaped: %s<%p> [%" G_GINT64_FORMAT " - %"
|
|
G_GINT64_FORMAT "] and %s<%p> [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT
|
|
" (%" G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
|
|
data->element, moving_start, moving_end, data->duration_diff);
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (moving_start < end && moving_start > start) {
|
|
GST_LOG ("Overlap start: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT
|
|
"] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%"
|
|
G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
|
|
data->element, moving_start, moving_end, data->duration_diff);
|
|
if (data->overlaping_on_start) {
|
|
GST_INFO ("Clip is overlapped by %s and %s at its start",
|
|
data->overlaping_on_start->name, e->name);
|
|
goto error;
|
|
}
|
|
|
|
data->overlaping_on_start = node->data;
|
|
} else if (moving_end > end && end > moving_start) {
|
|
GST_LOG ("Overlap end: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT
|
|
"] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%"
|
|
G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
|
|
data->element, moving_start, moving_end, data->duration_diff);
|
|
|
|
if (data->overlaping_on_end) {
|
|
GST_INFO ("Clip is overlapped by %s and %s at its end",
|
|
data->overlaping_on_end->name, e->name);
|
|
goto error;
|
|
}
|
|
data->overlaping_on_end = node->data;
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
error:
|
|
data->res = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
check_can_move_children (GNode * node, TreeIterationData * data)
|
|
{
|
|
GESTimelineElement *element = node->data;
|
|
GstClockTimeDiff start = GST_CLOCK_DIFF (data->start_diff, element->start);
|
|
GstClockTime inpoint = GST_CLOCK_DIFF (data->inpoint_diff, element->inpoint);
|
|
GstClockTime duration =
|
|
GST_CLOCK_DIFF (data->duration_diff, element->duration);
|
|
gint64 priority =
|
|
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
|
|
data->priority_diff;
|
|
if (element == data->element)
|
|
return FALSE;
|
|
|
|
data->res =
|
|
timeline_tree_can_move_element_internal (data->root, node->data, priority,
|
|
start, inpoint, duration, data->movings, data->ripple_time,
|
|
data->snapping, data->edge);
|
|
|
|
return !data->res;
|
|
}
|
|
|
|
static gboolean
|
|
timeline_tree_can_move_element_from_data (GNode * root,
|
|
TreeIterationData * data)
|
|
{
|
|
GNode *node = find_node (root, data->element);
|
|
|
|
g_assert (node);
|
|
if (G_NODE_IS_LEAF (node)) {
|
|
if (GES_IS_SOURCE (node->data)) {
|
|
g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
|
|
(GNodeTraverseFunc) check_track_elements_overlaps_and_values, data);
|
|
|
|
return data->res;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAFS, -1,
|
|
(GNodeTraverseFunc) check_can_move_children, data);
|
|
|
|
return data->res;
|
|
}
|
|
|
|
static gboolean
|
|
add_element_to_list (GNode * node, GList ** elements)
|
|
{
|
|
*elements = g_list_prepend (*elements, node->data);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
timeline_tree_can_move_element_internal (GNode * root,
|
|
GESTimelineElement * element, gint64 priority, GstClockTimeDiff start,
|
|
GstClockTimeDiff inpoint, GstClockTimeDiff duration,
|
|
GList * moving_track_elements, GstClockTime ripple_time,
|
|
SnappingData * snapping, GESEdge edge)
|
|
{
|
|
gboolean res;
|
|
TreeIterationData data = tree_iteration_data_init;
|
|
|
|
data.root = root;
|
|
data.start_diff = GST_CLOCK_DIFF (start, element->start);
|
|
data.inpoint_diff = GST_CLOCK_DIFF (inpoint, element->inpoint);
|
|
data.duration_diff = GST_CLOCK_DIFF (duration, element->duration);
|
|
data.priority_diff =
|
|
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority;
|
|
data.element = element;
|
|
data.movings = g_list_copy (moving_track_elements);
|
|
data.ripple_time = ripple_time;
|
|
data.snapping = snapping;
|
|
data.edge = edge;
|
|
|
|
if (GES_IS_SOURCE (element))
|
|
data.priority_diff =
|
|
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority;
|
|
|
|
res = timeline_tree_can_move_element_from_data (root, &data);
|
|
clean_iteration_data (&data);
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
timeline_tree_can_move_element (GNode * root,
|
|
GESTimelineElement * element, guint32 priority, GstClockTime start,
|
|
GstClockTime duration, GList * moving_track_elements)
|
|
{
|
|
GESTimelineElement *toplevel;
|
|
GstClockTimeDiff start_offset, duration_offset;
|
|
gint64 priority_diff;
|
|
|
|
toplevel = get_toplevel_container (element);
|
|
if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
|
|
ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE))
|
|
return TRUE;
|
|
|
|
start_offset = GST_CLOCK_DIFF (start, element->start);
|
|
duration_offset = GST_CLOCK_DIFF (duration, element->duration);
|
|
priority_diff =
|
|
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) -
|
|
(gint64) priority;
|
|
|
|
g_node_traverse (find_node (root, toplevel), G_IN_ORDER,
|
|
G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
|
|
&moving_track_elements);
|
|
|
|
return timeline_tree_can_move_element_internal (root, toplevel,
|
|
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) - priority_diff,
|
|
GST_CLOCK_DIFF (start_offset, toplevel->start),
|
|
toplevel->inpoint,
|
|
GST_CLOCK_DIFF (duration_offset, toplevel->duration),
|
|
moving_track_elements, GST_CLOCK_TIME_NONE, NULL, GES_EDGE_NONE);
|
|
}
|
|
|
|
static void
|
|
move_to_new_layer (GESTimelineElement * elem, gint layer_priority_offset)
|
|
{
|
|
guint32 nprio =
|
|
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) - layer_priority_offset;
|
|
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (elem);
|
|
|
|
if (!layer_priority_offset)
|
|
return;
|
|
|
|
GST_DEBUG ("%s moving from %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " (%"
|
|
G_GUINT32_FORMAT ")", elem->name, elem->priority, nprio,
|
|
layer_priority_offset);
|
|
if (GES_IS_CLIP (elem)) {
|
|
GESLayer *layer = ges_timeline_get_layer (timeline, nprio);
|
|
|
|
if (layer == NULL) {
|
|
do {
|
|
layer = ges_timeline_append_layer (timeline);
|
|
} while (ges_layer_get_priority (layer) < nprio);
|
|
} else {
|
|
gst_object_unref (layer);
|
|
}
|
|
|
|
ges_clip_move_to_layer (GES_CLIP (elem), layer);
|
|
} else if (GES_IS_GROUP (elem)) {
|
|
ges_timeline_element_set_priority (elem, nprio);
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
timeline_tree_ripple (GNode * root, gint64 layer_priority_offset,
|
|
GstClockTimeDiff offset, GESTimelineElement * rippled_element,
|
|
GESEdge edge, GstClockTime snapping_distance)
|
|
{
|
|
GNode *node;
|
|
GHashTableIter iter;
|
|
GESTimelineElement *elem;
|
|
GstClockTimeDiff start, duration;
|
|
gboolean res = TRUE;
|
|
GHashTable *to_move = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
GList *moving_track_elements = NULL;
|
|
SnappingData snapping = {
|
|
.distance = snapping_distance,
|
|
.on_end_only = edge == GES_EDGE_END,
|
|
.on_start_only = FALSE,
|
|
.element = NULL,
|
|
.edge = GES_EDGE_NONE,
|
|
.diff = (GstClockTimeDiff) snapping_distance,
|
|
};
|
|
gint64 new_layer_priority =
|
|
((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (rippled_element)) -
|
|
layer_priority_offset;
|
|
GESTimelineElement *ripple_toplevel =
|
|
get_toplevel_container (rippled_element);
|
|
GstClockTimeDiff ripple_time = ELEMENT_EDGE_VALUE (rippled_element, edge);
|
|
|
|
if (edge == GES_EDGE_END) {
|
|
if (ripple_toplevel != rippled_element) {
|
|
GST_FIXME ("Trying to ripple end %" GES_FORMAT " but in %" GES_FORMAT
|
|
" we do not know how to do that yet!",
|
|
GES_ARGS (rippled_element), GES_ARGS (ripple_toplevel));
|
|
goto error;
|
|
}
|
|
} else {
|
|
g_node_traverse (find_node (root, ripple_toplevel), G_IN_ORDER,
|
|
G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
|
|
&moving_track_elements);
|
|
}
|
|
|
|
GST_INFO ("Moving %" GES_FORMAT " with offset %" G_GINT64_FORMAT "",
|
|
GES_ARGS (ripple_toplevel), offset);
|
|
|
|
if (edge == GES_EDGE_END) {
|
|
start = _START (rippled_element);
|
|
duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element));
|
|
} else {
|
|
start = GST_CLOCK_DIFF (offset, _START (rippled_element));
|
|
duration = _DURATION (rippled_element);
|
|
}
|
|
|
|
if (!timeline_tree_can_move_element_internal (root, rippled_element,
|
|
new_layer_priority, start, rippled_element->inpoint, duration, NULL,
|
|
ripple_time, snapping_distance ? &snapping : NULL, edge)) {
|
|
goto error;
|
|
}
|
|
|
|
if (snapping_distance) {
|
|
if (snapping.element) {
|
|
offset =
|
|
GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
|
|
ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
|
|
|
|
if (edge == GES_EDGE_END) {
|
|
start = _START (rippled_element);
|
|
duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element));
|
|
} else {
|
|
start = GST_CLOCK_DIFF (offset, _START (rippled_element));
|
|
duration = _DURATION (rippled_element);
|
|
}
|
|
|
|
GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT "",
|
|
GES_ARGS (snapping.element),
|
|
snapping.edge == GES_EDGE_END ? "end" : "start",
|
|
ELEMENT_EDGE_VALUE (snapping.element, snapping.edge));
|
|
if (!timeline_tree_can_move_element_internal (root, rippled_element,
|
|
new_layer_priority, start, rippled_element->inpoint, duration,
|
|
NULL, ripple_time, NULL, edge)) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ges_timeline_emit_snapping (root->data, rippled_element, snapping.element,
|
|
snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
|
|
snapping.edge) : GST_CLOCK_TIME_NONE);
|
|
}
|
|
|
|
/* Make sure we can ripple all toplevels after the rippled element */
|
|
for (node = root->children; node; node = node->next) {
|
|
GESTimelineElement *toplevel = get_toplevel_container (node->data);
|
|
|
|
if (GES_TIMELINE_ELEMENT_START (toplevel) < ripple_time
|
|
&& (edge == GES_EDGE_END || toplevel != ripple_toplevel))
|
|
continue;
|
|
|
|
if (!timeline_tree_can_move_element_internal (root, node->data,
|
|
((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data)) -
|
|
layer_priority_offset,
|
|
GST_CLOCK_DIFF (offset, _START (node->data)),
|
|
_INPOINT (node->data),
|
|
_DURATION (node->data), moving_track_elements, ripple_time, NULL,
|
|
GES_EDGE_NONE)) {
|
|
goto error;
|
|
}
|
|
|
|
if (!check_can_move_to_layer (toplevel, layer_priority_offset)) {
|
|
GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
|
|
GES_ARGS (toplevel));
|
|
goto error;
|
|
}
|
|
|
|
g_hash_table_add (to_move, toplevel);
|
|
}
|
|
|
|
if (edge == GES_EDGE_END) {
|
|
if (!check_can_move_to_layer (rippled_element, layer_priority_offset)) {
|
|
GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
|
|
GES_ARGS (rippled_element));
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (duration < 0) {
|
|
GST_INFO ("Would set duration to %" G_GINT64_FORMAT " <= 0", duration);
|
|
goto error;
|
|
}
|
|
|
|
ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
ges_timeline_element_set_duration (rippled_element, duration);
|
|
ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, to_move);
|
|
while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL)) {
|
|
GST_LOG ("Moving %" GES_FORMAT " to %" G_GINT64_FORMAT " - layer %"
|
|
G_GINT64_FORMAT "", GES_ARGS (elem),
|
|
GES_TIMELINE_ELEMENT_START (elem) - offset,
|
|
(gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) -
|
|
layer_priority_offset);
|
|
|
|
ELEMENT_SET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
ges_timeline_element_set_start (elem,
|
|
GST_CLOCK_DIFF (offset, GES_TIMELINE_ELEMENT_START (elem)));
|
|
move_to_new_layer (elem, layer_priority_offset);
|
|
ELEMENT_UNSET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
}
|
|
|
|
ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
if (edge == GES_EDGE_END)
|
|
move_to_new_layer (rippled_element, layer_priority_offset);
|
|
ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
|
|
timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
|
|
timeline_update_transition (root->data);
|
|
timeline_update_duration (root->data);
|
|
|
|
done:
|
|
g_hash_table_unref (to_move);
|
|
g_list_free (moving_track_elements);
|
|
return res;
|
|
|
|
error:
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
static gboolean
|
|
check_trim_child (GNode * node, TreeIterationData * data)
|
|
{
|
|
GESTimelineElement *e = node->data;
|
|
GstClockTimeDiff n_start = GST_CLOCK_DIFF (data->start_diff, e->start);
|
|
GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (data->inpoint_diff, e->inpoint);
|
|
GstClockTimeDiff n_duration = data->edge == GES_EDGE_END ?
|
|
GST_CLOCK_DIFF (data->duration_diff, e->duration) :
|
|
GST_CLOCK_DIFF (n_start, (GstClockTimeDiff) e->start + e->duration);
|
|
|
|
if (!timeline_tree_can_move_element_internal (data->root, e,
|
|
(gint64) ges_timeline_element_get_layer_priority (e) -
|
|
data->priority_diff, n_start, n_inpoint, n_duration, NULL,
|
|
GST_CLOCK_TIME_NONE, data->snapping, GES_EDGE_NONE))
|
|
goto error;
|
|
|
|
if (GES_IS_CLIP (e->parent))
|
|
g_hash_table_add (data->moved_clips, e->parent);
|
|
else if (GES_IS_CLIP (e))
|
|
g_hash_table_add (data->moved_clips, e);
|
|
|
|
return FALSE;
|
|
|
|
error:
|
|
data->res = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
timeline_tree_can_trim_element_internal (GNode * root, TreeIterationData * data)
|
|
{
|
|
g_node_traverse (find_node (root, data->element), G_IN_ORDER,
|
|
G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) check_trim_child, data);
|
|
|
|
return data->res;
|
|
}
|
|
|
|
static void
|
|
trim_simple (GESTimelineElement * element, GstClockTimeDiff offset,
|
|
GESEdge edge)
|
|
{
|
|
ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
if (edge == GES_EDGE_END) {
|
|
ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
|
|
element->duration));
|
|
} else {
|
|
ges_timeline_element_set_start (element, GST_CLOCK_DIFF (offset,
|
|
element->start));
|
|
ges_timeline_element_set_inpoint (element, GST_CLOCK_DIFF (offset,
|
|
element->inpoint));
|
|
ges_timeline_element_set_duration (element, element->duration + offset);
|
|
}
|
|
GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element));
|
|
ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
}
|
|
|
|
#define SET_TRIMMING_DATA(data, _edge, offset) G_STMT_START { \
|
|
data.edge = (_edge); \
|
|
data.start_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
|
|
data.inpoint_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
|
|
data.duration_diff = (_edge) == GES_EDGE_END ? (offset) : -(offset); \
|
|
} G_STMT_END
|
|
|
|
|
|
gboolean
|
|
timeline_tree_trim (GNode * root, GESTimelineElement * element,
|
|
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
|
|
GstClockTime snapping_distance)
|
|
{
|
|
GHashTableIter iter;
|
|
gboolean res = TRUE;
|
|
GESTimelineElement *elem;
|
|
gint64 new_layer_priority =
|
|
((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element)) -
|
|
layer_priority_offset;
|
|
SnappingData snapping = {
|
|
.distance = snapping_distance,
|
|
.on_end_only = edge == GES_EDGE_END,
|
|
.on_start_only = edge != GES_EDGE_END,
|
|
.element = NULL,
|
|
.edge = GES_EDGE_NONE,
|
|
.diff = (GstClockTimeDiff) snapping_distance,
|
|
};
|
|
TreeIterationData data = tree_iteration_data_init;
|
|
|
|
data.root = root;
|
|
data.element = element;
|
|
data.priority_diff =
|
|
(gint64) ges_timeline_element_get_layer_priority (element) -
|
|
new_layer_priority;
|
|
data.snapping = snapping_distance ? &snapping : NULL;
|
|
data.moved_clips = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
SET_TRIMMING_DATA (data, edge, offset);
|
|
GST_INFO ("%" GES_FORMAT " trimming %s with offset %" G_GINT64_FORMAT "",
|
|
GES_ARGS (element), edge == GES_EDGE_END ? "end" : "start", offset);
|
|
g_node_traverse (find_node (root, element), G_IN_ORDER,
|
|
G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
|
|
&data.movings);
|
|
|
|
if (!timeline_tree_can_trim_element_internal (root, &data)) {
|
|
GST_INFO ("Can not trim object.");
|
|
goto error;
|
|
}
|
|
|
|
if (snapping_distance) {
|
|
if (snapping.element) {
|
|
offset =
|
|
GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
|
|
ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
|
|
|
|
GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT
|
|
" -- offset: %" G_GINT64_FORMAT "", GES_ARGS (snapping.element),
|
|
snapping.edge == GES_EDGE_END ? "end" : "start",
|
|
ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), offset);
|
|
}
|
|
|
|
ges_timeline_emit_snapping (root->data, element,
|
|
snapping.element,
|
|
snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
|
|
snapping.edge) : GST_CLOCK_TIME_NONE);
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, data.moved_clips);
|
|
while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL))
|
|
trim_simple (elem, offset, edge);
|
|
|
|
timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
|
|
timeline_update_transition (root->data);
|
|
timeline_update_duration (root->data);
|
|
|
|
return TRUE;
|
|
|
|
done:
|
|
clean_iteration_data (&data);
|
|
return res;
|
|
|
|
error:
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
gboolean
|
|
timeline_tree_move (GNode * root, GESTimelineElement * element,
|
|
gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
|
|
GstClockTime snapping_distance)
|
|
{
|
|
gboolean res = TRUE;
|
|
GESTimelineElement *toplevel = get_toplevel_container (element);
|
|
TreeIterationData data = tree_iteration_data_init;
|
|
SnappingData snapping = {
|
|
.distance = snapping_distance,
|
|
.on_end_only = edge == GES_EDGE_END,
|
|
.on_start_only = edge == GES_EDGE_END,
|
|
.element = NULL,
|
|
.edge = GES_EDGE_NONE,
|
|
.diff = (GstClockTimeDiff) snapping_distance,
|
|
};
|
|
|
|
data.root = root;
|
|
data.element = edge == GES_EDGE_END ? element : toplevel;
|
|
data.edge = edge;
|
|
data.priority_diff = layer_priority_offset;
|
|
data.snapping = snapping_distance ? &snapping : NULL;
|
|
data.start_diff = edge == GES_EDGE_END ? 0 : offset;
|
|
data.duration_diff = edge == GES_EDGE_END ? offset : 0;
|
|
|
|
GST_INFO ("%" GES_FORMAT
|
|
" moving %s with offset %" G_GINT64_FORMAT ", (snaping distance: %"
|
|
G_GINT64_FORMAT ")", GES_ARGS (element),
|
|
edge == GES_EDGE_END ? "end" : "start", offset, snapping_distance);
|
|
g_node_traverse (find_node (root, data.element), G_IN_ORDER,
|
|
G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
|
|
&data.movings);
|
|
|
|
if (!timeline_tree_can_move_element_from_data (root, &data)) {
|
|
GST_INFO ("Can not move object.");
|
|
return FALSE;
|
|
}
|
|
|
|
if (snapping_distance) {
|
|
if (snapping.element) {
|
|
gint64 noffset =
|
|
GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
|
|
ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
|
|
|
|
GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT
|
|
"%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT
|
|
" (previous offset: %" G_GINT64_FORMAT ")",
|
|
GES_ARGS (snapping.moving_element),
|
|
snapping.moving_edge == GES_EDGE_END ? "end" : "start",
|
|
GES_ARGS (snapping.element),
|
|
snapping.edge == GES_EDGE_END ? "end" : "start",
|
|
ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset,
|
|
offset);
|
|
offset = noffset;
|
|
data.start_diff = edge == GES_EDGE_END ? 0 : offset;
|
|
data.duration_diff = edge == GES_EDGE_END ? offset : 0;
|
|
data.snapping = NULL;
|
|
if (!timeline_tree_can_move_element_from_data (root, &data)) {
|
|
GST_INFO ("Can not move object.");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ges_timeline_emit_snapping (root->data, element,
|
|
snapping.element,
|
|
snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
|
|
snapping.edge) : GST_CLOCK_TIME_NONE);
|
|
}
|
|
|
|
if (!check_can_move_to_layer (toplevel, layer_priority_offset)) {
|
|
GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
|
|
GES_ARGS (toplevel));
|
|
goto error;
|
|
}
|
|
|
|
ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
if (edge == GES_EDGE_END)
|
|
ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
|
|
element->duration));
|
|
else
|
|
ges_timeline_element_set_start (toplevel, GST_CLOCK_DIFF (offset,
|
|
toplevel->start));
|
|
move_to_new_layer (toplevel, layer_priority_offset);
|
|
ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
|
|
timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
|
|
timeline_update_transition (root->data);
|
|
timeline_update_duration (root->data);
|
|
|
|
GST_LOG ("Moved %" GES_FORMAT, GES_ARGS (element));
|
|
|
|
return res;
|
|
|
|
error:
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
find_neighbour (GNode * node, TreeIterationData * data)
|
|
{
|
|
gboolean in_same_track = FALSE;
|
|
GList *tmp;
|
|
|
|
if (!GES_IS_SOURCE (node->data)) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
for (tmp = GES_CONTAINER_CHILDREN (data->element); tmp; tmp = tmp->next) {
|
|
if (tmp->data == node->data)
|
|
return FALSE;
|
|
|
|
if (ges_track_element_get_track (node->data) ==
|
|
ges_track_element_get_track (tmp->data)) {
|
|
in_same_track = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!in_same_track) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ELEMENT_EDGE_VALUE (node->data,
|
|
data->edge == GES_EDGE_START ? GES_EDGE_END : GES_EDGE_START) ==
|
|
ELEMENT_EDGE_VALUE (data->element, data->edge)) {
|
|
if (!g_list_find (data->neighbours,
|
|
GES_TIMELINE_ELEMENT_PARENT (node->data)))
|
|
data->neighbours =
|
|
g_list_prepend (data->neighbours,
|
|
GES_TIMELINE_ELEMENT_PARENT (node->data));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
timeline_tree_roll (GNode * root, GESTimelineElement * element,
|
|
GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance)
|
|
{
|
|
gboolean res = TRUE;
|
|
GList *tmp;
|
|
GESEdge neighbour_edge;
|
|
TreeIterationData data = tree_iteration_data_init;
|
|
SnappingData snapping = {
|
|
.distance = snapping_distance,
|
|
.on_end_only = edge == GES_EDGE_END,
|
|
.on_start_only = edge == GES_EDGE_END,
|
|
.element = NULL,
|
|
.edge = GES_EDGE_NONE,
|
|
.moving_edge = GES_EDGE_NONE,
|
|
.diff = (GstClockTimeDiff) snapping_distance,
|
|
};
|
|
|
|
data.root = root;
|
|
data.element = element;
|
|
data.edge = edge;
|
|
data.snapping = snapping_distance ? &snapping : NULL;
|
|
data.start_diff = edge == GES_EDGE_END ? 0 : offset;
|
|
data.inpoint_diff = edge == GES_EDGE_END ? 0 : offset;
|
|
data.duration_diff = edge == GES_EDGE_END ? offset : -offset;
|
|
data.ripple_time = GST_CLOCK_TIME_NONE;
|
|
neighbour_edge = data.edge == GES_EDGE_END ? GES_EDGE_START : GES_EDGE_END;
|
|
|
|
SET_TRIMMING_DATA (data, edge, offset);
|
|
g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
|
|
(GNodeTraverseFunc) find_neighbour, &data);
|
|
|
|
if (data.neighbours == NULL) {
|
|
GST_INFO ("%s doesn't have any direct neighbour on edge %s",
|
|
element->name, ges_edge_name (edge));
|
|
|
|
return timeline_tree_trim (root, element, 0, offset, edge,
|
|
snapping_distance);
|
|
}
|
|
|
|
GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
|
|
GES_ARGS (data.element), ges_edge_name (edge), offset);
|
|
|
|
if (!timeline_tree_can_move_element_from_data (root, &data))
|
|
goto error;
|
|
|
|
|
|
if (snapping_distance) {
|
|
if (snapping.element) {
|
|
gint64 noffset =
|
|
GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
|
|
ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
|
|
|
|
GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT
|
|
"%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT
|
|
" (previous offset: %" G_GINT64_FORMAT ")",
|
|
GES_ARGS (snapping.moving_element),
|
|
snapping.moving_edge == GES_EDGE_END ? "end" : "start",
|
|
GES_ARGS (snapping.element),
|
|
snapping.edge == GES_EDGE_END ? "end" : "start",
|
|
ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset,
|
|
offset);
|
|
offset = noffset;
|
|
|
|
SET_TRIMMING_DATA (data, edge, offset);
|
|
|
|
if (!timeline_tree_can_move_element_from_data (root, &data)) {
|
|
GST_INFO ("Can not move object.");
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (snapping_distance && snapping.element) {
|
|
ges_timeline_emit_snapping (root->data, element,
|
|
snapping.element,
|
|
snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
|
|
snapping.edge) : GST_CLOCK_TIME_NONE);
|
|
}
|
|
|
|
data.snapping = NULL;
|
|
SET_TRIMMING_DATA (data, neighbour_edge, offset);
|
|
for (tmp = data.neighbours; tmp; tmp = tmp->next) {
|
|
data.element = tmp->data;
|
|
|
|
GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
|
|
GES_ARGS (data.element), ges_edge_name (data.edge), offset);
|
|
if (!timeline_tree_can_move_element_from_data (root, &data)) {
|
|
GST_INFO ("Can not move object.");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
trim_simple (element, offset, edge);
|
|
for (tmp = data.neighbours; tmp; tmp = tmp->next)
|
|
trim_simple (tmp->data, offset, data.edge);
|
|
|
|
done:
|
|
timeline_update_duration (root->data);
|
|
return res;
|
|
|
|
error:
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
static void
|
|
create_transition_if_needed (GESTimeline * timeline, GESTrackElement * prev,
|
|
GESTrackElement * next, GESTreeGetAutoTransitionFunc get_auto_transition)
|
|
{
|
|
GstClockTime duration = _END (prev) - _START (next);
|
|
GESAutoTransition *trans =
|
|
get_auto_transition (timeline, prev, next, duration);
|
|
|
|
if (!trans) {
|
|
GESLayer *layer = ges_timeline_get_layer (timeline,
|
|
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev));
|
|
gst_object_unref (layer);
|
|
|
|
GST_INFO ("Creating transition [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT
|
|
"]", _START (next), duration);
|
|
ges_timeline_create_transition (timeline, prev, next, NULL, layer,
|
|
_START (next), duration);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
create_transitions (GNode * node,
|
|
GESTreeGetAutoTransitionFunc get_auto_transition)
|
|
{
|
|
TreeIterationData data = tree_iteration_data_init;
|
|
GESTimeline *timeline;
|
|
GESLayer *layer;
|
|
|
|
if (G_NODE_IS_ROOT (node))
|
|
return FALSE;
|
|
|
|
timeline = GES_TIMELINE_ELEMENT_TIMELINE (node->data);
|
|
data.element = node->data;
|
|
if (!GES_IS_SOURCE (node->data))
|
|
return FALSE;
|
|
|
|
if (!timeline) {
|
|
GST_INFO ("%" GES_FORMAT " not in timeline yet", GES_ARGS (node->data));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
layer =
|
|
ges_timeline_get_layer (timeline,
|
|
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data));
|
|
gst_object_unref (layer);
|
|
|
|
if (!ges_layer_get_auto_transition (layer))
|
|
return FALSE;
|
|
|
|
g_node_traverse (g_node_get_root (node), G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
|
|
(GNodeTraverseFunc) check_track_elements_overlaps_and_values, &data);
|
|
|
|
if (data.overlaping_on_start)
|
|
create_transition_if_needed (timeline,
|
|
GES_TRACK_ELEMENT (data.overlaping_on_start), node->data,
|
|
get_auto_transition);
|
|
|
|
if (data.overlaping_on_end)
|
|
create_transition_if_needed (timeline, node->data,
|
|
GES_TRACK_ELEMENT (data.overlaping_on_end), get_auto_transition);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
timeline_tree_create_transitions (GNode * root,
|
|
GESTreeGetAutoTransitionFunc get_auto_transition)
|
|
{
|
|
g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
|
|
(GNodeTraverseFunc) create_transitions, get_auto_transition);
|
|
}
|
|
|
|
static gboolean
|
|
compute_duration (GNode * node, GstClockTime * duration)
|
|
{
|
|
*duration = MAX (_END (node->data), *duration);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
GstClockTime
|
|
timeline_tree_get_duration (GNode * root)
|
|
{
|
|
GstClockTime duration = 0;
|
|
|
|
if (root->children)
|
|
g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
|
|
(GNodeTraverseFunc) compute_duration, &duration);
|
|
|
|
return duration;
|
|
}
|