static GList *ges_clip_create_track_elements_func (GESClip * clip,
GESTrackType type);
static void _compute_height (GESContainer * container);
+static GstClockTime _convert_core_time (GESClip * clip, GstClockTime time,
+ gboolean to_timeline, gboolean * no_core, GError ** error);
struct _GESClipPrivate
{
}
static gint
-_cmp_by_track_then_priority (gconstpointer a_p, gconstpointer b_p)
+_cmp_duration_limit_data_by_track_then_priority (gconstpointer a_p,
+ gconstpointer b_p)
{
const DurationLimitData *a = a_p, *b = b_p;
if (a->track < b->track)
GstClockTime limit = GST_CLOCK_TIME_NONE;
GList *start, *end;
- child_data = g_list_sort (child_data, _cmp_by_track_then_priority);
+ child_data = g_list_sort (child_data,
+ _cmp_duration_limit_data_by_track_then_priority);
start = child_data;
return clip->priv->duration_limit;
}
+static gint
+_cmp_children_by_priority (gconstpointer a_p, gconstpointer b_p)
+{
+ const GESTimelineElement *a = a_p, *b = b_p;
+ if (a->priority > b->priority)
+ return 1;
+ else if (a->priority < b->priority)
+ return -1;
+ return 0;
+}
+
/**
* ges_clip_get_top_effects:
* @clip: A #GESClip
GST_DEBUG_OBJECT (clip, "Getting the %i top effects", clip->priv->nb_effects);
ret = NULL;
- /* should be sorted by priority, but make sure */
- _ges_container_sort_children (GES_CONTAINER (clip));
-
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
child = tmp->data;
if (_IS_TOP_EFFECT (child))
ret = g_list_append (ret, gst_object_ref (child));
}
- return ret;
+ return g_list_sort (ret, _cmp_children_by_priority);
}
static gboolean
return ret;
}
+/* Convert from an internal time of a child within a clip to a
+ * ===========================================================
+ * timeline time
+ * =============
+ *
+ * Given an internal time T for some child in a clip, we want to know
+ * what the corresponding time in the timeline is.
+ *
+ * If the time T is between the in-point and out-point of the child,
+ * then we can convert to the timeline coordinates by answering:
+ *
+ * a) "What is the timeline time at which the internal data from the child
+ * found at time T appears in the timeline output?"
+ *
+ * If the time T is after the out-point of the child, we instead want to
+ * answer:
+ *
+ * b) "If we extended the clip indefinetly in the timeline, what would be
+ * the timeline time at which the internal data from the child found at
+ * time T would appear in the timeline output?"
+ *
+ * However, if the time T is before the in-point of the child, we instead
+ * want to answer a more subtle question:
+ *
+ * c) "If we set the 'in-point' of the child to T, what would we need to
+ * set the 'start' of the clip to such that the internal data from the
+ * child currently found at the *beginning* of the clip would then appear
+ * at the same timeline time?"
+ *
+ * E.g. consider the following children of a clip, all in the same track,
+ * and all active:
+ * T
+ * :
+ * +=====================:======+
+ * | _/ \_ |
+ * | source ~(o_o)~ |
+ * | / @ \ |
+ * +=====================:======+
+ * i :
+ * :
+ * +=====================:======+
+ * | time-effect0 : | | g0
+ * +=====================:======+ v
+ * :
+ * +=====================:======+
+ * | overlay : |
+ * +=====================:======+
+ * i' :
+ * :
+ * +=====================:======+
+ * | time-effect1 : | | g1
+ * +=====================:======+ v
+ * :
+ * -------------------------------:-------------------timeline
+ * S X
+ *
+ * where i is the in-point of the source and i' is the in-point of the
+ * overlay. Also, g0 is the sink_to_source translation function for the
+ * first time effect, and g1 is the same for the second. S is the start of
+ * the clip. The ~(o_o)~ figure is the data that appears in the source at
+ * T.
+ *
+ * Essentially, question a) wants us to convert from the time T, where the
+ * data is, which is in the internal time coordinates of the source, to
+ * the timeline time X. First, we subtract i to convert from the internal
+ * source coordinates of the source to the external source coordinates of
+ * the source, then we apply the sink_to_source translation functions,
+ * which act on external source coordinates, then add 'start' to finally
+ * convert to the timeline coordinates. So overall we have
+ *
+ * X = S + g1(g0(T - i))
+ *
+ * To answer b), T would be beyond the end of the clip. Since g1 and g0
+ * can convert beyond the end time, we similarly compute
+ *
+ * X = S + g1(g0(T - i))
+ *
+ * The user themselves should note that this could exceed the max-duration
+ * of any of the children.
+ *
+ * Now consider
+ *
+ * T
+ * :
+ * : +============================+
+ * : \_ |
+ * : _o)~ source |
+ * : @ \ |
+ * : +============================+
+ * : i
+ * :
+ * : +============================+
+ * : | time-effect0 | | g0
+ * : +============================+ v
+ * :
+ * : +============================+
+ * : | overlay |
+ * : +============================+
+ * : i'
+ * :
+ * : +============================+
+ * : | time-effect1 | | g1
+ * : +============================+ v
+ * :
+ * ---:-----------------------------------------------timeline
+ * X S
+ *
+ * To do the same as a), we would need to be able to convert from T to X,
+ * but this isn't defined since the children do not extend to here. More
+ * specifically, the functions g0 and g1 are not defined for negative
+ * times. Instead, we want to answer question c). That is, we want to know
+ * what we should set the start of the clip to to keep the figure at the
+ * same timeline position if we change the in-point of the source to T.
+ *
+ * First, if we set the in-point to T, then we would have
+ *
+ * T
+ * :
+ * +============================+
+ * | _/ \_ |
+ * | ~(o_o)~ source |
+ * | / @ \ |
+ * +============================+
+ * : i
+ * : :
+ * +=====:======================+
+ * | : time-effect0 | | g0
+ * +=====:======================+ v
+ * : :
+ * +=====:======================+
+ * | : overlay |
+ * +=====:======================+
+ * : :
+ * +=====:======================+
+ * | : time-effect1 | | g1
+ * +=====:======================+ v
+ * : :
+ * ---:-----:-----:-----------------------------------timeline
+ * X S Y
+ *
+ * In order to make the figure appear at 'start' again, we would need to
+ * reduce the start of the clip by the difference between S and Y, where
+ * Y is the conversion of the previous in-point i to the timeline time.
+ *
+ * Thus,
+ *
+ * X = S - (Y - S)
+ * = S - (S + g1(g0(i - T)) - S)
+ * = S - g1(g0(i - T))
+ *
+ * If this would be negative, the conversion will not be possible.
+ *
+ * Note, we are relying on the *assumption* that the translation functions
+ * *do not* change when we change the in-point. GESBaseEffect only claims
+ * to support such time effects.
+ *
+ * Note that if g0 and g1 are simply identities, and we translate the
+ * internal time using a) and b), we calculate
+ *
+ * S + (T - i)
+ *
+ * and for c), we calculate
+ *
+ * S - (i - T) = S + (T - i)
+ *
+ * In summary, if we are converting from internal time T to a timeline
+ * time the return is
+ *
+ * G(T) = { S + g1(g0(T - i)) if T >= i,
+ * { S - g1(g0(i - T)) otherwise.
+ *
+ * Note that the overlay did not play a role since it overall translates
+ * all received times by the identity. Note that we could similarly want
+ * to convert from an internal time in the overlay to the timeline time.
+ * This would be given by
+ *
+ * S + g1(T - i') if T >= i',
+ * S - g1(i' - T) otherwise.
+ *
+ *
+ * Convert from a timeline time to an internal time of a child
+ * ===========================================================
+ * in a clip
+ * =========
+ *
+ * We basically want to reverse the previous conversion. Specifically,
+ * when the timeline time X is between the start and end of the clip we
+ * want to answer:
+ *
+ * d) "What is the internal time at which the data from the child that
+ * appears in the timeline at time X is created in the child?"
+ *
+ * If the time X is after the end of the clip, we instead want to answer:
+ *
+ * e) "If we extended the clip indefinetly in the timeline, what would be
+ * the internal time at which the data from the child that appears in the
+ * timeline at time T would be created in the child?"
+ *
+ * However, if the time X is before the start of the child, we instead
+ * want to answer:
+ *
+ * f) "If we set the 'start' of the clip to X, what would we need to
+ * set the 'in-point' of the clip to such that the internal data from the
+ * child currently found at the *beginning* of the clip would then appear
+ * at the same timeline time?"
+ *
+ * Following the same arguments, these would all be answered by
+ *
+ * F(X) = { i + f0(f1(X - S)) if X >= S,
+ * { i - f0(f1(S - X)) otherwise.
+ *
+ * where f0 and f1 are the corresponding source_to_sink translation
+ * functions, which should be close reverses of g0 and g1, respectively.
+ *
+ * Note that this does indeed reverse the internal to timeline conversion:
+ *
+ * F(G(T)) = { i + f0(f1(G(T) - S)) if G(T) >= S,
+ * { i - f0(f1(S - G(T))) otherwise.
+ *
+ * but, since g1 and g0 map from [0,inf) to [0,inf),
+ *
+ * G(T) - S = { + g1(g0(T - i)) if T >= i,
+ * { - g1(g0(i - T)) otherwise.
+ * { >= 0 if T >= i,
+ * { = 0 if (T < i and g1(g0(i - T)) = 0)
+ * { < 0 otherwise.
+ *
+ * => ( G(T) >= S <==> T >= i or (T < i and g1(g0(i - T)) = 0) )
+ *
+ * therefore
+ * F(G(T)) = { i + f0(f1(g1(g0(T - i)))) if T >= i,
+ * { i + f0(f1(0)) if T < i
+ * { and g1(g0(i - T)) = 0,
+ * { i - f0(f1(g1(g0(i - T)))) otherwise
+ *
+ * = { i + f0(f1(g1(g0(T - i)))) if T >= i,
+ * { i - f0(f1(g1(g0(i - T)))) otherwise
+ *
+ * = T
+ *
+ * because f1 reverses g1, and f0 reverses g0.
+ */
+
+/* returns higher priority first */
+static GList *
+_active_time_effects_in_track_after_priority (GESClip * clip,
+ GESTrack * track, guint32 priority)
+{
+ GList *tmp, *list = NULL;
+
+ for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+ GESTrackElement *child = tmp->data;
+
+ if (GES_IS_TIME_EFFECT (child)
+ && ges_track_element_get_track (child) == track
+ && ges_track_element_is_active (child)
+ && _PRIORITY (child) < priority)
+ list = g_list_prepend (list, child);
+ }
+
+ return g_list_sort (list, _cmp_children_by_priority);
+}
+
/**
- * ges_clip_get_timeline_time_from_source_frame:
+ * ges_clip_get_timeline_time_from_internal_time:
+ * @clip: A #GESClip
+ * @child: An #GESTrackElement:active child of @clip with a
+ * #GESTrackElement:track
+ * @internal_time: A time in the internal time coordinates of @child
+ * @error: (nullable): Return location for an error
+ *
+ * Convert the internal source time from the child to a timeline time.
+ * This will take any time effects placed on the clip into account (see
+ * #GESBaseEffect for what time effects are supported, and how to
+ * declare them in GES).
+ *
+ * When @internal_time is above the #GESTimelineElement:in-point of
+ * @child, this will return the timeline time at which the internal
+ * content found at @internal_time appears in the output of the timeline's
+ * track. For example, this would let you know where in the timeline a
+ * particular scene in a media file would appear.
+ *
+ * This will be done assuming the clip has an indefinite end, so the
+ * timeline time may be beyond the end of the clip, or even breaking its
+ * #GESClip:duration-limit.
+ *
+ * If, instead, @internal_time is below the current
+ * #GESTimelineElement:in-point of @child, this will return what you would
+ * need to set the #GESTimelineElement:start of @clip to if you set the
+ * #GESTimelineElement:in-point of @child to @internal_time and wanted to
+ * keep the content of @child currently found at the current
+ * #GESTimelineElement:start of @clip at the same timeline position. If
+ * this would be negative, the conversion fails. This is useful for
+ * determining what position to use in a #GES_EDIT_MODE_TRIM if you wish
+ * to trim to a specific point in the internal content, such as a
+ * particular scene in a media file.
+ *
+ * Note that whilst a clip has no time effects, this second return is
+ * equivalent to finding the timeline time at which the content of @child
+ * at @internal_time would be found in the timeline if it had indefinite
+ * extent in both directions. However, with non-linear time effects this
+ * second return will be more distinct.
+ *
+ * In either case, the returned time would be appropriate to use in
+ * ges_timeline_element_edit() for #GES_EDIT_MODE_TRIM, and similar, if
+ * you wish to use a particular internal point as a reference.
+ *
+ * See ges_clip_get_internal_time_from_timeline_time(), which performs the
+ * reverse, or ges_clip_get_timeline_time_from_source_frame() which does
+ * the same conversion, but using frame numbers.
+ *
+ * Returns: The time in the timeline coordinates corresponding to
+ * @internal_time, or #GST_CLOCK_TIME_NONE if the conversion could not be
+ * performed.
+ */
+GstClockTime
+ges_clip_get_timeline_time_from_internal_time (GESClip * clip,
+ GESTrackElement * child, GstClockTime internal_time, GError ** error)
+{
+ GstClockTime inpoint, start, external_time;
+ gboolean decrease;
+ GESTrack *track;
+ GList *tmp, *time_effects;
+
+ g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
+ g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), GST_CLOCK_TIME_NONE);
+ g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE);
+
+ if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
+ GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
+ "a child of the clip", GES_ARGS (child));
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ track = ges_track_element_get_track (child);
+
+ if (!track) {
+ GST_WARNING_OBJECT (clip, "Cannot convert the internal time of the "
+ "child %" GES_FORMAT " to a timeline time because it is not part "
+ "of a track", GES_ARGS (child));
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ if (!ges_track_element_is_active (child)) {
+ GST_WARNING_OBJECT (clip, "Cannot convert the internal time of the "
+ "child %" GES_FORMAT " to a timeline time because it is not "
+ "active in its track", GES_ARGS (child));
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ if (internal_time == GST_CLOCK_TIME_NONE)
+ return GST_CLOCK_TIME_NONE;
+
+ inpoint = _INPOINT (child);
+ if (inpoint <= internal_time) {
+ decrease = FALSE;
+ external_time = internal_time - inpoint;
+ } else {
+ decrease = TRUE;
+ external_time = inpoint - internal_time;
+ }
+
+ time_effects = _active_time_effects_in_track_after_priority (clip, track,
+ _PRIORITY (child));
+
+ /* currently ordered with highest priority (closest to the timeline)
+ * first, with @child being at the *end* of the list.
+ * Want to reverse this so we can convert from the child towards the
+ * timeline */
+ time_effects = g_list_reverse (time_effects);
+
+ for (tmp = time_effects; tmp; tmp = tmp->next) {
+ GESBaseEffect *effect = tmp->data;
+ GHashTable *values = ges_base_effect_get_time_property_values (effect);
+
+ external_time = ges_base_effect_translate_sink_to_source_time (effect,
+ external_time, values);
+ g_hash_table_unref (values);
+ }
+
+ g_list_free (time_effects);
+
+ if (!GST_CLOCK_TIME_IS_VALID (external_time))
+ return GST_CLOCK_TIME_NONE;
+
+ start = _START (clip);
+
+ if (!decrease)
+ return start + external_time;
+
+ if (external_time > start) {
+ GST_INFO_OBJECT (clip, "Cannot convert the internal time %"
+ GST_TIME_FORMAT " of the child %" GES_FORMAT " to a timeline "
+ "time because it would lie before the start of the timeline",
+ GST_TIME_ARGS (internal_time), GES_ARGS (child));
+
+ g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+ "The internal time %" GST_TIME_FORMAT " of child \"%s\" "
+ "would correspond to a negative start of -%" GST_TIME_FORMAT
+ " for the clip \"%s\"", GST_TIME_ARGS (internal_time),
+ GES_TIMELINE_ELEMENT_NAME (child),
+ GST_TIME_ARGS (external_time - start),
+ GES_TIMELINE_ELEMENT_NAME (clip));
+
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ return start - external_time;
+}
+
+/**
+ * ges_clip_get_internal_time_from_timeline_time:
* @clip: A #GESClip
- * @frame_number: The frame number to get the corresponding timestamp in the
- * timeline coordinates
- * @err: A #GError set on errors
+ * @child: An #GESTrackElement:active child of @clip with a
+ * #GESTrackElement:track
+ * @timeline_time: A time in the timeline time coordinates
+ * @error: (nullable): Return location for an error
*
- * This method allows you to convert a frame number into a #GstClockTime, this
- * can be used to either seek to a particular frame in the timeline or to later
- * on edit @self with that timestamp.
+ * Convert the timeline time to an internal source time of the child.
+ * This will take any time effects placed on the clip into account (see
+ * #GESBaseEffect for what time effects are supported, and how to
+ * declare them in GES).
+ *
+ * When @timeline_time is above the #GESTimelineElement:start of @clip,
+ * this will return the internal time at which the content that appears at
+ * @timeline_time in the output of the timeline is created in @child. For
+ * example, if @timeline_time corresponds to the current seek position,
+ * this would let you know which part of a media file is being read.
+ *
+ * This will be done assuming the clip has an indefinite end, so the
+ * internal time may be beyond the current out-point of the child, or even
+ * its #GESTimelineElement:max-duration.
+ *
+ * If, instead, @timeline_time is below the current
+ * #GESTimelineElement:start of @clip, this will return what you would
+ * need to set the #GESTimelineElement:in-point of @child to if you set
+ * the #GESTimelineElement:start of @clip to @timeline_time and wanted
+ * to keep the content of @child currently found at the current
+ * #GESTimelineElement:start of @clip at the same timeline position. If
+ * this would be negative, the conversion fails. This is useful for
+ * determining what #GESTimelineElement:in-point would result from a
+ * #GES_EDIT_MODE_TRIM to @timeline_time.
+ *
+ * Note that whilst a clip has no time effects, this second return is
+ * equivalent to finding the internal time at which the content that
+ * appears at @timeline_time in the timeline can be found in @child if it
+ * had indefinite extent in both directions. However, with non-linear time
+ * effects this second return will be more distinct.
+ *
+ * In either case, the returned time would be appropriate to use for the
+ * #GESTimelineElement:in-point or #GESTimelineElement:max-duration of the
+ * child.
+ *
+ * See ges_clip_get_timeline_time_from_internal_time(), which performs the
+ * reverse.
+ *
+ * Returns: The time in the internal coordinates of @child corresponding
+ * to @timeline_time, or #GST_CLOCK_TIME_NONE if the conversion could not
+ * be performed.
+ */
+GstClockTime
+ges_clip_get_internal_time_from_timeline_time (GESClip * clip,
+ GESTrackElement * child, GstClockTime timeline_time, GError ** error)
+{
+ GstClockTime inpoint, start, external_time;
+ gboolean decrease;
+ GESTrack *track;
+ GList *tmp, *time_effects;
+
+ g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
+ g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), GST_CLOCK_TIME_NONE);
+ g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE);
+
+ if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
+ GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
+ "a child of the clip", GES_ARGS (child));
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ track = ges_track_element_get_track (child);
+
+ if (!track) {
+ GST_WARNING_OBJECT (clip, "Cannot convert the timeline time to an "
+ "internal time of child %" GES_FORMAT " because it is not part "
+ "of a track", GES_ARGS (child));
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ if (!ges_track_element_is_active (child)) {
+ GST_WARNING_OBJECT (clip, "Cannot convert the timeline time to an "
+ "internal time of child %" GES_FORMAT " because it is not active "
+ "in its track", GES_ARGS (child));
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ if (timeline_time == GST_CLOCK_TIME_NONE)
+ return GST_CLOCK_TIME_NONE;
+
+ start = _START (clip);
+ if (start <= timeline_time) {
+ decrease = FALSE;
+ external_time = timeline_time - start;
+ } else {
+ decrease = TRUE;
+ external_time = start - timeline_time;
+ }
+
+ time_effects = _active_time_effects_in_track_after_priority (clip, track,
+ _PRIORITY (child));
+
+ /* currently ordered with highest priority (closest to the timeline)
+ * first, with @child being at the *end* of the list, which is what we
+ * want */
+
+ for (tmp = time_effects; tmp; tmp = tmp->next) {
+ GESBaseEffect *effect = tmp->data;
+ GHashTable *values = ges_base_effect_get_time_property_values (effect);
+
+ external_time = ges_base_effect_translate_source_to_sink_time (effect,
+ external_time, values);
+ g_hash_table_unref (values);
+ }
+
+ g_list_free (time_effects);
+
+ if (!GST_CLOCK_TIME_IS_VALID (external_time))
+ return GST_CLOCK_TIME_NONE;
+
+ inpoint = _INPOINT (child);
+
+ if (!decrease)
+ return inpoint + external_time;
+
+ if (external_time > inpoint) {
+ GST_INFO_OBJECT (clip, "Cannot convert the timeline time %"
+ GST_TIME_FORMAT " to an internal time of the child %"
+ GES_FORMAT " because it would be before the element has any "
+ "internal content", GST_TIME_ARGS (timeline_time), GES_ARGS (child));
+
+ g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+ "The timeline time %" GST_TIME_FORMAT " would correspond to "
+ "a negative in-point of -%" GST_TIME_FORMAT " for the child "
+ "\"%s\" under clip \"%s\"", GST_TIME_ARGS (timeline_time),
+ GST_TIME_ARGS (external_time - inpoint),
+ GES_TIMELINE_ELEMENT_NAME (child), GES_TIMELINE_ELEMENT_NAME (clip));
+
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ return inpoint - external_time;
+}
+
+static GstClockTime
+_convert_core_time (GESClip * clip, GstClockTime time, gboolean to_timeline,
+ gboolean * no_core, GError ** error)
+{
+ GList *tmp;
+ GstClockTime converted = GST_CLOCK_TIME_NONE;
+ GstClockTime half_frame;
+ GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+ GESClipAsset *asset =
+ GES_CLIP_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
+
+ if (no_core)
+ *no_core = TRUE;
+
+ if (to_timeline)
+ half_frame = timeline ? ges_timeline_get_frame_time (timeline, 1) : 0;
+ else
+ half_frame = ges_clip_asset_get_frame_time (asset, 1);
+ half_frame = GST_CLOCK_TIME_IS_VALID (half_frame) ? half_frame / 2 : 0;
+
+ for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+ GESTrackElement *child = tmp->data;
+ GESTrack *track = ges_track_element_get_track (child);
+
+ if (_IS_CORE_CHILD (child) && track && ges_track_element_is_active (child)
+ && ges_track_element_has_internal_source (child)) {
+ GstClockTime tmp_time;
+ GError *convert_error = NULL;
+
+ if (no_core)
+ *no_core = FALSE;
+
+ if (to_timeline)
+ tmp_time =
+ ges_clip_get_timeline_time_from_internal_time (clip, child, time,
+ &convert_error);
+ else
+ tmp_time =
+ ges_clip_get_internal_time_from_timeline_time (clip, child, time,
+ &convert_error);
+
+ if (!GST_CLOCK_TIME_IS_VALID (converted)) {
+ converted = tmp_time;
+ } else if (!GST_CLOCK_TIME_IS_VALID (tmp_time)) {
+ GST_WARNING_OBJECT (clip, "The calculated %s time for %s time %"
+ GST_TIME_FORMAT " using core child %" GES_FORMAT " is not "
+ "defined, but it had a definite value of %" GST_TIME_FORMAT
+ " for another core child", to_timeline ? "timeline" : "internal",
+ to_timeline ? "internal" : "timeline", GST_TIME_ARGS (time),
+ GES_ARGS (child), GST_TIME_ARGS (converted));
+ } else if (tmp_time != converted) {
+ GstClockTime diff = (tmp_time > converted) ?
+ tmp_time - converted : converted - tmp_time;
+
+ if (diff > half_frame) {
+ GST_WARNING_OBJECT (clip, "The calculated %s time for %s time %"
+ GST_TIME_FORMAT " using core child %" GES_FORMAT " is %"
+ GST_TIME_FORMAT ", which is different from the value of %"
+ GST_TIME_FORMAT " calculated using a different core child",
+ to_timeline ? "timeline" : "internal",
+ to_timeline ? "internal" : "timeline", GST_TIME_ARGS (time),
+ GES_ARGS (child), GST_TIME_ARGS (tmp_time),
+ GST_TIME_ARGS (converted));
+ }
+
+ /* prefer result from video tracks */
+ if (GES_IS_VIDEO_TRACK (track))
+ converted = tmp_time;
+ }
+ if (convert_error) {
+ if (error) {
+ g_clear_error (error);
+ *error = convert_error;
+ } else {
+ g_error_free (convert_error);
+ }
+ }
+ }
+ }
+
+ return converted;
+}
+
+/**
+ * ges_clip_get_timeline_time_from_source_frame:
+ * @clip: A #GESClip
+ * @frame_number: The frame number to get the corresponding timestamp of
+ * in the timeline coordinates
+ * @error: (nullable): Return location for an error
*
- * This method should be use specifically in the case where you want to trim the
- * clip to a particular frame.
+ * Convert the source frame number to a timeline time. This acts the same
+ * as ges_clip_get_timeline_time_from_internal_time() using the core
+ * children of the clip and using the frame number to specify the internal
+ * position, rather than a timestamp.
*
- * The returned timestamp is in the global #GESTimeline time coordinates of @self, not
- * in the internal time coordinates. In practice, this means that you can not use
- * that time to set the clip #GESTimelineElement:in-point but it can be used in
- * the timeline editing API, for example as the @position argument of the
- * #ges_timeline_element_edit method.
+ * The returned timeline time can be used to seek or edit to a specific
+ * frame.
*
- * Note that you can get the frame timestamp of a particular clip asset with
- * #ges_clip_asset_get_frame_time.
+ * Note that you can get the frame timestamp of a particular clip asset
+ * with ges_clip_asset_get_frame_time().
*
- * Returns: The timestamp corresponding to @frame_number in the element source
- * in the timeline coordinates.
+ * Returns: The timestamp corresponding to @frame_number in the core
+ * children of @clip, in the timeline coordinates, or #GST_CLOCK_TIME_NONE
+ * if the conversion could not be performed.
*/
GstClockTime
ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
- GESFrameNumber frame_number, GError ** err)
+ GESFrameNumber frame_number, GError ** error)
{
+ GstClockTime timeline_time = GST_CLOCK_TIME_NONE;
GstClockTime frame_ts;
GESClipAsset *asset;
- GstClockTimeDiff inpoint_diff;
g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
- g_return_val_if_fail (!err || !*err, GST_CLOCK_TIME_NONE);
+ g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE);
if (!GES_FRAME_NUMBER_IS_VALID (frame_number))
return GST_CLOCK_TIME_NONE;
if (!GST_CLOCK_TIME_IS_VALID (frame_ts))
return GST_CLOCK_TIME_NONE;
- inpoint_diff = GST_CLOCK_DIFF (frame_ts, GES_TIMELINE_ELEMENT_INPOINT (clip));
- if (GST_CLOCK_DIFF (inpoint_diff, _START (clip)) < 0) {
- g_set_error (err, GES_ERROR, GES_ERROR_INVALID_FRAME_NUMBER,
- "Requested frame %" G_GINT64_FORMAT
- " would be outside the timeline.", frame_number);
- return GST_CLOCK_TIME_NONE;
+ timeline_time = _convert_core_time (clip, frame_ts, TRUE, NULL, error);
+
+ if (error && *error) {
+ g_clear_error (error);
+ g_set_error (error, GES_ERROR, GES_ERROR_INVALID_FRAME_NUMBER,
+ "Requested frame %" G_GINT64_FORMAT " would be outside the "
+ "timeline.", frame_number);
}
- return GST_CLOCK_DIFF (inpoint_diff, _START (clip));
+ return timeline_time;
}
/**
GError ** error);
GES_API
+GstClockTime ges_clip_get_internal_time_from_timeline_time (GESClip * clip,
+ GESTrackElement * child,
+ GstClockTime timeline_time,
+ GError ** error);
+GES_API
+GstClockTime ges_clip_get_timeline_time_from_internal_time (GESClip * clip,
+ GESTrackElement * child,
+ GstClockTime internal_time,
+ GError ** error);
+GES_API
GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
GESFrameNumber frame_number,
- GError ** err);
+ GError ** error);
GES_API
GstClockTime ges_clip_get_duration_limit (GESClip * clip);
* #GESTimelineElement:start, or having insufficient internal
* content to last for the desired #GESTimelineElement:duration).
*
+ * ## Time Coordinates
+ *
+ * There are three main sets of time coordinates to consider when using
+ * timeline elements:
+ *
+ * + Timeline coordinates: these are the time coordinates used in the
+ * output of the timeline in its #GESTrack-s. Each track share the same
+ * coordinates, so there is only one set of coordinates for the
+ * timeline. These extend indefinitely from 0. The times used for
+ * editing (including setting #GESTimelineElement:start and
+ * #GESTimelineElement:duration) use these coordinates, since these
+ * define when an element is present and for how long the element lasts
+ * for in the timeline.
+ * + Internal source coordinates: these are the time coordinates used
+ * internally at the element's output. This is only really defined for
+ * #GESTrackElement-s, where it refers to time coordinates used at the
+ * final source pad of the wrapped #GstElement-s. However, these
+ * coordinates may also be used in a #GESClip in reference to its
+ * children. In particular, these are the coordinates used for
+ * #GESTimelineElement:in-point and #GESTimelineElement:max-duration.
+ * + Internal sink coordinates: these are the time coordinates used
+ * internally at the element's input. A #GESSource has no input, so
+ * these would be undefined. Otherwise, for most #GESTrackElement-s
+ * these will be the same set of coordinates as the internal source
+ * coordinates because the element does not change the timing
+ * internally. Only #GESBaseEffect can support elements where these
+ * are different. See #GESBaseEffect for more information.
+ *
+ * You can determine the timeline time for a given internal source time
+ * in a #GESTrack in a #GESClip using
+ * ges_clip_get_timeline_time_from_internal_time(), and vice versa using
+ * ges_clip_get_internal_time_from_timeline_time(), for the purposes of
+ * editing and setting timings properties.
+ *
* ## Children Properties
*
* If a timeline element owns another #GstObject and wishes to expose
* @mode: The edit mode
* @edge: The edge of @self where the edit should occur
* @position: The edit position: a new location for the edge of @self
- * (in nanoseconds)
+ * (in nanoseconds) in the timeline coordinates
* @error: (nullable): Return location for an error
*
* Edits the element within its timeline by adjusting its
gst_object_unref (clip1);
gst_object_unref (timeline);
- gst_deinit ();
+ ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _assert_timeline_to_internal(clip, child, in, expect_out) \
+{\
+ GError *error = NULL; \
+ GstClockTime found = ges_clip_get_internal_time_from_timeline_time ( \
+ clip, child, (in) * GST_SECOND, &error); \
+ GstClockTime expect = expect_out * GST_SECOND; \
+ fail_unless (found == expect, "Conversion from timeline time %" \
+ GST_TIME_FORMAT " to the internal time of %" GES_FORMAT " gave %" \
+ GST_TIME_FORMAT " rather than the expected %" GST_TIME_FORMAT \
+ " (error: %s)", GST_TIME_ARGS ((in) * GST_SECOND), \
+ GES_ARGS (child), GST_TIME_ARGS (found), GST_TIME_ARGS (expect), \
+ error ? error->message : "None"); \
+ fail_if (error); \
+}
+
+#define _assert_timeline_to_internal_fails(clip, child, in, error_code) \
+{ \
+ GError *error = NULL; \
+ GstClockTime found = ges_clip_get_internal_time_from_timeline_time ( \
+ clip, child, (in) * GST_SECOND, &error); \
+ fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from timeline " \
+ "time %" GST_TIME_FORMAT " to the internal time of %" GES_FORMAT \
+ " successfully converted to %" GST_TIME_FORMAT " rather than " \
+ "GST_CLOCK_TIME_NONE", GST_TIME_ARGS ((in) * GST_SECOND), \
+ GES_ARGS (child), GST_TIME_ARGS (found)); \
+ assert_GESError (error, error_code); \
+}
+
+#define _assert_internal_to_timeline(clip, child, in, expect_out) \
+{\
+ GError *error = NULL; \
+ GstClockTime found = ges_clip_get_timeline_time_from_internal_time ( \
+ clip, child, (in) * GST_SECOND, &error); \
+ GstClockTime expect = expect_out * GST_SECOND; \
+ fail_unless (found == expect, "Conversion from the internal time %" \
+ GST_TIME_FORMAT " of %" GES_FORMAT " to the timeline time gave %" \
+ GST_TIME_FORMAT " rather than the expected %" GST_TIME_FORMAT, \
+ GST_TIME_ARGS ((in) * GST_SECOND), GES_ARGS (child), \
+ GST_TIME_ARGS (found), GST_TIME_ARGS (expect)); \
+ fail_if (error); \
+}
+
+#define _assert_internal_to_timeline_fails(clip, child, in, error_code) \
+{\
+ GError *error = NULL; \
+ GstClockTime found = ges_clip_get_timeline_time_from_internal_time ( \
+ clip, child, (in) * GST_SECOND, &error); \
+ fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from the " \
+ "internal time %" GST_TIME_FORMAT " of %" GES_FORMAT " to the " \
+ "timeline time gave %" GST_TIME_FORMAT " rather than " \
+ "GST_CLOCK_TIME_NONE", GST_TIME_ARGS ((in) * GST_SECOND), \
+ GES_ARGS (child), GST_TIME_ARGS (found)); \
+ assert_GESError (error, error_code); \
+}
+
+#define _assert_frame_to_timeline(clip, frame, expect_out) \
+{\
+ GError *error = NULL; \
+ GstClockTime found = ges_clip_get_timeline_time_from_source_frame ( \
+ clip, frame, &error); \
+ GstClockTime expect = expect_out * GST_SECOND; \
+ fail_unless (found == expect, "Conversion from the source frame %" \
+ G_GINT64_FORMAT " to the timeline time gave %" GST_TIME_FORMAT \
+ " rather than the expected %" GST_TIME_FORMAT, frame, \
+ GST_TIME_ARGS (found), GST_TIME_ARGS (expect)); \
+ fail_if (error); \
+}
+
+#define _assert_frame_to_timeline_fails(clip, frame, error_code) \
+{\
+ GError *error = NULL; \
+ GstClockTime found = ges_clip_get_timeline_time_from_source_frame ( \
+ clip, frame, &error); \
+ fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from the " \
+ "source frame %" G_GINT64_FORMAT " to the timeline time gave %" \
+ GST_TIME_FORMAT " rather than the expected GST_CLOCK_TIME_NONE", \
+ frame, GST_TIME_ARGS (found)); \
+ assert_GESError (error, error_code); \
+}
+
+GST_START_TEST (test_convert_time)
+{
+ GESTimeline *timeline;
+ GESTrack *track0, *track1;
+ GESAsset *asset;
+ GESLayer *layer;
+ GESClip *clip;
+ GESTrackElement *source0, *source1, *rate0, *rate1, *rate2, *overlay;
+ GValue val = G_VALUE_INIT;
+
+ ges_init ();
+
+ asset = ges_asset_request (GES_TYPE_TEST_CLIP,
+ "framerate=30/1, max-duration=93.0", NULL);
+ fail_unless (asset);
+
+ timeline = ges_timeline_new ();
+
+ track0 = GES_TRACK (ges_video_track_new ());
+ track1 = GES_TRACK (ges_video_track_new ());
+
+ fail_unless (ges_timeline_add_track (timeline, track0));
+ fail_unless (ges_timeline_add_track (timeline, track1));
+
+ layer = ges_timeline_append_layer (timeline);
+
+ clip = ges_layer_add_asset (layer, asset, 20 * GST_SECOND,
+ 13 * GST_SECOND, 10 * GST_SECOND, GES_TRACK_TYPE_VIDEO);
+ fail_unless (clip);
+ CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND,
+ 10 * GST_SECOND, 93 * GST_SECOND);
+
+ source0 =
+ ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE);
+ source1 =
+ ges_clip_find_track_element (clip, track1, GES_TYPE_VIDEO_TEST_SOURCE);
+
+ rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+ rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+ rate2 = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+ overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+ ges_track_element_set_has_internal_source (overlay, TRUE);
+ /* enough internal content to last 10 seconds at a rate of 4.0 */
+ assert_set_inpoint (overlay, 7 * GST_SECOND);
+ assert_set_max_duration (overlay, 50 * GST_SECOND);
+
+ fail_unless (ges_track_add_element (track0, rate0));
+ fail_unless (ges_track_add_element (track1, rate1));
+ fail_unless (ges_track_add_element (track1, rate2));
+ fail_unless (ges_track_add_element (track1, overlay));
+
+ _assert_add (clip, rate0);
+ _assert_add (clip, rate2);
+ _assert_add (clip, overlay);
+ _assert_add (clip, rate1);
+
+ /* in track0:
+ *
+ * source0 -> rate0 -> out
+ *
+ * in track1:
+ *
+ * source1 -> rate1 -> overlay -> rate2 -> out
+ */
+
+ g_value_init (&val, G_TYPE_DOUBLE);
+
+ _assert_rate_equal (rate0, "rate", 1.0, val);
+ _assert_rate_equal (rate1, "rate", 1.0, val);
+ _assert_rate_equal (rate2, "rate", 1.0, val);
+
+ /* without rates */
+
+ /* start of the clip */
+ _assert_internal_to_timeline (clip, source0, 13, 20);
+ _assert_internal_to_timeline (clip, source1, 13, 20);
+ _assert_internal_to_timeline (clip, overlay, 7, 20);
+ _assert_frame_to_timeline (clip, 390, 20);
+ _assert_timeline_to_internal (clip, source0, 20, 13);
+ _assert_timeline_to_internal (clip, source1, 20, 13);
+ _assert_timeline_to_internal (clip, overlay, 20, 7);
+
+ /* middle of the clip */
+ _assert_internal_to_timeline (clip, source0, 18, 25);
+ _assert_internal_to_timeline (clip, source1, 18, 25);
+ _assert_internal_to_timeline (clip, overlay, 12, 25);
+ _assert_frame_to_timeline (clip, 540, 25);
+ _assert_timeline_to_internal (clip, source0, 25, 18);
+ _assert_timeline_to_internal (clip, source1, 25, 18);
+ _assert_timeline_to_internal (clip, overlay, 25, 12);
+
+ /* end of the clip */
+ _assert_internal_to_timeline (clip, source0, 23, 30);
+ _assert_internal_to_timeline (clip, source1, 23, 30);
+ _assert_internal_to_timeline (clip, overlay, 17, 30);
+ _assert_frame_to_timeline (clip, 690, 30);
+ _assert_timeline_to_internal (clip, source0, 30, 23);
+ _assert_timeline_to_internal (clip, source1, 30, 23);
+ _assert_timeline_to_internal (clip, overlay, 30, 17);
+
+ /* beyond the end of the clip */
+ /* exceeds the max-duration of the elements, but that is ok */
+ _assert_internal_to_timeline (clip, source0, 123, 130);
+ _assert_internal_to_timeline (clip, source1, 123, 130);
+ _assert_internal_to_timeline (clip, overlay, 117, 130);
+ _assert_frame_to_timeline (clip, 3690, 130);
+ _assert_timeline_to_internal (clip, source0, 130, 123);
+ _assert_timeline_to_internal (clip, source1, 130, 123);
+ _assert_timeline_to_internal (clip, overlay, 130, 117);
+
+ /* before the start of the clip */
+ _assert_internal_to_timeline (clip, source0, 8, 15);
+ _assert_internal_to_timeline (clip, source1, 8, 15);
+ _assert_internal_to_timeline (clip, overlay, 2, 15);
+ _assert_frame_to_timeline (clip, 240, 15);
+ _assert_timeline_to_internal (clip, source0, 15, 8);
+ _assert_timeline_to_internal (clip, source1, 15, 8);
+ _assert_timeline_to_internal (clip, overlay, 15, 2);
+
+ /* too early for overlay */
+ _assert_timeline_to_internal (clip, source0, 10, 3);
+ _assert_timeline_to_internal (clip, source1, 10, 3);
+ _assert_timeline_to_internal_fails (clip, overlay, 10,
+ GES_ERROR_NEGATIVE_TIME);
+
+ /* too early for sources */
+ _assert_timeline_to_internal_fails (clip, source0, 5,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, source1, 5,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, overlay, 5,
+ GES_ERROR_NEGATIVE_TIME);
+
+ assert_set_start (clip, 10 * GST_SECOND);
+
+ /* too early in the timeline */
+ _assert_internal_to_timeline_fails (clip, source0, 2,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_internal_to_timeline_fails (clip, source1, 2,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_internal_to_timeline (clip, overlay, 2, 5);
+ _assert_frame_to_timeline_fails (clip, 60, GES_ERROR_INVALID_FRAME_NUMBER);
+
+ assert_set_start (clip, 6 * GST_SECOND);
+ _assert_internal_to_timeline_fails (clip, source0, 6,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_internal_to_timeline_fails (clip, source1, 6,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_internal_to_timeline_fails (clip, overlay, 0,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_frame_to_timeline_fails (clip, 180, GES_ERROR_INVALID_FRAME_NUMBER);
+
+ assert_set_start (clip, 20 * GST_SECOND);
+
+ /* now with rate effects
+ * Note, they are currently out of sync */
+ _assert_set_rate (rate0, "rate", 0.5, val);
+ _assert_set_rate (rate1, "rate", 2.0, val);
+ _assert_set_rate (rate2, "rate", 4.0, val);
+
+ CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND,
+ 10 * GST_SECOND, 93 * GST_SECOND);
+
+ /* start of the clip is the same */
+ _assert_internal_to_timeline (clip, source0, 13, 20);
+ _assert_internal_to_timeline (clip, source1, 13, 20);
+ _assert_internal_to_timeline (clip, overlay, 7, 20);
+ _assert_timeline_to_internal (clip, source0, 20, 13);
+ _assert_timeline_to_internal (clip, source1, 20, 13);
+ _assert_timeline_to_internal (clip, overlay, 20, 7);
+
+ /* middle is different */
+ /* 5 seconds in the timeline is 2.5 seconds into the source */
+ _assert_internal_to_timeline (clip, source0, 15.5, 25);
+ /* 5 seconds in the timeline is 40 seconds into the source */
+ _assert_internal_to_timeline (clip, source1, 53, 25);
+ /* 5 seconds in the timeline is 20 seconds into the source */
+ _assert_internal_to_timeline (clip, overlay, 27, 25);
+ /* reverse */
+ _assert_timeline_to_internal (clip, source0, 25, 15.5);
+ _assert_timeline_to_internal (clip, source1, 25, 53);
+ _assert_timeline_to_internal (clip, overlay, 25, 27);
+
+ /* end is different */
+ _assert_internal_to_timeline (clip, source0, 18, 30);
+ _assert_internal_to_timeline (clip, source1, 93, 30);
+ _assert_internal_to_timeline (clip, overlay, 47, 30);
+ _assert_timeline_to_internal (clip, source0, 30, 18);
+ _assert_timeline_to_internal (clip, source1, 30, 93);
+ _assert_timeline_to_internal (clip, overlay, 30, 47);
+
+ /* beyond end is different */
+ _assert_internal_to_timeline (clip, source0, 68, 130);
+ _assert_internal_to_timeline (clip, source1, 893, 130);
+ _assert_internal_to_timeline (clip, overlay, 447, 130);
+ _assert_timeline_to_internal (clip, source0, 130, 68);
+ _assert_timeline_to_internal (clip, source1, 130, 893);
+ _assert_timeline_to_internal (clip, overlay, 130, 447);
+
+ /* before the start */
+ _assert_internal_to_timeline (clip, source0, 12.5, 19);
+ _assert_internal_to_timeline (clip, source1, 5, 19);
+ _assert_internal_to_timeline (clip, overlay, 3, 19);
+ _assert_timeline_to_internal (clip, source0, 19, 12.5);
+ _assert_timeline_to_internal (clip, source1, 19, 5);
+ _assert_timeline_to_internal (clip, overlay, 19, 3);
+
+ /* too early for source1 and overlay */
+ _assert_internal_to_timeline (clip, source0, 12, 18);
+ _assert_timeline_to_internal (clip, source0, 18, 12);
+ _assert_timeline_to_internal_fails (clip, source1, 18,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, overlay, 18,
+ GES_ERROR_NEGATIVE_TIME);
+
+ assert_set_inpoint (overlay, 8 * GST_SECOND);
+ /* now fine */
+ _assert_internal_to_timeline (clip, overlay, 0, 18);
+ _assert_timeline_to_internal (clip, overlay, 18, 0);
+
+ assert_set_inpoint (overlay, 7 * GST_SECOND);
+
+ /* still not too early for source0 */
+ _assert_internal_to_timeline (clip, source0, 5.5, 5);
+ _assert_timeline_to_internal (clip, source0, 5, 5.5);
+ _assert_timeline_to_internal_fails (clip, source1, 5,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, overlay, 5,
+ GES_ERROR_NEGATIVE_TIME);
+
+ _assert_internal_to_timeline (clip, source0, 3, 0);
+ _assert_timeline_to_internal (clip, source0, 0, 3);
+ _assert_timeline_to_internal_fails (clip, source1, 5,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, overlay, 5,
+ GES_ERROR_NEGATIVE_TIME);
+
+ /* too early for the timeline */
+ _assert_internal_to_timeline_fails (clip, source0, 2,
+ GES_ERROR_NEGATIVE_TIME);
+
+ /* re-sync rates between tracks */
+ _assert_set_rate (rate2, "rate", 0.25, val);
+
+ CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND,
+ 10 * GST_SECOND, 93 * GST_SECOND);
+
+ /* start of the clip */
+ _assert_internal_to_timeline (clip, source0, 13, 20);
+ _assert_internal_to_timeline (clip, source1, 13, 20);
+ _assert_internal_to_timeline (clip, overlay, 7, 20);
+ _assert_frame_to_timeline (clip, 390, 20);
+ _assert_timeline_to_internal (clip, source0, 20, 13);
+ _assert_timeline_to_internal (clip, source1, 20, 13);
+ _assert_timeline_to_internal (clip, overlay, 20, 7);
+
+ /* middle of the clip */
+ _assert_internal_to_timeline (clip, source0, 15.5, 25);
+ _assert_internal_to_timeline (clip, source1, 15.5, 25);
+ _assert_internal_to_timeline (clip, overlay, 8.25, 25);
+ _assert_frame_to_timeline (clip, 465, 25);
+ _assert_timeline_to_internal (clip, source0, 25, 15.5);
+ _assert_timeline_to_internal (clip, source1, 25, 15.5);
+ _assert_timeline_to_internal (clip, overlay, 25, 8.25);
+
+ /* end of the clip */
+ _assert_internal_to_timeline (clip, source0, 18, 30);
+ _assert_internal_to_timeline (clip, source1, 18, 30);
+ _assert_internal_to_timeline (clip, overlay, 9.5, 30);
+ _assert_frame_to_timeline (clip, 540, 30);
+ _assert_timeline_to_internal (clip, source0, 30, 18);
+ _assert_timeline_to_internal (clip, source1, 30, 18);
+ _assert_timeline_to_internal (clip, overlay, 30, 9.5);
+
+ /* beyond the end of the clip */
+ /* exceeds the max-duration of the elements, but that is ok */
+ _assert_internal_to_timeline (clip, source0, 68, 130);
+ _assert_internal_to_timeline (clip, source1, 68, 130);
+ _assert_internal_to_timeline (clip, overlay, 34.5, 130);
+ _assert_frame_to_timeline (clip, 2040, 130);
+ _assert_timeline_to_internal (clip, source0, 130, 68);
+ _assert_timeline_to_internal (clip, source1, 130, 68);
+ _assert_timeline_to_internal (clip, overlay, 130, 34.5);
+
+ /* before the start of the clip */
+ _assert_internal_to_timeline (clip, source0, 10.5, 15);
+ _assert_internal_to_timeline (clip, source1, 10.5, 15);
+ _assert_internal_to_timeline (clip, overlay, 5.75, 15);
+ _assert_frame_to_timeline (clip, 315, 15);
+ _assert_timeline_to_internal (clip, source0, 15, 10.5);
+ _assert_timeline_to_internal (clip, source1, 15, 10.5);
+ _assert_timeline_to_internal (clip, overlay, 15, 5.75);
+
+ /* not too early */
+ _assert_internal_to_timeline (clip, source0, 3, 0);
+ _assert_internal_to_timeline (clip, source1, 3, 0);
+ _assert_internal_to_timeline (clip, overlay, 2, 0);
+ _assert_frame_to_timeline (clip, 90, 0);
+ _assert_timeline_to_internal (clip, source0, 0, 3);
+ _assert_timeline_to_internal (clip, source1, 0, 3);
+ _assert_timeline_to_internal (clip, overlay, 0, 2);
+
+ /* too early for timeline */
+ _assert_internal_to_timeline_fails (clip, source0, 2,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_internal_to_timeline_fails (clip, source1, 2,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_internal_to_timeline_fails (clip, overlay, 1,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_frame_to_timeline_fails (clip, 89, GES_ERROR_INVALID_FRAME_NUMBER);
+
+ assert_set_start (clip, 30 * GST_SECOND);
+ /* timeline times have shifted by 10 */
+ _assert_timeline_to_internal (clip, source0, 10, 3);
+ _assert_timeline_to_internal (clip, source1, 10, 3);
+ _assert_timeline_to_internal (clip, overlay, 10, 2);
+
+ _assert_timeline_to_internal (clip, source0, 4, 0);
+ _assert_timeline_to_internal (clip, source1, 4, 0);
+ _assert_timeline_to_internal (clip, overlay, 2, 0);
+ /* too early for internal */
+ _assert_timeline_to_internal_fails (clip, source0, 3,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, source1, 3,
+ GES_ERROR_NEGATIVE_TIME);
+ _assert_timeline_to_internal_fails (clip, overlay, 1,
+ GES_ERROR_NEGATIVE_TIME);
+
+ g_value_unset (&val);
+ gst_object_unref (asset);
+ gst_object_unref (timeline);
+
+ ges_deinit ();
}
GST_END_TEST;
tcase_add_test (tc_chain, test_children_properties_change);
tcase_add_test (tc_chain, test_copy_paste_children_properties);
tcase_add_test (tc_chain, test_unchanged_after_layer_add_failure);
+ tcase_add_test (tc_chain, test_convert_time);
return s;
}