mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 16:26:39 +00:00
6b7c658b6a
We were implementing the logic for moving/trimming elements specific to SourceClip but this was not correct ass the new timeline tree allows us to handle that for all element types in a generic and nice way. This make us need to have groups trimming properly implemented in the timeline tree, leading to some fixes in the group tests. This adds tests for the various cases known to not be handled properly by the previous code. Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/92
1335 lines
42 KiB
C
1335 lines
42 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;
|
|
|
|
/* Data related to trimming groups */
|
|
GstClockTime trim_group_start;
|
|
GstClockTime trim_group_end;
|
|
} 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,
|
|
.trim_group_start = GST_CLOCK_TIME_NONE,
|
|
.trim_group_end = GST_CLOCK_TIME_NONE,
|
|
};
|
|
/* *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;
|
|
gboolean res;
|
|
GList *local_moving_track_elements = g_list_copy (moving_track_elements);
|
|
|
|
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,
|
|
&local_moving_track_elements);
|
|
|
|
res = 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),
|
|
local_moving_track_elements, GST_CLOCK_TIME_NONE, NULL, GES_EDGE_NONE);
|
|
|
|
g_list_free (local_moving_track_elements);
|
|
return res;
|
|
}
|
|
|
|
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
|
|
trim_group_get_vals (TreeIterationData * data, GESTimelineElement * e,
|
|
GstClockTimeDiff * n_start, GstClockTimeDiff * n_inpoint,
|
|
GstClockTimeDiff * n_duration)
|
|
{
|
|
GstClockTimeDiff offset;
|
|
GstClockTimeDiff group_nstart =
|
|
GST_CLOCK_DIFF (data->start_diff, data->trim_group_start);
|
|
GstClockTimeDiff group_nend =
|
|
GST_CLOCK_DIFF (data->duration_diff, data->trim_group_end);
|
|
|
|
if (data->edge == GES_EDGE_START &&
|
|
((group_nstart >= e->start) || (e->start == data->trim_group_start))) {
|
|
|
|
offset = GST_CLOCK_DIFF (group_nstart, e->start);
|
|
*n_start = group_nstart;
|
|
*n_inpoint = GST_CLOCK_DIFF (offset, e->inpoint);
|
|
*n_duration =
|
|
GST_CLOCK_DIFF (*n_start, (GstClockTimeDiff) e->start + e->duration);
|
|
|
|
GST_DEBUG_OBJECT (data->element, "Trimming %" GES_FORMAT " start",
|
|
GES_ARGS (e));
|
|
return TRUE;
|
|
} else if (data->edge == GES_EDGE_END &&
|
|
((group_nend <= _END (e)) || (_END (e) == data->trim_group_end))) {
|
|
|
|
offset = GST_CLOCK_DIFF (group_nend, _END (e));
|
|
*n_start = e->start;
|
|
*n_inpoint = e->inpoint;
|
|
*n_duration = GST_CLOCK_DIFF (offset, e->duration);
|
|
|
|
GST_DEBUG_OBJECT (data->element, "Trimming %" GES_FORMAT " end",
|
|
GES_ARGS (e));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Ignoring child */
|
|
return FALSE;
|
|
}
|
|
|
|
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 (GST_CLOCK_TIME_IS_VALID (data->trim_group_start)
|
|
&& !trim_group_get_vals (data, e, &n_start, &n_inpoint, &n_duration)) {
|
|
GST_DEBUG_OBJECT (data->element, "Not trimming");
|
|
return FALSE;
|
|
}
|
|
|
|
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, TreeIterationData * data)
|
|
{
|
|
GESTimelineElement *toplevel =
|
|
ges_timeline_element_get_toplevel_parent (element);
|
|
|
|
GstClockTimeDiff n_start = GST_CLOCK_DIFF (offset, element->start);
|
|
GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (offset, element->inpoint);
|
|
GstClockTimeDiff n_duration = edge == GES_EDGE_END
|
|
? GST_CLOCK_DIFF (offset, element->duration)
|
|
: element->duration + offset;
|
|
|
|
if (data && GST_CLOCK_TIME_IS_VALID (data->trim_group_start))
|
|
g_assert (trim_group_get_vals (data, element, &n_start, &n_inpoint,
|
|
&n_duration));
|
|
|
|
ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
if (edge != GES_EDGE_END) {
|
|
ges_timeline_element_set_start (element, n_start);
|
|
ges_timeline_element_set_inpoint (element, n_inpoint);
|
|
}
|
|
ges_timeline_element_set_duration (element, n_duration);
|
|
|
|
GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element));
|
|
ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
gst_object_unref (toplevel);
|
|
}
|
|
|
|
#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); \
|
|
if (GES_IS_GROUP (data.element)) {\
|
|
data.trim_group_start = data.element->start;\
|
|
data.trim_group_end = _END (data.element); \
|
|
} \
|
|
} 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;
|
|
|
|
/* Make sure to check all children of clips */
|
|
if (GES_IS_TRACK_ELEMENT (element) && element->parent)
|
|
element = element->parent;
|
|
|
|
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, get_toplevel_container (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, &data);
|
|
|
|
timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
|
|
timeline_update_transition (root->data);
|
|
timeline_update_duration (root->data);
|
|
|
|
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.");
|
|
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;
|
|
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));
|
|
|
|
done:
|
|
clean_iteration_data (&data);
|
|
return res;
|
|
|
|
error:
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
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, NULL);
|
|
for (tmp = data.neighbours; tmp; tmp = tmp->next)
|
|
trim_simple (tmp->data, offset, data.edge, NULL);
|
|
|
|
done:
|
|
timeline_update_duration (root->data);
|
|
g_list_free (data.neighbours);
|
|
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;
|
|
}
|