mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-24 02:31:03 +00:00
timeline: reimplement snap_to_position a bit more appropriately.
It could yet be made be simpler, but it would require touching the rest of the timeline editing code. Fixes https://phabricator.freedesktop.org/T7587 Reviewed-by: Thibault Saunier <thibault.saunier@collabora.com> Differential Revision: https://phabricator.freedesktop.org/D657
This commit is contained in:
parent
ff6dc38900
commit
73cf36fa25
4 changed files with 59 additions and 81 deletions
|
@ -1271,85 +1271,46 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackElement * obj1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static guint64 *
|
static GstClockTime *
|
||||||
ges_timeline_snap_position (GESTimeline * timeline,
|
ges_timeline_snap_position (GESTimeline * timeline,
|
||||||
GESTrackElement * trackelement, guint64 * current, guint64 timecode,
|
GESTrackElement * trackelement, GstClockTime * current,
|
||||||
gboolean emit)
|
GstClockTime timecode, gboolean emit)
|
||||||
{
|
{
|
||||||
GESTimelinePrivate *priv = timeline->priv;
|
GESTimelinePrivate *priv = timeline->priv;
|
||||||
GSequenceIter *iter, *prev_iter, *nxt_iter;
|
GSequenceIter *iter, *end_iter;
|
||||||
GESTrackElement *tmp_trackelement;
|
GESContainer *container = get_toplevel_container (trackelement);
|
||||||
GESContainer *tmp_container, *container;
|
GstClockTime *ret = NULL;
|
||||||
|
GstClockTime smallest_offset = G_MAXUINT64;
|
||||||
|
GstClockTime tmp_pos;
|
||||||
|
|
||||||
GstClockTime *last_snap_ts = priv->movecontext.last_snap_ts;
|
tmp_pos = timecode - priv->snapping_distance;
|
||||||
guint64 snap_distance = timeline->priv->snapping_distance;
|
/* Rippling, not snapping with previous elements */
|
||||||
guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 =
|
if (priv->movecontext.moving_trackelements)
|
||||||
G_MAXUINT64;
|
tmp_pos = timecode;
|
||||||
|
iter = g_sequence_search (priv->starts_ends, &tmp_pos,
|
||||||
/* Avoid useless calculations */
|
|
||||||
if (snap_distance == 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/* If we can just resnap as last snap... do it */
|
|
||||||
if (last_snap_ts) {
|
|
||||||
off = timecode > *last_snap_ts ?
|
|
||||||
timecode - *last_snap_ts : *last_snap_ts - timecode;
|
|
||||||
if (off <= snap_distance) {
|
|
||||||
ret = last_snap_ts;
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container = get_toplevel_container (trackelement);
|
|
||||||
|
|
||||||
iter = g_sequence_search (priv->starts_ends, &timecode,
|
|
||||||
(GCompareDataFunc) compare_uint64, NULL);
|
(GCompareDataFunc) compare_uint64, NULL);
|
||||||
|
|
||||||
/* Getting the next/previous values, and use the closest one if any "respects"
|
tmp_pos = timecode + priv->snapping_distance;
|
||||||
* the snap_distance value */
|
end_iter = g_sequence_search (priv->starts_ends, &tmp_pos,
|
||||||
nxt_iter = iter;
|
(GCompareDataFunc) compare_uint64, NULL);
|
||||||
while (!g_sequence_iter_is_end (nxt_iter)) {
|
|
||||||
next_tc = g_sequence_get (iter);
|
|
||||||
tmp_trackelement = g_hash_table_lookup (timeline->priv->by_object, next_tc);
|
|
||||||
tmp_container = get_toplevel_container (tmp_trackelement);
|
|
||||||
|
|
||||||
off = timecode > *next_tc ? timecode - *next_tc : *next_tc - timecode;
|
for (; iter != end_iter && !g_sequence_iter_is_end (iter);
|
||||||
if (next_tc != current && off <= snap_distance
|
iter = g_sequence_iter_next (iter)) {
|
||||||
&& container != tmp_container) {
|
GstClockTime *iter_tc = g_sequence_get (iter);
|
||||||
|
GESTrackElement *tmp_trackelement =
|
||||||
|
g_hash_table_lookup (priv->by_object, iter_tc);
|
||||||
|
GESContainer *tmp_container = get_toplevel_container (tmp_trackelement);
|
||||||
|
|
||||||
ret = next_tc;
|
if (tmp_container == container)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ABS (timecode - *iter_tc) > smallest_offset)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
smallest_offset = ABS (timecode - *iter_tc);
|
||||||
|
ret = iter_tc;
|
||||||
}
|
}
|
||||||
|
|
||||||
nxt_iter = g_sequence_iter_next (nxt_iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret == NULL)
|
|
||||||
off = G_MAXUINT64;
|
|
||||||
|
|
||||||
if (priv->movecontext.moving_trackelements) {
|
|
||||||
GST_INFO_OBJECT (timeline, "Rippling, no way we snap end");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_iter = g_sequence_iter_prev (iter);
|
|
||||||
while (!g_sequence_iter_is_begin (prev_iter)) {
|
|
||||||
prev_tc = g_sequence_get (prev_iter);
|
|
||||||
tmp_trackelement = g_hash_table_lookup (timeline->priv->by_object, prev_tc);
|
|
||||||
tmp_container = get_toplevel_container (tmp_trackelement);
|
|
||||||
|
|
||||||
off1 = timecode > *prev_tc ? timecode - *prev_tc : *prev_tc - timecode;
|
|
||||||
if (prev_tc != current && off1 < off && off1 <= snap_distance &&
|
|
||||||
container != tmp_container) {
|
|
||||||
ret = prev_tc;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_iter = g_sequence_iter_prev (prev_iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
|
||||||
/* We emit the snapping signal only if we snapped with a different value
|
/* We emit the snapping signal only if we snapped with a different value
|
||||||
* than the current one */
|
* than the current one */
|
||||||
if (emit) {
|
if (emit) {
|
||||||
|
@ -1413,7 +1374,6 @@ ges_move_context_set_objects (GESTimeline * timeline, GESTrackElement * obj,
|
||||||
GSequenceIter *iter, *trackelement_iter;
|
GSequenceIter *iter, *trackelement_iter;
|
||||||
|
|
||||||
MoveContext *mv_ctx = &timeline->priv->movecontext;
|
MoveContext *mv_ctx = &timeline->priv->movecontext;
|
||||||
|
|
||||||
iters = g_hash_table_lookup (timeline->priv->obj_iters, obj);
|
iters = g_hash_table_lookup (timeline->priv->obj_iters, obj);
|
||||||
trackelement_iter = iters->iter_obj;
|
trackelement_iter = iters->iter_obj;
|
||||||
switch (edge) {
|
switch (edge) {
|
||||||
|
|
|
@ -393,9 +393,8 @@ GST_START_TEST (test_snapping)
|
||||||
* time 26-------- 62 --------122
|
* time 26-------- 62 --------122
|
||||||
*/
|
*/
|
||||||
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip1), 26);
|
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip1), 26);
|
||||||
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip1), 37);
|
|
||||||
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement1, 26, 5, 36);
|
CHECK_OBJECT_PROPS (trackelement1, 26, 5, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
|
CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -409,7 +408,7 @@ GST_START_TEST (test_snapping)
|
||||||
fail_unless (ges_timeline_element_ripple (GES_TIMELINE_ELEMENT (clip1),
|
fail_unless (ges_timeline_element_ripple (GES_TIMELINE_ELEMENT (clip1),
|
||||||
58) == TRUE);
|
58) == TRUE);
|
||||||
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 36);
|
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement2, 98, 0, 60);
|
CHECK_OBJECT_PROPS (trackelement2, 98, 0, 60);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -419,7 +418,7 @@ GST_START_TEST (test_snapping)
|
||||||
*/
|
*/
|
||||||
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip2), 110);
|
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip2), 110);
|
||||||
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 36);
|
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
|
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -430,7 +429,7 @@ GST_START_TEST (test_snapping)
|
||||||
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
||||||
GES_EDGE_NONE, 72) == TRUE);
|
GES_EDGE_NONE, 72) == TRUE);
|
||||||
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement1, 73, 5, 36);
|
CHECK_OBJECT_PROPS (trackelement1, 73, 5, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
|
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -441,7 +440,7 @@ GST_START_TEST (test_snapping)
|
||||||
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
||||||
GES_EDGE_NONE, 58) == TRUE);
|
GES_EDGE_NONE, 58) == TRUE);
|
||||||
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 36);
|
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
|
||||||
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
|
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,6 @@ gi.require_version("GES", "1.0")
|
||||||
from gi.repository import Gst # noqa
|
from gi.repository import Gst # noqa
|
||||||
from gi.repository import GES # noqa
|
from gi.repository import GES # noqa
|
||||||
|
|
||||||
from .common import GESSimpleTimelineTest # noqa
|
|
||||||
|
|
||||||
import unittest # noqa
|
import unittest # noqa
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,9 @@ from gi.repository import GES # noqa
|
||||||
import unittest # noqa
|
import unittest # noqa
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from . import common
|
from .common import create_main_loop
|
||||||
|
from .common import create_project
|
||||||
|
from .common import GESSimpleTimelineTest # noqa
|
||||||
|
|
||||||
Gst.init(None)
|
Gst.init(None)
|
||||||
GES.init()
|
GES.init()
|
||||||
|
@ -36,8 +38,8 @@ GES.init()
|
||||||
class TestTimeline(unittest.TestCase):
|
class TestTimeline(unittest.TestCase):
|
||||||
|
|
||||||
def test_signals_not_emitted_when_loading(self):
|
def test_signals_not_emitted_when_loading(self):
|
||||||
mainloop = common.create_main_loop()
|
mainloop = create_main_loop()
|
||||||
timeline = common.create_project(with_group=True, saved=True)
|
timeline = create_project(with_group=True, saved=True)
|
||||||
|
|
||||||
# Reload the project, check the group.
|
# Reload the project, check the group.
|
||||||
project = GES.Project.new(uri=timeline.get_asset().props.uri)
|
project = GES.Project.new(uri=timeline.get_asset().props.uri)
|
||||||
|
@ -52,7 +54,6 @@ class TestTimeline(unittest.TestCase):
|
||||||
timeline = project.extract()
|
timeline = project.extract()
|
||||||
|
|
||||||
signals = ["layer-added", "group-added", "track-added"]
|
signals = ["layer-added", "group-added", "track-added"]
|
||||||
called = []
|
|
||||||
handle = mock.Mock()
|
handle = mock.Mock()
|
||||||
for signal in signals:
|
for signal in signals:
|
||||||
timeline.connect(signal, handle)
|
timeline.connect(signal, handle)
|
||||||
|
@ -61,7 +62,7 @@ class TestTimeline(unittest.TestCase):
|
||||||
self.assertTrue(loaded_called)
|
self.assertTrue(loaded_called)
|
||||||
handle.assert_not_called()
|
handle.assert_not_called()
|
||||||
|
|
||||||
class TestEditing(common.GESSimpleTimelineTest):
|
class TestEditing(GESSimpleTimelineTest):
|
||||||
|
|
||||||
def test_transition_disappears_when_moving_to_another_layer(self):
|
def test_transition_disappears_when_moving_to_another_layer(self):
|
||||||
self.timeline.props.auto_transition = True
|
self.timeline.props.auto_transition = True
|
||||||
|
@ -111,3 +112,23 @@ class TestEditing(common.GESSimpleTimelineTest):
|
||||||
self.assertEquals(len(self.layer.get_clips()), 4)
|
self.assertEquals(len(self.layer.get_clips()), 4)
|
||||||
clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND)
|
clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND)
|
||||||
self.assertEquals(len(self.layer.get_clips()), 4)
|
self.assertEquals(len(self.layer.get_clips()), 4)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSnapping(GESSimpleTimelineTest):
|
||||||
|
|
||||||
|
def test_snapping(self):
|
||||||
|
self.timeline.props.auto_transition = True
|
||||||
|
self.timeline.set_snapping_distance(1)
|
||||||
|
clip1 = self.add_clip(0, 0, 100)
|
||||||
|
|
||||||
|
# Split clip1.
|
||||||
|
split_position = 50
|
||||||
|
clip2 = clip1.split(split_position)
|
||||||
|
self.assertEquals(len(self.layer.get_clips()), 2)
|
||||||
|
self.assertEqual(clip1.props.duration, split_position)
|
||||||
|
self.assertEqual(clip2.props.start, split_position)
|
||||||
|
|
||||||
|
# Make sure snapping prevents clip2 to be moved to the left.
|
||||||
|
clip2.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE,
|
||||||
|
clip2.props.start - 1)
|
||||||
|
self.assertEqual(clip2.props.start, split_position)
|
||||||
|
|
Loading…
Reference in a new issue