gstreamer/ges/ges-timeline-tree.c
2019-04-15 17:11:48 -04:00

1264 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;
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
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);
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);
for (tmp = data.neighbours; tmp; tmp = tmp->next)
trim_simple (tmp->data, offset, data.edge);
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;
}