clip: add methods to convert between time coordinates

Add methods to convert between the timeline time coordinates and the
internal time coordinates of a track element in a clip, taking time
effects into account.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
This commit is contained in:
Henry Wilkes 2020-05-12 18:18:09 +01:00
parent 571120dcfb
commit 449bc935f1
4 changed files with 1143 additions and 37 deletions

View file

@ -138,6 +138,8 @@
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
{
@ -286,7 +288,8 @@ _duration_limit_data_list_with_data (GESClip * clip, DurationLimitData * data)
}
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)
@ -408,7 +411,8 @@ _calculate_duration_limit (GESClip * self, GList * child_data)
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;
@ -2680,6 +2684,17 @@ ges_clip_get_duration_limit (GESClip * clip)
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
@ -2702,16 +2717,13 @@ ges_clip_get_top_effects (GESClip * clip)
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
@ -3227,42 +3239,673 @@ ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
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_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
* @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
*
* 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 in the
* timeline coordinates
* @err: A #GError set on errors
* @frame_number: The frame number to get the corresponding timestamp of
* in the timeline 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 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.
*
* This method should be use specifically in the case where you want to trim the
* clip to a particular frame.
* The returned timeline time can be used to seek or edit to a specific
* frame.
*
* 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.
* 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;
@ -3272,15 +3915,16 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
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;
}
/**

View file

@ -216,9 +216,19 @@ GESClip* ges_clip_split_full (GESClip *clip,
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);

View file

@ -56,6 +56,40 @@
* #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
@ -2366,7 +2400,7 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self)
* @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

View file

@ -4776,7 +4776,424 @@ GST_START_TEST (test_unchanged_after_layer_add_failure)
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;
@ -4813,6 +5230,7 @@ ges_suite (void)
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;
}