ges: support test clips assets natural size/framerate

This way we can test this kind of behaviour without requiring
real sources.

Also add simple tests.
This commit is contained in:
Thibault Saunier 2020-02-28 11:47:25 -03:00
parent e835042f04
commit 0c87b44fae
8 changed files with 314 additions and 18 deletions

View file

@ -386,8 +386,10 @@ _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
return FALSE;
gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
gst_structure_set (structure, "asset-id", G_TYPE_STRING,
gst_structure_get_string (structure, "pattern"), NULL);
if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
NULL);
return _ges_add_clip_from_struct (timeline, structure, error);
}

View file

@ -81,6 +81,11 @@ G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
static gchar *
extractable_get_id (GESExtractable * self)
{
GESAsset *asset;
if ((asset = ges_extractable_get_asset (self)))
return g_strdup (ges_asset_get_id (asset));
return g_strdup (g_type_name (G_OBJECT_TYPE (self)));
}

View file

@ -91,8 +91,9 @@ typedef gchar* (*GESExtractableCheckId) (GType type, const gchar *id,
* set, or even that an asset with such an #GESAsset:id does not exist in
* the GES cache. Instead, this should return the #GESAsset:id that is
* _compatible_ with the current state of the object. The default
* implementation simply returns the type name of the object, which is
* what is used as the #GESAsset:id by default.
* implementation simply returns the currently set asset ID, or the type name
* of the object, which is what is used as the #GESAsset:id by default,
* if no asset is set.
* @get_real_extractable_type: The method to call to get the actual
* #GESAsset:extractable-type an asset should have set, given the
* requested #GESAsset:id. The default implementation simply returns the

View file

@ -83,6 +83,8 @@ GstDebugCategory * _ges_debug (void);
#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
#define SUPRESS_UNUSED_WARNING(a) (void)a
G_GNUC_INTERNAL gboolean
timeline_ripple_object (GESTimeline *timeline, GESTimelineElement *obj,
gint new_layer_priority,
@ -489,6 +491,13 @@ G_GNUC_INTERNAL GESMultiFileURI * ges_multi_file_uri_new (const gchar * uri);
G_GNUC_INTERNAL gboolean
ges_video_uri_source_get_natural_size(GESVideoSource* source, gint* width, gint* height);
/**********************************
* GESTestClipAsset internal API *
**********************************/
G_GNUC_INTERNAL gboolean ges_test_clip_asset_get_natural_size(GESAsset *self,
gint *width,
gint *height);
/************************
* Our property masks *
************************/

View file

@ -185,6 +185,5 @@ ges_structure_parser_get_error (GESStructureParser * self)
error = g_error_new_literal (GES_ERROR, 0, msg->str);
g_string_free (msg, TRUE);
GST_ERROR ("BoOOOM ");
return error;
}

View file

@ -25,8 +25,16 @@
*
* Useful for testing purposes.
*
* You can use the ges_asset_request_simple API to create an Asset
* capable of extracting GESTestClip-s
* ## Asset
*
* The default asset ID is GESTestClip, but the framerate and video
* size can be overriden using an ID of the form:
*
* ```
* framerate=60/1, width=1920, height=1080, max-duration=5.0
* ```
* Note: `max-duration` can be provided in seconds as float, or as GstClockTime
* as guint64 or gint.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
@ -43,6 +51,98 @@
#define DEFAULT_VOLUME 1.0
#define DEFAULT_VPATTERN GES_VIDEO_TEST_PATTERN_SMPTE
G_DECLARE_FINAL_TYPE (GESTestClipAsset, ges_test_clip_asset, GES,
TEST_CLIP_ASSET, GESClipAsset);
struct _GESTestClipAsset
{
GESClipAsset parent;
gint natural_framerate_n;
gint natural_framerate_d;
gint natural_width;
gint natural_height;
GstClockTime max_duration;
};
#define GES_TYPE_TEST_CLIP_ASSET (ges_test_clip_asset_get_type())
G_DEFINE_TYPE (GESTestClipAsset, ges_test_clip_asset, GES_TYPE_CLIP_ASSET);
static gboolean
_get_natural_framerate (GESClipAsset * asset, gint * framerate_n,
gint * framerate_d)
{
GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset);
*framerate_n = self->natural_framerate_n;
*framerate_d = self->natural_framerate_d;
return TRUE;
}
static GstClockTime
ges_test_clip_asset_get_max_duration (GESAsset * asset)
{
GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset);
return GES_TEST_CLIP_ASSET (self)->max_duration;
}
gboolean
ges_test_clip_asset_get_natural_size (GESAsset * asset, gint * width,
gint * height)
{
GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset);
*width = self->natural_width;
*height = self->natural_height;
return TRUE;
}
static void
ges_test_clip_asset_constructed (GObject * gobject)
{
GESFrameNumber fmax_dur = GES_FRAME_NUMBER_NONE;
GESTestClipAsset *self = GES_TEST_CLIP_ASSET (gobject);
GstStructure *structure =
gst_structure_from_string (ges_asset_get_id (GES_ASSET (self)), NULL);
g_assert (structure);
gst_structure_get_int (structure, "width", &self->natural_width);
gst_structure_get_int (structure, "height", &self->natural_height);
gst_structure_get_fraction (structure, "framerate",
&self->natural_framerate_n, &self->natural_framerate_d);
ges_util_structure_get_clocktime (structure, "max-duration",
&self->max_duration, &fmax_dur);
if (GES_FRAME_NUMBER_IS_VALID (fmax_dur))
self->max_duration =
gst_util_uint64_scale (fmax_dur, self->natural_framerate_d * GST_SECOND,
self->natural_framerate_n);
gst_structure_free (structure);
G_OBJECT_CLASS (ges_test_clip_asset_parent_class)->constructed (gobject);
}
static void
ges_test_clip_asset_class_init (GESTestClipAssetClass * klass)
{
GESClipAssetClass *clip_asset_class = GES_CLIP_ASSET_CLASS (klass);
clip_asset_class->get_natural_framerate = _get_natural_framerate;
G_OBJECT_CLASS (klass)->constructed = ges_test_clip_asset_constructed;
}
static void
ges_test_clip_asset_init (GESTestClipAsset * self)
{
self->natural_width = DEFAULT_WIDTH;
self->natural_height = DEFAULT_HEIGHT;
self->natural_framerate_n = DEFAULT_FRAMERATE_N;
self->natural_framerate_d = DEFAULT_FRAMERATE_D;
self->max_duration = GST_CLOCK_TIME_NONE;
}
struct _GESTestClipPrivate
{
gboolean mute;
@ -60,7 +160,83 @@ enum
PROP_VOLUME,
};
G_DEFINE_TYPE_WITH_PRIVATE (GESTestClip, ges_test_clip, GES_TYPE_SOURCE_CLIP);
typedef struct
{
const gchar *name;
GType type;
} ValidField;
static gchar *
ges_extractable_check_id (GType type, const gchar * id, GError ** error)
{
if (id && g_strcmp0 (id, g_type_name (type))) {
gchar *struct_str = g_strdup_printf ("%s,%s", g_type_name (type), id);
gchar *res = NULL;
GstStructure *structure = gst_structure_from_string (struct_str, NULL);
GST_DEBUG ("Structure is %s %" GST_PTR_FORMAT, struct_str, structure);
if (!structure) {
g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
"GESTestClipAsset ID should be in the form: `framerate=30/1, "
"width=1920, height=1080, got %s", id);
} else {
static ValidField valid_fields[] = {
{"width", G_TYPE_INT},
{"height", G_TYPE_INT},
{"framerate", G_TYPE_NONE}, /* GST_TYPE_FRACTION is not constant */
{"max-duration", GST_TYPE_CLOCK_TIME},
};
gint i;
for (i = 0; i < G_N_ELEMENTS (valid_fields); i++) {
if (gst_structure_has_field (structure, valid_fields[i].name)) {
GstClockTime ts;
GESFrameNumber fn;
ValidField field = valid_fields[i];
GType type =
field.type == G_TYPE_NONE ? GST_TYPE_FRACTION : field.type;
if (!(gst_structure_has_field_typed (structure, field.name,
type) ||
(type == GST_TYPE_CLOCK_TIME &&
ges_util_structure_get_clocktime (structure, field.name,
&ts, &fn)))) {
g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
"Field %s has wrong type, %s, expected %s", field.name,
g_type_name (gst_structure_get_field_type (structure,
field.name)), g_type_name (type));
gst_structure_free (structure);
g_free (struct_str);
return FALSE;
}
}
}
res = gst_structure_to_string (structure);
gst_structure_free (structure);
}
g_free (struct_str);
return res;
}
return g_strdup (g_type_name (type));
}
static void
ges_extractable_interface_init (GESExtractableInterface * iface)
{
iface->asset_type = GES_TYPE_TEST_CLIP_ASSET;
iface->check_id = ges_extractable_check_id;
}
G_DEFINE_TYPE_WITH_CODE (GESTestClip, ges_test_clip, GES_TYPE_SOURCE_CLIP,
G_ADD_PRIVATE (GESTestClip)
G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
ges_extractable_interface_init));
static GESTrackElement
* ges_test_clip_create_track_element (GESClip * clip, GESTrackType type);
@ -169,6 +345,7 @@ ges_test_clip_class_init (GESTestClipClass * klass)
static void
ges_test_clip_init (GESTestClip * self)
{
SUPRESS_UNUSED_WARNING (GES_IS_TEST_CLIP_ASSET);
self->priv = ges_test_clip_get_instance_private (self);
self->priv->freq = 0;
@ -331,6 +508,7 @@ ges_test_clip_get_volume (GESTestClip * self)
static GESTrackElement *
ges_test_clip_create_track_element (GESClip * clip, GESTrackType type)
{
GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
GESTestClipPrivate *priv = GES_TEST_CLIP (clip)->priv;
GESTrackElement *res = NULL;
@ -351,6 +529,10 @@ ges_test_clip_create_track_element (GESClip * clip, GESTrackType type)
ges_audio_test_source_set_volume ((GESAudioTestSource *) res, priv->volume);
}
if (asset)
ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (res),
ges_test_clip_asset_get_max_duration (asset));
return res;
}

View file

@ -40,12 +40,12 @@ struct _GESVideoTestSourcePrivate
gboolean use_overlay;
GstElement *overlay;
GstPad *is_passthrough_pad;
GstPad *os_passthrough_pad;
GstPad *is_overlay_pad;
GstPad *os_overlay_pad;
GstElement *capsfilter;
};
G_DEFINE_TYPE_WITH_PRIVATE (GESVideoTestSource, ges_video_test_source,
@ -56,8 +56,20 @@ static GstElement *ges_video_test_source_create_source (GESTrackElement * self);
static gboolean
get_natural_size (GESVideoSource * source, gint * width, gint * height)
{
*width = DEFAULT_WIDTH;
*height = DEFAULT_HEIGHT;
gboolean res = FALSE;
GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (source);
if (parent) {
GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (parent));
if (asset)
res = ges_test_clip_asset_get_natural_size (asset, width, height);
}
if (!res) {
*width = DEFAULT_WIDTH;
*height = DEFAULT_HEIGHT;
}
return TRUE;
}
@ -157,6 +169,65 @@ done:
->set_child_property (self, child, pspec, value);
}
static gboolean
_set_parent (GESTimelineElement * element, GESTimelineElement * parent)
{
GESVideoTestSource *self = GES_VIDEO_TEST_SOURCE (element);
if (parent) {
gint width, height, fps_n, fps_d;
GstCaps *caps;
g_assert (self->priv->capsfilter);
/* Setting the parent ourself as we need it to get the natural size */
element->parent = parent;
if (!ges_video_source_get_natural_size (GES_VIDEO_SOURCE (self), &width,
&height)) {
width = DEFAULT_WIDTH;
height = DEFAULT_HEIGHT;
}
if (!ges_timeline_element_get_natural_framerate (parent, &fps_n, &fps_d)) {
fps_n = DEFAULT_FRAMERATE_N;
fps_d = DEFAULT_FRAMERATE_D;
}
caps = gst_caps_new_simple ("video/x-raw",
"width", G_TYPE_INT, width,
"height", G_TYPE_INT, height,
"framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
g_object_set (self->priv->capsfilter, "caps", caps, NULL);
gst_caps_unref (caps);
}
return TRUE;
}
static gboolean
_get_natural_framerate (GESTimelineElement * element, gint * fps_n,
gint * fps_d)
{
gboolean res = FALSE;
GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (element);
if (parent) {
GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (parent));
if (asset) {
res =
ges_clip_asset_get_natural_framerate (GES_CLIP_ASSET (asset), fps_n,
fps_d);
}
}
if (!res) {
*fps_n = DEFAULT_FRAMERATE_N;
*fps_d = DEFAULT_FRAMERATE_D;
}
return TRUE;
}
static void
dispose (GObject * object)
{
@ -192,14 +263,18 @@ ges_video_test_source_class_init (GESVideoTestSourceClass * klass)
*/
properties[PROP_USE_TIME_OVERLAY] =
g_param_spec_boolean ("use-time-overlay", "Use-time-overlay",
"Use time overlay, setting a child property corresponding to GstTimeOverlay will switch this on"
" by default.", FALSE, G_PARAM_READWRITE);
"Use time overlay, setting a child property corresponding to"
"GstTimeOverlay will switch this on by default.", FALSE,
G_PARAM_READWRITE);
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->dispose = dispose;
GES_TIMELINE_ELEMENT_CLASS (klass)->set_child_property = _set_child_property;
GES_TIMELINE_ELEMENT_CLASS (klass)->set_parent = _set_parent;
GES_TIMELINE_ELEMENT_CLASS (klass)->get_natural_framerate =
_get_natural_framerate;
g_object_class_install_properties (object_class, PROP_LAST, properties);
}
@ -275,25 +350,26 @@ ges_video_test_source_create_source (GESTrackElement * element)
{
GstCaps *caps;
gint pattern;
GstElement *testsrc, *capsfilter, *res;
GstElement *testsrc, *res;
const gchar *props[] = { "pattern", NULL };
GPtrArray *elements;
GESVideoTestSource *self = GES_VIDEO_TEST_SOURCE (element);
g_assert (!GES_TIMELINE_ELEMENT_PARENT (element));
testsrc = gst_element_factory_make ("videotestsrc", NULL);
capsfilter = gst_element_factory_make ("capsfilter", NULL);
self->priv->capsfilter = gst_element_factory_make ("capsfilter", NULL);
pattern = self->priv->pattern;
g_object_set (testsrc, "pattern", pattern, NULL);
elements = g_ptr_array_new ();
g_ptr_array_add (elements, capsfilter);
g_ptr_array_add (elements, self->priv->capsfilter);
caps = gst_caps_new_simple ("video/x-raw",
"width", G_TYPE_INT, DEFAULT_WIDTH,
"height", G_TYPE_INT, DEFAULT_HEIGHT,
"framerate", GST_TYPE_FRACTION, DEFAULT_FRAMERATE_N, DEFAULT_FRAMERATE_D,
NULL);
g_object_set (capsfilter, "caps", caps, NULL);
g_object_set (self->priv->capsfilter, "caps", caps, NULL);
gst_caps_unref (caps);
self->priv->overlay = ges_video_test_source_create_overlay (self);

View file

@ -200,6 +200,28 @@ class TestTimeline(common.GESSimpleTimelineTest):
]
])
def test_frame_info(self):
self.track_types = [GES.TrackType.VIDEO]
super().setUp()
vtrack, = self.timeline.get_tracks()
vtrack.update_restriction_caps(Gst.Caps("video/x-raw,framerate=60/1"))
self.assertEqual(self.timeline.get_frame_time(60), Gst.SECOND)
layer = self.timeline.append_layer()
asset = GES.Asset.request(GES.TestClip, "framerate=120/1,height=500,width=500,max-duration=f120")
clip = layer.add_asset( asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN)
self.assertEqual(clip.get_id(), "GESTestClip, framerate=(fraction)120/1, height=(int)500, width=(int)500, max-duration=(string)f120;")
test_source, = clip.get_children(True)
self.assertEqual(test_source.get_natural_size(), (True, 500, 500))
self.assertEqual(test_source.get_natural_framerate(), (True, 120, 1))
self.assertEqual(test_source.props.max_duration, Gst.SECOND)
self.assertEqual(clip.get_natural_framerate(), (True, 120, 1))
self.assertEqual(self.timeline.get_frame_at(Gst.SECOND), 60)
self.assertEqual(clip.props.max_duration, Gst.SECOND)
class TestEditing(common.GESSimpleTimelineTest):