mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-03 06:56:46 +00:00
f9f30c4ced
a.k.a muting layers. Adding unit tests and making sure serialization works properly
1362 lines
42 KiB
C
1362 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;
|
|
}
|
|
|
|
static gboolean
|
|
reset_layer_activness (GNode * node, GESLayer * layer)
|
|
{
|
|
GESTrack *track;
|
|
|
|
|
|
if (!GES_IS_TRACK_ELEMENT (node->data))
|
|
return FALSE;
|
|
|
|
track = ges_track_element_get_track (node->data);
|
|
if (!track || (ges_timeline_element_get_layer_priority (node->data) !=
|
|
ges_layer_get_priority (layer)))
|
|
return FALSE;
|
|
|
|
ges_track_element_set_layer_active (node->data,
|
|
ges_layer_get_active_for_track (layer, track));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
timeline_tree_reset_layer_active (GNode * root, GESLayer * layer)
|
|
{
|
|
g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
|
|
(GNodeTraverseFunc) reset_layer_activness, layer);
|
|
}
|