summaryrefslogtreecommitdiff
path: root/ges/ges-clip.c
diff options
context:
space:
mode:
authorHenry Wilkes <hwilkes@igalia.com>2020-05-15 14:28:09 +0100
committerHenry Wilkes <hwilkes@igalia.com>2020-05-22 19:16:04 +0100
commit571120dcfbefc02c01f9ddb0be7c2debbcacad88 (patch)
tree5359b62222021e5c2c7cefb35291abd7f5bdf7a0 /ges/ges-clip.c
parent142456d8ba802485d065b8306131837e3cce1521 (diff)
effect: Add support for time effects
Allow the user to register a child property of a base effect as a time property. This can be used by GES to correctly calculate the duration-limit of a clip when it has time effects on it. The existing ges_effect_class_register_rate_property is now used to automatically register such time effects for rate effects. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
Diffstat (limited to 'ges/ges-clip.c')
-rw-r--r--ges/ges-clip.c208
1 files changed, 181 insertions, 27 deletions
diff --git a/ges/ges-clip.c b/ges/ges-clip.c
index 9cd79f59..baf42b44 100644
--- a/ges/ges-clip.c
+++ b/ges/ges-clip.c
@@ -224,6 +224,7 @@ typedef struct _DurationLimitData
GstClockTime max_duration;
GstClockTime inpoint;
gboolean active;
+ GHashTable *time_property_values;
} DurationLimitData;
static DurationLimitData *
@@ -239,6 +240,10 @@ _duration_limit_data_new (GESTrackElement * child)
data->priority = _PRIORITY (child);
data->active = ges_track_element_is_active (child);
+ if (GES_IS_TIME_EFFECT (child))
+ data->time_property_values =
+ ges_base_effect_get_time_property_values (GES_BASE_EFFECT (child));
+
return data;
}
@@ -248,6 +253,8 @@ _duration_limit_data_free (gpointer data_p)
DurationLimitData *data = data_p;
gst_clear_object (&data->track);
gst_clear_object (&data->child);
+ if (data->time_property_values)
+ g_hash_table_unref (data->time_property_values);
g_free (data);
}
@@ -288,9 +295,9 @@ _cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p)
return 1;
/* if higher priority (numerically lower) place later */
if (a->priority < b->priority)
- return -1;
- else if (a->priority > b->priority)
return 1;
+ else if (a->priority > b->priority)
+ return -1;
return 0;
}
@@ -298,43 +305,129 @@ _cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p)
((data->active && GST_CLOCK_TIME_IS_VALID (data->max_duration)) ? \
data->max_duration - data->inpoint : GST_CLOCK_TIME_NONE)
+static GstClockTime
+_calculate_track_duration_limit (GESClip * self, GList * start, GList * end)
+{
+ GList *tmp;
+ DurationLimitData *data = start->data;
+ GstClockTime track_limit;
+
+ /* convert source-duration to timeline-duration
+ * E.g. consider the following stack
+ *
+ * *=============================*
+ * | source |
+ * | in-point = 5 |
+ * | max-duration = 20 |
+ * *=============================*
+ * 5 10 15 20 (internal coordinates)
+ *
+ * duration-limit = 15 because max-duration - in-point = 15
+ *
+ * 0 5 10 15
+ * *=============================*
+ * | time-effect | | sink_to_source
+ * | rate = 0.5 | v / 0.5
+ * *=============================*
+ * 0 10 20 30
+ *
+ * duration-limit = 30 because rate effect can make it last longer
+ *
+ * 13 23 33 (internal coordinates)
+ * *===================*
+ * |effect-with-source |
+ * | in-point = 13 |
+ * | max-duration = 33 |
+ * *===================*
+ * 13 23 33 (internal coordinates)
+ *
+ * duration-limit = 20 because effect-with-source cannot cover 30
+ *
+ * 0 10 20
+ * *===================*
+ * | time-effect | | sink_to_source
+ * | rate = 2.0 | v / 2.0
+ * *===================*
+ * 0 5 10
+ *
+ * duration-limit = 10 because rate effect uses up twice as much
+ *
+ * -----------------------------------------------timeline
+ */
+
+ while (!_IS_CORE_CHILD (data->child)) {
+ GST_WARNING_OBJECT (self, "Child %" GES_FORMAT " has a lower "
+ "priority than the core child in the same track. Ignoring.",
+ GES_ARGS (data->child));
+
+ start = start->next;
+ if (start == end) {
+ GST_ERROR_OBJECT (self, "Track %" GST_PTR_FORMAT " is missing a "
+ "core child", data->track);
+ return GST_CLOCK_TIME_NONE;
+ }
+ data = start->data;
+ }
+
+ track_limit = _INTERNAL_LIMIT (data);
+
+ for (tmp = start->next; tmp != end; tmp = tmp->next) {
+ data = tmp->data;
+
+ if (GES_IS_TIME_EFFECT (data->child)) {
+ GESBaseEffect *effect = GES_BASE_EFFECT (data->child);
+ if (data->inpoint)
+ GST_ERROR_OBJECT (self, "Did not expect an in-point to be set "
+ "for the time effect %" GES_FORMAT, GES_ARGS (effect));
+ if (GST_CLOCK_TIME_IS_VALID (data->max_duration))
+ GST_ERROR_OBJECT (self, "Did not expect a max-duration to be set "
+ "for the time effect %" GES_FORMAT, GES_ARGS (effect));
+
+ if (data->active) {
+ /* for the time effect, the minimum time it will receive is 0
+ * (it should map 0 -> 0), and the maximum time will be track_limit */
+ track_limit = ges_base_effect_translate_sink_to_source_time (effect,
+ track_limit, data->time_property_values);
+ }
+ } else {
+ GstClockTime el_limit = _INTERNAL_LIMIT (data);
+ track_limit = _MIN_CLOCK_TIME (track_limit, el_limit);
+ }
+ }
+
+ GST_LOG_OBJECT (self, "Track duration-limit for track %" GST_PTR_FORMAT
+ " is %" GST_TIME_FORMAT, data->track, GST_TIME_ARGS (track_limit));
+
+ return track_limit;
+}
+
/* transfer-full of child_data */
static GstClockTime
_calculate_duration_limit (GESClip * self, GList * child_data)
{
GstClockTime limit = GST_CLOCK_TIME_NONE;
- GList *tmp;
+ GList *start, *end;
child_data = g_list_sort (child_data, _cmp_by_track_then_priority);
- tmp = child_data;
+ start = child_data;
- while (tmp) {
+ while (start) {
/* we have the first element in the track, of the lowest priority, and
* work our way up from here */
- DurationLimitData *data = tmp->data;
- GESTrack *track = data->track;
- if (track) {
- GstClockTime track_limit = _INTERNAL_LIMIT (data);
+ GESTrack *track = ((DurationLimitData *) (start->data))->track;
- for (tmp = tmp->next; tmp; tmp = tmp->next) {
- data = tmp->data;
- if (data->track != track)
- break;
- track_limit = _MIN_CLOCK_TIME (track_limit, _INTERNAL_LIMIT (data));
- }
+ end = start;
+ do {
+ end = end->next;
+ } while (end && ((DurationLimitData *) (end->data))->track == track);
- GST_LOG_OBJECT (self, "duration-limit for track %" GST_PTR_FORMAT
- " is %" GST_TIME_FORMAT, track, GST_TIME_ARGS (track_limit));
+ if (track) {
+ GstClockTime track_limit =
+ _calculate_track_duration_limit (self, start, end);
limit = _MIN_CLOCK_TIME (limit, track_limit);
- } else {
- /* children not in a track do not affect the duration-limit */
- for (tmp = tmp->next; tmp; tmp = tmp->next) {
- data = tmp->data;
- if (data->track)
- break;
- }
}
+ start = end;
}
GST_LOG_OBJECT (self, "calculated duration-limit for the clip is %"
GST_TIME_FORMAT, GST_TIME_ARGS (limit));
@@ -969,6 +1062,58 @@ _child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
_update_duration_limit (self);
}
+/****************************************************
+ * time properties *
+ ****************************************************/
+
+gboolean
+ges_clip_can_set_time_property_of_child (GESClip * clip,
+ GESTrackElement * child, GObject * child_prop_object, GParamSpec * pspec,
+ const GValue * value, GError ** error)
+{
+ if (_IS_TOP_EFFECT (child)) {
+ gchar *prop_name =
+ ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child),
+ child_prop_object, pspec);
+
+ if (prop_name) {
+ GList *child_data;
+ DurationLimitData *data = _duration_limit_data_new (child);
+ GValue *copy = g_new0 (GValue, 1);
+
+ g_value_init (copy, pspec->value_type);
+ g_value_copy (value, copy);
+
+ g_hash_table_insert (data->time_property_values, prop_name, copy);
+
+ child_data = _duration_limit_data_list_with_data (clip, data);
+
+ if (!_can_update_duration_limit (clip, child_data, error)) {
+ gchar *val_str = gst_value_serialize (value);
+ GST_INFO_OBJECT (clip, "Cannot set the child-property %s of "
+ "child %" GES_FORMAT " to %s because the duration-limit "
+ "cannot be adjusted", prop_name, GES_ARGS (child), val_str);
+ g_free (val_str);
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static void
+_child_time_property_changed_cb (GESTimelineElement * child,
+ GObject * prop_object, GParamSpec * pspec, GESClip * self)
+{
+ gchar *time_prop =
+ ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child),
+ prop_object, pspec);
+ if (time_prop) {
+ g_free (time_prop);
+ _update_duration_limit (self);
+ }
+}
+
/*****************************************************
* *
* GESTimelineElement virtual methods implementation *
@@ -1551,6 +1696,10 @@ _child_added (GESContainer * container, GESTimelineElement * element)
g_signal_connect (element, "notify", G_CALLBACK (_child_property_changed_cb),
self);
+ if (GES_IS_TIME_EFFECT (element))
+ g_signal_connect (element, "deep-notify",
+ G_CALLBACK (_child_time_property_changed_cb), self);
+
if (_IS_CORE_CHILD (element))
_update_max_duration (container);
@@ -1564,6 +1713,10 @@ _child_removed (GESContainer * container, GESTimelineElement * element)
g_signal_handlers_disconnect_by_func (element, _child_property_changed_cb,
self);
+ /* NOTE: we do not test if the effect is a time effect since technically
+ * it can stop being a time effect, although this would be rare */
+ g_signal_handlers_disconnect_by_func (element,
+ _child_time_property_changed_cb, self);
if (_IS_CORE_CHILD (element))
_update_max_duration (container);
@@ -2124,9 +2277,10 @@ ges_clip_class_init (GESClipClass * klass)
*
* The maximum #GESTimelineElement:duration that can be *currently* set
* for the clip, taking into account the #GESTimelineElement:in-point,
- * #GESTimelineElement:max-duration, GESTrackElement:active, and
- * #GESTrackElement:track properties of its children. If there is no
- * limit, this will be set to #GST_CLOCK_TIME_NONE.
+ * #GESTimelineElement:max-duration, #GESTrackElement:active, and
+ * #GESTrackElement:track properties of its children, as well as any
+ * time effects. If there is no limit, this will be set to
+ * #GST_CLOCK_TIME_NONE.
*
* Note that whilst a clip has no children in any tracks, the limit will
* be unknown, and similarly set to #GST_CLOCK_TIME_NONE.