ges: support test clips assets natural size/framerate
authorThibault Saunier <tsaunier@igalia.com>
Fri, 28 Feb 2020 14:47:25 +0000 (11:47 -0300)
committerThibault Saunier <tsaunier@igalia.com>
Wed, 25 Mar 2020 14:26:29 +0000 (11:26 -0300)
This way we can test this kind of behaviour without requiring
real sources.

Also add simple tests.

ges/ges-command-line-formatter.c
ges/ges-extractable.c
ges/ges-extractable.h
ges/ges-internal.h
ges/ges-structure-parser.c
ges/ges-test-clip.c
ges/ges-video-test-source.c
tests/check/python/test_timeline.py

index 3a1a6793f8a103bc53be4dee3294f6cd0a12a569..0fd75e422c391b980a8ca04497473e24fde268ff 100644 (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);
 }
index ad76ee351bb117e3baf25c91053f6475b1bb4fd6..6ce50dd7a871ef0251ca74317fc9c5981f17981a 100644 (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)));
 
 }
index 54ec4bfecd3e256bff7944e242d4685cdc2246f4..77ad495fe9d6c765bf48a001c0ec47c03b5600b3 100644 (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
index 6fe876f33c05ccb0a578814bf525288760046156..c711a97034f9f8ff2157910fa9e9811469d8806b 100644 (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   *
  ************************/
index f2ef280628e1d87accd4837ab29d3740f98ce3f9..aa6752f967bd1ee8ff54b52494be3ed02cdb44af 100644 (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;
 }
index 77bd5f346fdc574a76106f51c6159ef313c91a18..36257ed8540c2b971d93815f581baffdd36be9e4 100644 (file)
  *
  * 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"
 #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;
 }
 
index f548308be4dbce45c7c5bb9bb30d12d733dc34b1..aade73da51f25a6316b24e3b93355c67fcd519d4 100644 (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);
index 4b3c9c91dd5c8eae72e8643b4e0967d1bc107df2..818f0b052b2e282cbdf14f67c8d5a75ba7b16333 100644 (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):