mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 18:50:48 +00:00
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:
parent
571120dcfb
commit
449bc935f1
4 changed files with 1143 additions and 37 deletions
712
ges/ges-clip.c
712
ges/ges-clip.c
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue