mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-10 17:35:59 +00:00
timeline: Implement snapping to markers
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/259>
This commit is contained in:
parent
777be5fad9
commit
3e66d05ed2
8 changed files with 364 additions and 14 deletions
|
@ -605,3 +605,26 @@ ges_meta_flag_get_type (void)
|
|||
g_once (&once, (GThreadFunc) register_ges_meta_flag, &id);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void
|
||||
register_ges_marker_flags (GType * id)
|
||||
{
|
||||
static const GFlagsValue values[] = {
|
||||
{C_ENUM (GES_MARKER_FLAG_NONE), "GES_MARKER_FLAG_NONE", "none"},
|
||||
{C_ENUM (GES_MARKER_FLAG_SNAPPABLE), "GES_MARKER_FLAG_SNAPPABLE",
|
||||
"snappable"},
|
||||
{0, NULL, NULL}
|
||||
};
|
||||
|
||||
*id = g_flags_register_static ("GESMarkerFlags", values);
|
||||
}
|
||||
|
||||
GType
|
||||
ges_marker_flags_get_type (void)
|
||||
{
|
||||
static GType id;
|
||||
static GOnce once = G_ONCE_INIT;
|
||||
|
||||
g_once (&once, (GThreadFunc) register_ges_marker_flags, &id);
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -564,6 +564,23 @@ const gchar * ges_edge_name (GESEdge edge);
|
|||
GES_API
|
||||
GType ges_edge_get_type (void);
|
||||
|
||||
#define GES_TYPE_MARKER_FLAGS (ges_marker_flags_get_type ())
|
||||
|
||||
GES_API
|
||||
GType ges_marker_flags_get_type (void);
|
||||
|
||||
/**
|
||||
* GESMarkerFlags:
|
||||
* @GES_MARKER_FLAG_NONE: Marker does not serve any special purpose.
|
||||
* @GES_MARKER_FLAG_SNAPPABLE: Marker can be a snapping target.
|
||||
*
|
||||
* Since: 1.20
|
||||
*/
|
||||
typedef enum {
|
||||
GES_MARKER_FLAG_NONE = 0,
|
||||
GES_MARKER_FLAG_SNAPPABLE = 1 << 0,
|
||||
} GESMarkerFlags;
|
||||
|
||||
|
||||
GES_API
|
||||
const gchar * ges_track_type_name (GESTrackType type);
|
||||
|
|
|
@ -586,10 +586,10 @@ G_GNUC_INTERNAL gchar *ges_test_source_asset_check_id (GType type, const
|
|||
GError **error);
|
||||
|
||||
/*******************************
|
||||
* GESMarkerList serialization *
|
||||
* GESMarkerList *
|
||||
*******************************/
|
||||
|
||||
|
||||
G_GNUC_INTERNAL GESMarker * ges_marker_list_get_closest (GESMarkerList *list, GstClockTime position);
|
||||
G_GNUC_INTERNAL gchar * ges_marker_list_serialize (const GValue * v);
|
||||
G_GNUC_INTERNAL gboolean ges_marker_list_deserialize (GValue *dest, const gchar *s);
|
||||
|
||||
|
|
|
@ -53,12 +53,12 @@ G_DEFINE_TYPE_WITH_CODE (GESMarker, ges_marker, G_TYPE_OBJECT,
|
|||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_POSITION,
|
||||
PROP_LAST
|
||||
PROP_MARKER_0,
|
||||
PROP_MARKER_POSITION,
|
||||
PROP_MARKER_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *properties[PROP_LAST];
|
||||
static GParamSpec *marker_properties[PROP_MARKER_LAST];
|
||||
|
||||
/* GObject Standard vmethods*/
|
||||
static void
|
||||
|
@ -68,7 +68,7 @@ ges_marker_get_property (GObject * object, guint property_id,
|
|||
GESMarker *marker = GES_MARKER (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_POSITION:
|
||||
case PROP_MARKER_POSITION:
|
||||
g_value_set_uint64 (value, marker->position);
|
||||
break;
|
||||
default:
|
||||
|
@ -97,12 +97,12 @@ ges_marker_class_init (GESMarkerClass * klass)
|
|||
*
|
||||
* Since: 1.18
|
||||
*/
|
||||
properties[PROP_POSITION] =
|
||||
marker_properties[PROP_MARKER_POSITION] =
|
||||
g_param_spec_uint64 ("position", "Position",
|
||||
"The position of the marker", 0, G_MAXUINT64,
|
||||
GST_CLOCK_TIME_NONE, G_PARAM_READABLE);
|
||||
g_object_class_install_property (object_class, PROP_POSITION,
|
||||
properties[PROP_POSITION]);
|
||||
g_object_class_install_property (object_class, PROP_MARKER_POSITION,
|
||||
marker_properties[PROP_MARKER_POSITION]);
|
||||
|
||||
}
|
||||
|
||||
|
@ -119,8 +119,18 @@ struct _GESMarkerList
|
|||
|
||||
GSequence *markers;
|
||||
GHashTable *markers_iters;
|
||||
GESMarkerFlags flags;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_MARKER_LIST_0,
|
||||
PROP_MARKER_LIST_FLAGS,
|
||||
PROP_MARKER_LIST_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *list_properties[PROP_MARKER_LIST_LAST];
|
||||
|
||||
enum
|
||||
{
|
||||
MARKER_ADDED,
|
||||
|
@ -133,6 +143,37 @@ static guint ges_marker_list_signals[LAST_SIGNAL] = { 0 };
|
|||
|
||||
G_DEFINE_TYPE (GESMarkerList, ges_marker_list, G_TYPE_OBJECT);
|
||||
|
||||
static void
|
||||
ges_marker_list_get_property (GObject * object, guint property_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GESMarkerList *self = GES_MARKER_LIST (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_MARKER_LIST_FLAGS:
|
||||
g_value_set_flags (value, self->flags);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ges_marker_list_set_property (GObject * object,
|
||||
guint property_id, const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GESMarkerList *self = GES_MARKER_LIST (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_MARKER_LIST_FLAGS:
|
||||
self->flags = g_value_get_flags (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
remove_marker (gpointer data)
|
||||
{
|
||||
|
@ -165,6 +206,23 @@ ges_marker_list_class_init (GESMarkerListClass * klass)
|
|||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = ges_marker_list_finalize;
|
||||
object_class->get_property = ges_marker_list_get_property;
|
||||
object_class->set_property = ges_marker_list_set_property;
|
||||
|
||||
/**
|
||||
* GESMarkerList:flags:
|
||||
*
|
||||
* Flags indicating how markers on the list should be treated.
|
||||
*
|
||||
* Since: 1.20
|
||||
*/
|
||||
list_properties[PROP_MARKER_LIST_FLAGS] =
|
||||
g_param_spec_flags ("flags", "Flags",
|
||||
"Functionalities the marker list should be used for",
|
||||
GES_TYPE_MARKER_FLAGS, GES_MARKER_FLAG_NONE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
||||
g_object_class_install_property (object_class, PROP_MARKER_LIST_FLAGS,
|
||||
list_properties[PROP_MARKER_LIST_FLAGS]);
|
||||
|
||||
/**
|
||||
* GESMarkerList::marker-added:
|
||||
|
@ -317,7 +375,6 @@ done:
|
|||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ges_marker_list_get_markers:
|
||||
*
|
||||
|
@ -347,6 +404,52 @@ ges_marker_list_get_markers (GESMarkerList * self)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* ges_marker_list_get_closest:
|
||||
* @position: The position which we want to find the closest marker to
|
||||
*
|
||||
* Returns: (transfer full): The marker found to be the closest
|
||||
* to the given position. If two markers are at equal distance from position,
|
||||
* the "earlier" one will be returned.
|
||||
*/
|
||||
GESMarker *
|
||||
ges_marker_list_get_closest (GESMarkerList * self, GstClockTime position)
|
||||
{
|
||||
GESMarker *new_marker, *ret = NULL;
|
||||
GstClockTime distance_next, distance_prev;
|
||||
GSequenceIter *iter;
|
||||
|
||||
if (g_sequence_is_empty (self->markers))
|
||||
goto done;
|
||||
|
||||
new_marker = (GESMarker *) g_object_new (GES_TYPE_MARKER, NULL);
|
||||
new_marker->position = position;
|
||||
iter = g_sequence_search (self->markers, new_marker, cmp_marker, NULL);
|
||||
g_object_unref (new_marker);
|
||||
|
||||
if (g_sequence_iter_is_begin (iter)) {
|
||||
/* We know the sequence isn't empty, this is safe */
|
||||
ret = g_sequence_get (iter);
|
||||
} else if (g_sequence_iter_is_end (iter)) {
|
||||
/* We know the sequence isn't empty, this is safe */
|
||||
ret = g_sequence_get (g_sequence_iter_prev (iter));
|
||||
} else {
|
||||
GESMarker *next_marker, *prev_marker;
|
||||
|
||||
prev_marker = g_sequence_get (g_sequence_iter_prev (iter));
|
||||
next_marker = g_sequence_get (iter);
|
||||
|
||||
distance_next = next_marker->position - position;
|
||||
distance_prev = position - prev_marker->position;
|
||||
|
||||
ret = distance_prev <= distance_next ? prev_marker : next_marker;
|
||||
}
|
||||
|
||||
done:
|
||||
if (ret)
|
||||
return g_object_ref (ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* ges_marker_list_move:
|
||||
|
|
|
@ -62,7 +62,7 @@ guint ges_marker_list_size (GESMarkerList * list);
|
|||
|
||||
|
||||
GES_API
|
||||
GList *ges_marker_list_get_markers (GESMarkerList *list);
|
||||
GList * ges_marker_list_get_markers (GESMarkerList *list);
|
||||
|
||||
GES_API
|
||||
gboolean ges_marker_list_move (GESMarkerList *list, GESMarker *marker, GstClockTime position);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "ges-timeline-tree.h"
|
||||
#include "ges-internal.h"
|
||||
#include "ges-marker-list.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (tree_debug);
|
||||
#undef GST_CAT_DEFAULT
|
||||
|
@ -400,6 +401,28 @@ get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
|
|||
* Snapping *
|
||||
****************************************************/
|
||||
|
||||
static void
|
||||
snap_to_marker (GESTrackElement * element, GstClockTime position,
|
||||
gboolean negative, GstClockTime marker_timestamp,
|
||||
GESTrackElement * marker_parent, SnappedPosition * snap)
|
||||
{
|
||||
GstClockTime distance;
|
||||
|
||||
if (negative)
|
||||
distance = _clock_time_plus (position, marker_timestamp);
|
||||
else
|
||||
distance = _abs_clock_time_distance (position, marker_timestamp);
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (distance) && distance <= snap->distance) {
|
||||
snap->negative = negative;
|
||||
snap->position = position;
|
||||
snap->distance = distance;
|
||||
snap->snapped = marker_timestamp;
|
||||
snap->element = element;
|
||||
snap->snapped_to = marker_parent;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
snap_to_edge (GESTrackElement * element, GstClockTime position,
|
||||
gboolean negative, GESTrackElement * snap_to, GESEdge edge,
|
||||
|
@ -431,6 +454,51 @@ snap_to_edge (GESTrackElement * element, GstClockTime position,
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
find_marker_snap (const GESMetaContainer * container, const gchar * key,
|
||||
const GValue * value, TreeIterationData * data)
|
||||
{
|
||||
GESTrackElement *marker_parent, *moving;
|
||||
GESClip *parent_clip;
|
||||
GstClockTime timestamp;
|
||||
GESMarkerList *marker_list;
|
||||
GESMarker *marker;
|
||||
GESMarkerFlags flags;
|
||||
gpointer gvalue = g_value_get_object (value);
|
||||
|
||||
if (!GES_IS_MARKER_LIST (gvalue))
|
||||
return;
|
||||
|
||||
marker_list = GES_MARKER_LIST (gvalue);
|
||||
|
||||
g_object_get (marker_list, "flags", &flags, NULL);
|
||||
if (!(flags & GES_MARKER_FLAG_SNAPPABLE))
|
||||
return;
|
||||
|
||||
marker_parent = GES_TRACK_ELEMENT ((gpointer) container);
|
||||
moving = GES_TRACK_ELEMENT (data->element);
|
||||
parent_clip = (GESClip *) GES_TIMELINE_ELEMENT_PARENT (marker_parent);
|
||||
|
||||
/* Translate current position into the target clip's time domain */
|
||||
timestamp =
|
||||
ges_clip_get_internal_time_from_timeline_time (parent_clip, marker_parent,
|
||||
data->position, NULL);
|
||||
marker = ges_marker_list_get_closest (marker_list, timestamp);
|
||||
|
||||
if (marker == NULL)
|
||||
return;
|
||||
|
||||
/* Make timestamp timeline-relative again */
|
||||
g_object_get (marker, "position", ×tamp, NULL);
|
||||
timestamp =
|
||||
ges_clip_get_timeline_time_from_internal_time (parent_clip, marker_parent,
|
||||
timestamp, NULL);
|
||||
snap_to_marker (moving, data->position, data->negative, timestamp,
|
||||
marker_parent, data->snap);
|
||||
|
||||
g_object_unref (marker);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
find_snap (GNode * node, TreeIterationData * data)
|
||||
{
|
||||
|
@ -454,6 +522,9 @@ find_snap (GNode * node, TreeIterationData * data)
|
|||
snap_to_edge (moving, data->position, data->negative, track_el,
|
||||
GES_EDGE_START, data->snap);
|
||||
|
||||
ges_meta_container_foreach (GES_META_CONTAINER (element),
|
||||
(GESMetaForeachFunc) find_marker_snap, data);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
|
@ -453,7 +453,6 @@ GST_START_TEST (test_marker_color)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
|
||||
static Suite *
|
||||
ges_suite (void)
|
||||
{
|
||||
|
|
|
@ -1139,6 +1139,142 @@ GST_START_TEST (test_snapping_groups)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_marker_snapping)
|
||||
{
|
||||
GESTrack *track;
|
||||
GESTimeline *timeline;
|
||||
GESTrackElement *trackelement1, *trackelement2;
|
||||
GESContainer *clip1, *clip2;
|
||||
GESLayer *layer;
|
||||
GList *trackelements;
|
||||
GESMarkerList *marker_list1, *marker_list2;
|
||||
|
||||
ges_init ();
|
||||
|
||||
track = GES_TRACK (ges_video_track_new ());
|
||||
fail_unless (track != NULL);
|
||||
|
||||
timeline = ges_timeline_new ();
|
||||
fail_unless (timeline != NULL);
|
||||
|
||||
fail_unless (ges_timeline_add_track (timeline, track));
|
||||
|
||||
clip1 = GES_CONTAINER (ges_test_clip_new ());
|
||||
clip2 = GES_CONTAINER (ges_test_clip_new ());
|
||||
|
||||
fail_unless (clip1 && clip2);
|
||||
|
||||
/**
|
||||
* Our timeline
|
||||
* ------------
|
||||
* 30
|
||||
* markers -----|----------------
|
||||
* | clip1 || clip2 |
|
||||
* time 20 ------- 50 ------ 110
|
||||
*
|
||||
*/
|
||||
g_object_set (clip1, "start", (guint64) 20, "duration", (guint64) 30,
|
||||
"in-point", (guint64) 0, NULL);
|
||||
g_object_set (clip2, "start", (guint64) 50, "duration", (guint64) 60,
|
||||
"in-point", (guint64) 0, NULL);
|
||||
|
||||
fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL);
|
||||
assert_equals_int (ges_layer_get_priority (layer), 0);
|
||||
|
||||
fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip1)));
|
||||
fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip1)) != NULL);
|
||||
fail_unless ((trackelement1 =
|
||||
GES_TRACK_ELEMENT (trackelements->data)) != NULL);
|
||||
fail_unless (ges_track_element_get_track (trackelement1) == track);
|
||||
|
||||
fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip2)));
|
||||
fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip2)) != NULL);
|
||||
fail_unless ((trackelement2 =
|
||||
GES_TRACK_ELEMENT (trackelements->data)) != NULL);
|
||||
fail_unless (ges_track_element_get_track (trackelement2) == track);
|
||||
|
||||
marker_list1 = ges_marker_list_new ();
|
||||
g_object_set (marker_list1, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL);
|
||||
ges_marker_list_add (marker_list1, 10);
|
||||
ges_marker_list_add (marker_list1, 20);
|
||||
fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER
|
||||
(trackelement1), "ges-test", marker_list1));
|
||||
|
||||
/**
|
||||
* Snapping clip2 to a marker on clip1
|
||||
* ------------
|
||||
* 30 40
|
||||
* markers -----|--|--
|
||||
* | clip1 |
|
||||
* time 20 ------ 50
|
||||
* -----------
|
||||
* | clip2 |
|
||||
* 30 ------ 90
|
||||
*/
|
||||
g_object_set (timeline, "snapping-distance", (guint64) 3, NULL);
|
||||
/* Move within 2 units of marker timestamp */
|
||||
fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL,
|
||||
GES_EDGE_NONE, 32) == TRUE);
|
||||
/* Clip nr. 2 should snap to marker at timestamp 30 */
|
||||
DEEP_CHECK (clip1, 20, 0, 30);
|
||||
DEEP_CHECK (clip2, 30, 0, 60);
|
||||
|
||||
/**
|
||||
* Snapping clip1 to a marker on clip2
|
||||
* ------------
|
||||
* 90
|
||||
* markers --|--------
|
||||
* | clip1 |
|
||||
* time 80 ------ 110
|
||||
* markers ----------|--
|
||||
* | clip2 |
|
||||
* 30 -------- 90
|
||||
*/
|
||||
marker_list2 = ges_marker_list_new ();
|
||||
g_object_set (marker_list2, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL);
|
||||
ges_marker_list_add (marker_list2, 40);
|
||||
ges_marker_list_add (marker_list2, 50);
|
||||
fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER
|
||||
(trackelement2), "ges-test", marker_list2));
|
||||
|
||||
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
||||
GES_EDGE_NONE, 77) == TRUE);
|
||||
DEEP_CHECK (clip1, 80, 0, 30);
|
||||
DEEP_CHECK (clip2, 30, 0, 60);
|
||||
|
||||
/**
|
||||
* Checking if clip's own markers are properly ignored when snapping
|
||||
* (moving clip1 close to where one of its markers is)
|
||||
* ------------
|
||||
* 100 112 122
|
||||
* markers | --|-------|--
|
||||
* old m.pos. | clip1 |
|
||||
* time 102 ------- 132
|
||||
*/
|
||||
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
||||
GES_EDGE_NONE, 102) == TRUE);
|
||||
DEEP_CHECK (clip1, 102, 0, 30);
|
||||
DEEP_CHECK (clip2, 30, 0, 60);
|
||||
|
||||
/**
|
||||
* Checking if non-snappable marker lists are correctly ignored.
|
||||
* (moving clip1 close to clip2's non-snappable marker)
|
||||
*/
|
||||
g_object_set (marker_list2, "flags", GES_MARKER_FLAG_NONE, NULL);
|
||||
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
|
||||
GES_EDGE_NONE, 82) == TRUE);
|
||||
DEEP_CHECK (clip1, 82, 0, 30);
|
||||
DEEP_CHECK (clip2, 30, 0, 60);
|
||||
|
||||
g_object_unref (marker_list1);
|
||||
g_object_unref (marker_list2);
|
||||
check_destroyed (G_OBJECT (timeline), G_OBJECT (trackelement1),
|
||||
trackelement2, clip1, clip2, layer, marker_list1, marker_list2, NULL);
|
||||
ges_deinit ();
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
static Suite *
|
||||
ges_suite (void)
|
||||
{
|
||||
|
@ -1153,6 +1289,7 @@ ges_suite (void)
|
|||
tcase_add_test (tc_chain, test_simple_triming);
|
||||
tcase_add_test (tc_chain, test_groups);
|
||||
tcase_add_test (tc_chain, test_snapping_groups);
|
||||
tcase_add_test (tc_chain, test_marker_snapping);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue