From e835042f0475a0731f65c35b0b99b493f4e2abd0 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 21 Feb 2020 09:17:11 -0300 Subject: [PATCH] ges: Add APIs to have a sens of frame numbers APIs: - ges_timeline_get_frame_time - ges_timeline_get_frame_at - ges_clip_asset_get_frame_time - ges_clip_get_timeline_time_from_source_frame Extracting ges_util_structure_get_clocktime to internal utilities adding support for specifying timing values in frames with the special f synthax. --- ges/ges-clip-asset.c | 33 ++++ ges/ges-clip-asset.h | 2 + ges/ges-clip.c | 56 +++++++ ges/ges-clip.h | 7 +- ges/ges-command-line-formatter.c | 5 + ges/ges-gerror.h | 1 + ges/ges-internal.h | 7 + ges/ges-structured-interface.c | 87 ++++------ ges/ges-timeline.c | 101 ++++++++++++ ges/ges-timeline.h | 8 + ges/ges-types.h | 27 +++ ges/ges-utils.c | 59 +++++++ ges/ges-validate.c | 155 ++++++++++++++++-- tests/check/meson.build | 7 +- .../scenarios/check_edit_in_frames.scenario | 31 ++++ ...in_frames_with_framerate_mismatch.scenario | 23 +++ 16 files changed, 542 insertions(+), 67 deletions(-) create mode 100644 tests/check/scenarios/check_edit_in_frames.scenario create mode 100644 tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario diff --git a/ges/ges-clip-asset.c b/ges/ges-clip-asset.c index 6a18922d5f..7fef08064d 100644 --- a/ges/ges-clip-asset.c +++ b/ges/ges-clip-asset.c @@ -209,3 +209,36 @@ ges_clip_asset_get_natural_framerate (GESClipAsset * self, return FALSE; } + +/** + * ges_clip_asset_get_frame_time: + * @self: The object for which to compute timestamp for specifed frame + * @frame_number: The frame number we want the timestamp for the frame number + * inside the media scale of @self + * + * Converts the given frame number into a timestamp, using the "natural" frame + * rate of the asset. + * + * You can use this to reference a specific frame in a media file and use this + * as, for example, the `in-point` or `max-duration` of a #GESClip. + * + * Returns: The timestamp corresponding to @frame_number in the element source + * in the media scale, or #GST_CLOCK_TIME_NONE if the clip asset does not have a + * natural frame rate. + */ +GstClockTime +ges_clip_asset_get_frame_time (GESClipAsset * self, GESFrameNumber frame_number) +{ + gint fps_n, fps_d; + + g_return_val_if_fail (GES_IS_CLIP_ASSET (self), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GES_FRAME_NUMBER_IS_VALID (frame_number), + GST_CLOCK_TIME_NONE); + + + if (!ges_clip_asset_get_natural_framerate (self, &fps_n, &fps_d)) + return GST_CLOCK_TIME_NONE; + + return gst_util_uint64_scale_int_ceil (frame_number, fps_d * GST_SECOND, + fps_n); +} diff --git a/ges/ges-clip-asset.h b/ges/ges-clip-asset.h index e23adce90f..733189f994 100644 --- a/ges/ges-clip-asset.h +++ b/ges/ges-clip-asset.h @@ -57,5 +57,7 @@ GES_API GESTrackType ges_clip_asset_get_supported_formats (GESClipAsset *self); GES_API gboolean ges_clip_asset_get_natural_framerate (GESClipAsset* self, gint* framerate_n, gint* framerate_d); +GES_API +GstClockTime ges_clip_asset_get_frame_time (GESClipAsset* self, GESFrameNumber frame_number); G_END_DECLS diff --git a/ges/ges-clip.c b/ges/ges-clip.c index d631a21602..2d662a0d53 100644 --- a/ges/ges-clip.c +++ b/ges/ges-clip.c @@ -2027,3 +2027,59 @@ ges_clip_find_track_elements (GESClip * clip, GESTrack * track, return ret; } + +/** + * 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 + * + * 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. + * + * This method should be use specifically in the case where you want to trim the + * clip to a particular 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. + * + * Returns: The timestamp corresponding to @frame_number in the element source + * in the timeline coordinates. + */ +GstClockTime +ges_clip_get_timeline_time_from_source_frame (GESClip * clip, + GESFrameNumber frame_number, GError ** err) +{ + 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); + + if (!GES_FRAME_NUMBER_IS_VALID (frame_number)) + return GST_CLOCK_TIME_NONE; + + asset = GES_CLIP_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (clip))); + frame_ts = ges_clip_asset_get_frame_time (asset, frame_number); + 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; + } + + return GST_CLOCK_DIFF (inpoint_diff, _START (clip)); +} diff --git a/ges/ges-clip.h b/ges/ges-clip.h index f9712454b0..d130bd8262 100644 --- a/ges/ges-clip.h +++ b/ges/ges-clip.h @@ -185,4 +185,9 @@ gboolean ges_clip_set_top_effect_index (GESClip *clip, GESBaseEffect *effect, GES_API GESClip* ges_clip_split (GESClip *clip, guint64 position); -G_END_DECLS \ No newline at end of file +GES_API +GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip, + GESFrameNumber frame_number, + GError ** err); + +G_END_DECLS diff --git a/ges/ges-command-line-formatter.c b/ges/ges-command-line-formatter.c index 9ddbb0b8d0..3a1a6793f8 100644 --- a/ges/ges-command-line-formatter.c +++ b/ges/ges-command-line-formatter.c @@ -275,6 +275,11 @@ _convert_to_clocktime (GstStructure * structure, const gchar * name, goto done; } + if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + const gchar *v = g_value_get_string (gvalue); + return v && v[0] == 'f'; + } + if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) return 1; diff --git a/ges/ges-gerror.h b/ges/ges-gerror.h index 939e1bfd8f..1f4d8b4520 100644 --- a/ges/ges-gerror.h +++ b/ges/ges-gerror.h @@ -44,6 +44,7 @@ typedef enum GES_ERROR_ASSET_WRONG_ID, GES_ERROR_ASSET_LOADING, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE, + GES_ERROR_INVALID_FRAME_NUMBER, } GESError; G_END_DECLS \ No newline at end of file diff --git a/ges/ges-internal.h b/ges/ges-internal.h index b3bdb9a8fc..6fe876f33c 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -152,6 +152,9 @@ timeline_fill_gaps (GESTimeline *timeline); G_GNUC_INTERNAL void timeline_create_transitions (GESTimeline * timeline, GESTrackElement * track_element); +G_GNUC_INTERNAL void timeline_get_framerate(GESTimeline *self, gint *fps_n, + gint *fps_d); + G_GNUC_INTERNAL void track_resort_and_fill_gaps (GESTrack *track); @@ -371,6 +374,10 @@ ges_get_compositor_factory (void); G_GNUC_INTERNAL void ges_idle_add (GSourceFunc func, gpointer udata, GDestroyNotify notify); +G_GNUC_INTERNAL gboolean +ges_util_structure_get_clocktime (GstStructure *structure, const gchar *name, + GstClockTime *val, GESFrameNumber *frames); + /**************************************************** * GESContainer * diff --git a/ges/ges-structured-interface.c b/ges/ges-structured-interface.c index cfc52bb3f5..abfd2503af 100644 --- a/ges/ges-structured-interface.c +++ b/ges/ges-structured-interface.c @@ -22,6 +22,7 @@ #endif #include "ges-structured-interface.h" +#include "ges-internal.h" #include @@ -29,51 +30,11 @@ #define LAST_CONTAINER_QDATA g_quark_from_string("ges-structured-last-container") #define LAST_CHILD_QDATA g_quark_from_string("ges-structured-last-child") -static gboolean -_get_clocktime (GstStructure * structure, const gchar * name, gpointer var) -{ - gboolean found = FALSE; - GstClockTime *val = (GstClockTime *) var; - - const GValue *gvalue = gst_structure_get_value (structure, name); - - if (gvalue) { - if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) { - *val = (GstClockTime) g_value_get_uint64 (gvalue); - found = TRUE; - } else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) { - *val = (GstClockTime) g_value_get_uint64 (gvalue); - found = TRUE; - } else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT) { - *val = (GstClockTime) g_value_get_uint (gvalue); - found = TRUE; - } else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT) { - *val = (GstClockTime) g_value_get_int (gvalue); - found = TRUE; - } else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64) { - *val = (GstClockTime) g_value_get_int64 (gvalue); - found = TRUE; - } else if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE) { - gdouble d = g_value_get_double (gvalue); - - found = TRUE; - if (d == -1.0) - *val = GST_CLOCK_TIME_NONE; - else { - *val = d * GST_SECOND; - *val = GST_ROUND_UP_4 (*val); - } - } - } - - return found; -} - #define GET_AND_CHECK(name,type,var,label) G_STMT_START {\ gboolean found = FALSE; \ \ if (type == GST_TYPE_CLOCK_TIME) {\ - found = _get_clocktime(structure,name,var);\ + found = ges_util_structure_get_clocktime (structure,name, (GstClockTime*)var,NULL);\ }\ else { \ found = gst_structure_get (structure, name, type, var, NULL); \ @@ -94,13 +55,17 @@ _get_clocktime (GstStructure * structure, const gchar * name, gpointer var) *var = def; \ } G_STMT_END -#define TRY_GET(name,type,var,def) G_STMT_START {\ - if (type == GST_TYPE_CLOCK_TIME) {\ - if (!_get_clocktime(structure,name,var))\ - *var = def; \ - } else if (!gst_structure_get (structure, name, type, var, NULL)) {\ - *var = def; \ - } \ +#define TRY_GET_TIME(name, var, var_frames, def) G_STMT_START { \ + if (!ges_util_structure_get_clocktime (structure, name, var, var_frames)) { \ + *var = def; \ + *var_frames = GES_FRAME_NUMBER_NONE; \ + } \ +} G_STMT_END + +#define TRY_GET(name, type, var, def) G_STMT_START {\ + g_assert (type != GST_TYPE_CLOCK_TIME); \ + if (!gst_structure_get (structure, name, type, var, NULL))\ + *var = def; \ } G_STMT_END typedef struct @@ -418,6 +383,8 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure, gboolean res = FALSE; GESTrackType track_types = GES_TRACK_TYPE_UNKNOWN; + GESFrameNumber start_frame = GES_FRAME_NUMBER_NONE, inpoint_frame = + GES_FRAME_NUMBER_NONE, duration_frame = GES_FRAME_NUMBER_NONE; GstClockTime duration = 1 * GST_SECOND, inpoint = 0, start = GST_CLOCK_TIME_NONE; @@ -441,9 +408,9 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure, if (layer_priority == -1) TRY_GET ("layer", G_TYPE_INT, &layer_priority, -1); TRY_GET_STRING ("type", &type_string, "GESUriClip"); - TRY_GET ("start", GST_TYPE_CLOCK_TIME, &start, GST_CLOCK_TIME_NONE); - TRY_GET ("inpoint", GST_TYPE_CLOCK_TIME, &inpoint, 0); - TRY_GET ("duration", GST_TYPE_CLOCK_TIME, &duration, GST_CLOCK_TIME_NONE); + TRY_GET_TIME ("start", &start, &start_frame, GST_CLOCK_TIME_NONE); + TRY_GET_TIME ("inpoint", &inpoint, &inpoint_frame, 0); + TRY_GET_TIME ("duration", &duration, &duration_frame, GST_CLOCK_TIME_NONE); TRY_GET_STRING ("track-types", &track_types_str, NULL); TRY_GET_STRING ("project-uri", &nested_timeline_id, NULL); @@ -499,6 +466,24 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure, goto beach; } + if (GES_FRAME_NUMBER_IS_VALID (start_frame)) + start = ges_timeline_get_frame_time (timeline, start_frame); + + if (GES_FRAME_NUMBER_IS_VALID (inpoint_frame)) { + inpoint = + ges_clip_asset_get_frame_time (GES_CLIP_ASSET (asset), inpoint_frame); + if (!GST_CLOCK_TIME_IS_VALID (inpoint)) { + *error = + g_error_new (GES_ERROR, 0, "Could not get inpoint from frame %" + G_GINT64_FORMAT, inpoint_frame); + goto beach; + } + } + + if (GES_FRAME_NUMBER_IS_VALID (duration_frame)) { + duration = ges_timeline_get_frame_time (timeline, duration_frame); + } + if (GES_IS_URI_CLIP_ASSET (asset) && !GST_CLOCK_TIME_IS_VALID (duration)) { duration = GST_CLOCK_DIFF (inpoint, ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset))); diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 28a8625540..c335ae7ebc 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -1012,6 +1012,55 @@ ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1, } +/* Accept @self == NULL, making it use default framerate */ +void +timeline_get_framerate (GESTimeline * self, gint * fps_n, gint * fps_d) +{ + GList *tmp; + + if (!self) + goto done; + + *fps_n = *fps_d = -1; + LOCK_DYN (self); + for (tmp = self->tracks; tmp; tmp = tmp->next) { + if (GES_IS_VIDEO_TRACK (tmp->data)) { + GstCaps *restriction = ges_track_get_restriction_caps (tmp->data); + gint i; + + for (i = 0; i < gst_caps_get_size (restriction); i++) { + gint n, d; + + if (!gst_structure_get_fraction (gst_caps_get_structure (restriction, + i), "framerate", &n, &d)) + continue; + + if (*fps_n != -1 && *fps_d != -1 && !(n == *fps_n && d == *fps_d)) { + GST_WARNING_OBJECT (self, + "Various framerates specified, this is not supported" + " First one will be used."); + continue; + } + + *fps_n = n; + *fps_d = d; + } + gst_caps_unref (restriction); + } + } + UNLOCK_DYN (self); + +done: + if (*fps_n == -1 && *fps_d == -1) { + GST_INFO_OBJECT (self, + "No framerate found, using default " G_STRINGIFY (FRAMERATE_N) "/ " + G_STRINGIFY (FRAMERATE_D)); + *fps_n = DEFAULT_FRAMERATE_N; + *fps_d = DEFAULT_FRAMERATE_D; + } +} + + gboolean ges_timeline_trim_object_simple (GESTimeline * timeline, GESTimelineElement * element, guint32 new_layer_priority, @@ -2854,3 +2903,55 @@ ges_timeline_move_layer (GESTimeline * timeline, GESLayer * layer, return TRUE; } + +/** + * ges_timeline_get_frame_time: + * @self: The self on which to retrieve the timestamp for @frame_number + * @frame_number: The frame number to get the corresponding timestamp of in the + * timeline coordinates + * + * This method allows you to convert a timeline output frame number into a + * timeline #GstClockTime. For example, this time could be used to seek to a + * particular frame in the timeline's output, or as the edit position for + * an element within the timeline. + * + * Returns: The timestamp corresponding to @frame_number in the output of @self. + */ +GstClockTime +ges_timeline_get_frame_time (GESTimeline * self, GESFrameNumber frame_number) +{ + gint fps_n, fps_d; + + g_return_val_if_fail (GES_IS_TIMELINE (self), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GES_FRAME_NUMBER_IS_VALID (frame_number), + GST_CLOCK_TIME_NONE); + + timeline_get_framerate (self, &fps_n, &fps_d); + + return gst_util_uint64_scale_int_ceil (frame_number, + fps_d * GST_SECOND, fps_n); +} + +/** + * ges_timeline_get_frame_at: + * @self: A #GESTimeline + * @timestamp: The timestamp to get the corresponding frame number of + * + * This method allows you to convert a timeline #GstClockTime into its + * corresponding #GESFrameNumber in the timeline's output. + * + * Returns: The frame number @timestamp corresponds to. + */ +GESFrameNumber +ges_timeline_get_frame_at (GESTimeline * self, GstClockTime timestamp) +{ + gint fps_n, fps_d; + + g_return_val_if_fail (GES_IS_TIMELINE (self), GES_FRAME_NUMBER_NONE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), + GES_FRAME_NUMBER_NONE); + + timeline_get_framerate (self, &fps_n, &fps_d); + + return gst_util_uint64_scale (timestamp, fps_n, fps_d * GST_SECOND); +} diff --git a/ges/ges-timeline.h b/ges/ges-timeline.h index e4acc858cf..16a800b2c0 100644 --- a/ges/ges-timeline.h +++ b/ges/ges-timeline.h @@ -148,4 +148,12 @@ GESTimelineElement * ges_timeline_paste_element (GESTimeline * timeline, GES_API gboolean ges_timeline_move_layer (GESTimeline *timeline, GESLayer *layer, guint new_layer_priority); +GES_API +GstClockTime ges_timeline_get_frame_time(GESTimeline *self, + GESFrameNumber frame_number); + +GES_API +GESFrameNumber ges_timeline_get_frame_at (GESTimeline *self, + GstClockTime timestamp); + G_END_DECLS diff --git a/ges/ges-types.h b/ges/ges-types.h index e6ca008683..2c0605c4a7 100644 --- a/ges/ges-types.h +++ b/ges/ges-types.h @@ -35,6 +35,33 @@ G_BEGIN_DECLS */ #define GES_PADDING_LARGE 20 +/** + * GESFrameNumber: + * + * A datatype to hold a frame number. + */ +typedef gint64 GESFrameNumber; + +/** + * GES_FRAME_NUMBER_NONE: (value 9223372036854775807) (type GESFrameNumber) + * + * Constant to define an undefined frame number + */ +#define GES_FRAME_NUMBER_NONE ((gint64) 9223372036854775807) + +/** + * GES_FRAME_NUMBER_IS_VALID: + * Tests if a given GESFrameNumber represents a valid frame + */ +#define GES_FRAME_NUMBER_IS_VALID(frames) (((GESFrameNumber) frames) != GES_FRAME_NUMBER_NONE) + +/** + * GES_TYPE_FRAME_NUMBER: + * + * The #GType of a #GESFrameNumber. + */ +#define GES_TYPE_FRAME_NUMBER G_TYPE_UINT64 + /* Type definitions */ typedef struct _GESTimeline GESTimeline; diff --git a/ges/ges-utils.c b/ges/ges-utils.c index fcfcd0f661..d8baacf0a3 100644 --- a/ges/ges-utils.c +++ b/ges/ges-utils.c @@ -144,6 +144,65 @@ find_compositor (GstPluginFeatureFilter * feature, gpointer udata) return (strstr (klass, "Compositor") != NULL); } +gboolean +ges_util_structure_get_clocktime (GstStructure * structure, const gchar * name, + GstClockTime * val, GESFrameNumber * frames) +{ + gboolean found = FALSE; + + const GValue *gvalue; + + if (!val && !frames) + return FALSE; + + gvalue = gst_structure_get_value (structure, name); + if (!gvalue) + return FALSE; + + if (frames) + *frames = GES_FRAME_NUMBER_NONE; + + found = TRUE; + if (val && G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) { + *val = (GstClockTime) g_value_get_uint64 (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) { + *val = (GstClockTime) g_value_get_uint64 (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_UINT) { + *val = (GstClockTime) g_value_get_uint (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_INT) { + *val = (GstClockTime) g_value_get_int (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_INT64) { + *val = (GstClockTime) g_value_get_int64 (gvalue); + } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE) { + gdouble d = g_value_get_double (gvalue); + + if (d == -1.0) + *val = GST_CLOCK_TIME_NONE; + else + *val = d * GST_SECOND; + } else if (frames && G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + const gchar *str = g_value_get_string (gvalue); + + found = FALSE; + if (str && str[0] == 'f') { + GValue v = G_VALUE_INIT; + + g_value_init (&v, G_TYPE_UINT64); + if (gst_value_deserialize (&v, &str[1])) { + *frames = g_value_get_uint64 (&v); + if (val) + *val = GST_CLOCK_TIME_NONE; + found = TRUE; + } + g_value_reset (&v); + } + } else { + found = FALSE; + + } + + return found; +} GstElementFactory * diff --git a/ges/ges-validate.c b/ges/ges-validate.c index e1e428627f..56ae607da2 100644 --- a/ges/ges-validate.c +++ b/ges/ges-validate.c @@ -40,6 +40,40 @@ typedef struct GError *error; } LoadTimelineData; +static gboolean +_get_clocktime (GstStructure * structure, const gchar * name, + GstClockTime * val, GESFrameNumber * frames) +{ + const GValue *gvalue = gst_structure_get_value (structure, name); + + if (!gvalue) + return FALSE; + + if (frames && G_VALUE_TYPE (gvalue) == G_TYPE_STRING) { + const gchar *str = g_value_get_string (gvalue); + + if (str && str[0] == 'f') { + GValue v = G_VALUE_INIT; + + g_value_init (&v, G_TYPE_UINT64); + if (gst_value_deserialize (&v, &str[1])) { + *frames = g_value_get_uint64 (&v); + if (val) + *val = GST_CLOCK_TIME_NONE; + g_value_reset (&v); + + return TRUE; + } + g_value_reset (&v); + } + } + + if (!val) + return FALSE; + + return gst_validate_utils_get_clocktime (structure, name, val); +} + static void project_loaded_cb (GESProject * project, GESTimeline * timeline, LoadTimelineData * data) @@ -332,8 +366,11 @@ _edit (GstValidateScenario * scenario, GstValidateAction * action) { GList *layers = NULL; GESTimelineElement *element; + GESFrameNumber fposition = GES_FRAME_NUMBER_NONE; GstClockTime position; gboolean res = FALSE; + GError *err = NULL; + gboolean source_position = FALSE; gint new_layer_priority = -1; guint edge = GES_EDGE_NONE; @@ -356,20 +393,85 @@ _edit (GstValidateScenario * scenario, GstValidateAction * action) return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; } - if (!gst_validate_action_get_clocktime (scenario, action, - "position", &position)) { - GST_WARNING ("Could not get position"); - goto beach; + if (!_get_clocktime (action->structure, "position", &position, &fposition)) { + fposition = 0; + if (!gst_structure_get_int (action->structure, "source-frame", + (gint *) & fposition) + && !gst_structure_get_int64 (action->structure, "source-frame", + &fposition)) { + gchar *structstr = gst_structure_to_string (action->structure); + + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "could not find `position` or `source-frame` in %s", structstr); + g_free (structstr); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto beach; + } + + source_position = TRUE; + position = GST_CLOCK_TIME_NONE; } if ((edit_mode_str = - gst_structure_get_string (action->structure, "edit-mode"))) - g_return_val_if_fail (gst_validate_utils_enum_from_str (GES_TYPE_EDIT_MODE, - edit_mode_str, &mode), FALSE); + gst_structure_get_string (action->structure, "edit-mode"))) { + if (!gst_validate_utils_enum_from_str (GES_TYPE_EDIT_MODE, edit_mode_str, + &mode)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, "Could not get enum from %s", + edit_mode_str); - if ((edge_str = gst_structure_get_string (action->structure, "edge"))) - g_return_val_if_fail (gst_validate_utils_enum_from_str (GES_TYPE_EDGE, - edge_str, &edge), FALSE); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto beach; + } + } + + if ((edge_str = gst_structure_get_string (action->structure, "edge"))) { + if (!gst_validate_utils_enum_from_str (GES_TYPE_EDGE, edge_str, &edge)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not get enum from %s", edge_str); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto beach; + } + } + + if (GES_FRAME_NUMBER_IS_VALID (fposition)) { + if (source_position) { + GESClip *clip = NULL; + + if (GES_IS_CLIP (element)) + clip = GES_CLIP (element); + else if (GES_IS_TRACK_ELEMENT (element)) + clip = GES_CLIP (element->parent); + + if (!clip) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not get find element to edit using source frame for %" + GST_PTR_FORMAT, action->structure); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto beach; + } + + position = ges_clip_get_timeline_time_from_source_frame (clip, fposition, + &err); + } else { + position = ges_timeline_get_frame_time (timeline, fposition); + } + + if (!GST_CLOCK_TIME_IS_VALID (position)) { + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Invalid frame number '%" G_GINT64_FORMAT "': %s", fposition, + err->message); + + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; + goto beach; + } + } gst_structure_get_int (action->structure, "new-layer-priority", &new_layer_priority); @@ -383,13 +485,30 @@ _edit (GstValidateScenario * scenario, GstValidateAction * action) if (!(res = ges_timeline_element_edit (element, layers, new_layer_priority, mode, edge, position))) { - gst_object_unref (element); + + gchar *fpositionstr = GES_FRAME_NUMBER_IS_VALID (fposition) + ? g_strdup_printf ("(%" G_GINT64_FORMAT ")", fposition) + : NULL; + + GST_VALIDATE_REPORT_ACTION (scenario, action, + SCENARIO_ACTION_EXECUTION_ERROR, + "Could not edit '%s' to %" GST_TIME_FORMAT + "%s in %s mode, edge: %s " + "with new layer prio: %d", + element_name, GST_TIME_ARGS (position), + fpositionstr ? fpositionstr : "", + edit_mode_str ? edit_mode_str : "normal", + edge_str ? edge_str : "None", new_layer_priority); + g_free (fpositionstr); + res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; goto beach; } - gst_object_unref (element); SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action); + beach: + gst_clear_object (&element); + g_clear_error (&err); g_object_unref (timeline); return res; } @@ -1089,7 +1208,7 @@ ges_validate_register_action_types (void) { .name = "position", .description = "The new position of the GESContainer", - .mandatory = TRUE, + .mandatory = FALSE, .types = "double or string", .possible_variables = "position: The current position in the stream\n" "duration: The duration of the stream", @@ -1143,12 +1262,20 @@ ges_validate_register_action_types (void) { .name = "position", .description = "The new position of the element", - .mandatory = TRUE, + .mandatory = FALSE, .types = "double or string", .possible_variables = "position: The current position in the stream\n" "duration: The duration of the stream", NULL }, + { + .name = "source-frame", + .description = "The new frame of the element, computed from the @element-name" + "clip's source frame.", + .mandatory = FALSE, + .types = "double or string", + NULL + }, { .name = "edit-mode", .description = "The GESEditMode to use to edit @element-name", diff --git a/tests/check/meson.build b/tests/check/meson.build index cf0ba74c61..a905398636 100644 --- a/tests/check/meson.build +++ b/tests/check/meson.build @@ -72,7 +72,12 @@ foreach t : ges_tests endforeach if gstvalidate_dep.found() - scenarios = ['check_video_track_restriction_scale', 'check_video_track_restriction_scale_with_keyframes'] + scenarios = [ + 'check_video_track_restriction_scale', + 'check_video_track_restriction_scale_with_keyframes', + 'check_edit_in_frames', + 'check_edit_in_frames_with_framerate_mismatch', + ] foreach scenario: scenarios scenario_file = join_paths(meson.current_source_dir(), 'scenarios', scenario + '.scenario') diff --git a/tests/check/scenarios/check_edit_in_frames.scenario b/tests/check/scenarios/check_edit_in_frames.scenario new file mode 100644 index 0000000000..39a1eb596d --- /dev/null +++ b/tests/check/scenarios/check_edit_in_frames.scenario @@ -0,0 +1,31 @@ +description, handles-states=true, + ges-options={\ + --track-types, video\ + } + +add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=f0, inpoint=f30, duration=f60 + +check-ges-properties, element-name=clip, start=0, in-point=1.0, duration=2.0 +edit, element-name=clip, position=f30 + +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0 + +# Getting the 60th frame in the input media file, means inpoint=f30 + f30 = f60 +edit, element-name=clip, position=f60, edit-mode=edit_trim, edge=edge_end +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=1.0 + +set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1280,height=720,framerate=60/1" + +# 60 frames in media time, meaning 90 - inpoint (30) / 30 = 2 seconds +edit, element-name=clip, source-frame=90, edit-mode=edit_trim, edge=edge_end +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0 + +# 60 frames in timeline time, meaning 60/60 = 1 second +edit, element-name=clip, position=f60 +check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0 + +# 60 frames in timeline time, meaning 60/60 = 1 second +edit, element-name=clip, source-frame=75, edit-mode=trim, edge=start +check-ges-properties, element-name=clip, start=2.5, in-point=2.5, duration=0.5 + +stop \ No newline at end of file diff --git a/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario b/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario new file mode 100644 index 0000000000..fefa4c902b --- /dev/null +++ b/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario @@ -0,0 +1,23 @@ +description, handles-states=true, + ges-options={\ + --track-types, video, + --disable-mixing, + "--videosink=fakevideosink"\ + } + +add-clip, name=clip, asset-id="framerate=120/1", layer-priority=0, type=GESTestClip, pattern=blue, duration=f240, inpoint=f100 +set-child-properties, element-name=clip, time-mode=time-code +pause + +check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=100 + +edit, element-name=clip, edit-mode=normal, position=1.0 + +edit, element-name=clip, edit-mode=edit_trim, source-frame=60 +edit, element-name=clip, position=0 +commit; +check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=60 + +edit, element-name=clip, edit-mode=edit_trim, edit-edge=end, source-frame=120 +check-ges-properties, element-name=clip, start=0.5 +stop \ No newline at end of file