command-line-formatter: Add a way to format timelines using the format
authorThibault Saunier <tsaunier@igalia.com>
Fri, 15 Jan 2021 18:21:06 +0000 (15:21 -0300)
committerThibault Saunier <tsaunier@igalia.com>
Wed, 10 Feb 2021 19:14:47 +0000 (16:14 -0300)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>

ges/ges-command-line-formatter.c
ges/ges-command-line-formatter.h
ges/ges-internal.h
ges/ges-xml-formatter.c
plugins/ges/gessrc.c
tools/utils.c

index 777ec9e..d834e39 100644 (file)
@@ -509,7 +509,6 @@ _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
   gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
       NULL);
-  GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
 
   return _ges_add_clip_from_struct (timeline, structure, error);
 }
@@ -809,3 +808,350 @@ ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
   formatter_klass->load_from_uri = _load;
   formatter_klass->rank = GST_RANK_MARGINAL;
 }
+
+/* Copy of GST_ASCII_IS_STRING */
+#define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \
+    ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \
+    ((c) == '.'))
+
+static void
+_sanitize_argument (const gchar * arg, GString * res)
+{
+  gboolean need_wrap = FALSE;
+  const gchar *tmp_string;
+
+  for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) {
+    if (!ASCII_IS_STRING (*tmp_string) || (*tmp_string == '\n')) {
+      need_wrap = TRUE;
+      break;
+    }
+  }
+
+  if (!need_wrap) {
+    g_string_append (res, arg);
+    return;
+  }
+
+  g_string_append_c (res, '"');
+  while (*arg != '\0') {
+    if (*arg == '"' || *arg == '\\') {
+      g_string_append_c (res, '\\');
+    } else if (*arg == '\n') {
+      g_string_append (res, "\\n");
+      arg++;
+      continue;
+    }
+
+    g_string_append_c (res, *(arg++));
+  }
+  g_string_append_c (res, '"');
+}
+
+static gboolean
+_serialize_control_binding (GESTrackElement * e, const gchar * prop,
+    GString * res)
+{
+  GstInterpolationMode mode;
+  GstControlSource *source = NULL;
+  GList *timed_values, *tmp;
+  gboolean absolute = FALSE;
+  GstControlBinding *binding = ges_track_element_get_control_binding (e, prop);
+
+  if (!binding)
+    return FALSE;
+
+  if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) {
+    g_warning ("Unsupported control binding type: %s",
+        G_OBJECT_TYPE_NAME (binding));
+    goto done;
+  }
+
+  g_object_get (binding, "control-source", &source,
+      "absolute", &absolute, NULL);
+
+  if (!GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
+    g_warning ("Unsupported control source type: %s",
+        G_OBJECT_TYPE_NAME (source));
+    goto done;
+  }
+
+  g_object_get (source, "mode", &mode, NULL);
+  g_string_append_printf (res, " +keyframes %s t=%s",
+      prop, absolute ? "direct-absolute" : "direct");
+
+  if (mode != GST_INTERPOLATION_MODE_LINEAR)
+    g_string_append_printf (res, " mode=%s",
+        g_enum_get_value (g_type_class_peek (GST_TYPE_INTERPOLATION_MODE),
+            mode)->value_nick);
+
+  timed_values =
+      gst_timed_value_control_source_get_all
+      (GST_TIMED_VALUE_CONTROL_SOURCE (source));
+  for (tmp = timed_values; tmp; tmp = tmp->next) {
+    gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
+    GstTimedValue *value;
+
+    value = (GstTimedValue *) tmp->data;
+    g_string_append_printf (res, " %f=%s",
+        (gdouble) value->timestamp / (gdouble) GST_SECOND,
+        g_ascii_dtostr (strbuf, G_ASCII_DTOSTR_BUF_SIZE, value->value));
+  }
+  g_list_free (timed_values);
+
+done:
+  g_clear_object (&source);
+  return TRUE;
+}
+
+static void
+_serialize_object_properties (GObject * object, GESCommandLineOption * option,
+    gboolean children_props, GString * res)
+{
+  guint n_props, j;
+  GParamSpec *spec, **pspecs;
+  GObjectClass *class = G_OBJECT_GET_CLASS (object);
+  const gchar *ignored_props[] = {
+    "max-duration", "supported-formats", "priority", "video-direction",
+    "is-image", NULL,
+  };
+
+  if (!children_props)
+    pspecs = g_object_class_list_properties (class, &n_props);
+  else {
+    pspecs =
+        ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
+        (object), &n_props);
+    g_assert (GES_IS_TRACK_ELEMENT (object));
+  }
+
+  for (j = 0; j < n_props; j++) {
+    const gchar *name;
+    gchar *value_str = NULL;
+    GValue val = { 0 };
+    gint i;
+
+    spec = pspecs[j];
+    if (!ges_util_can_serialize_spec (spec))
+      continue;
+
+    g_value_init (&val, spec->value_type);
+    if (!children_props)
+      g_object_get_property (object, spec->name, &val);
+    else
+      ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT
+          (object), spec, &val);
+
+    if (gst_value_compare (g_param_spec_get_default_value (spec),
+            &val) == GST_VALUE_EQUAL) {
+      GST_INFO ("Ignoring %s as it is using the default value", spec->name);
+      goto next;
+    }
+
+    name = spec->name;
+    if (!children_props && !g_strcmp0 (name, "in-point"))
+      name = "inpoint";
+
+    for (i = 0; option->properties[i].long_name; i++) {
+      if (!g_strcmp0 (spec->name, option->properties[i].long_name)) {
+        if (children_props) {
+          name = NULL;
+        } else {
+          name = option->properties[i].short_name;
+          if (option->properties[i].type == GST_TYPE_CLOCK_TIME)
+            value_str =
+                g_strdup_printf ("%f",
+                (gdouble) (g_value_get_uint64 (&val) / GST_SECOND));
+        }
+        break;
+      } else if (!g_strcmp0 (spec->name, option->properties[0].long_name)) {
+        name = NULL;
+        break;
+      }
+    }
+
+    for (i = 0; i < G_N_ELEMENTS (ignored_props); i++) {
+      if (!g_strcmp0 (spec->name, ignored_props[i])) {
+        name = NULL;
+        break;
+      }
+    }
+
+    if (!name) {
+      g_free (value_str);
+      continue;
+    }
+
+    if (GES_IS_TRACK_ELEMENT (object) &&
+        _serialize_control_binding (GES_TRACK_ELEMENT (object), name, res)) {
+      g_free (value_str);
+      continue;
+    }
+
+    if (!value_str)
+      value_str = gst_value_serialize (&val);
+
+    g_string_append_printf (res, " %s%s%s",
+        children_props ? "set-" : "", name, children_props ? " " : "=");
+    _sanitize_argument (value_str, res);
+    g_free (value_str);
+
+  next:
+    g_value_unset (&val);
+  }
+  g_free (pspecs);
+}
+
+static void
+_serialize_clip_track_types (GESClip * clip, GESTrackType tt, GString * res)
+{
+  GValue v = G_VALUE_INIT;
+  gchar *ttype_str;
+
+  if (ges_clip_get_supported_formats (clip) == tt)
+    return;
+
+  g_value_init (&v, GES_TYPE_TRACK_TYPE);
+  g_value_set_flags (&v, ges_clip_get_supported_formats (clip));
+
+  ttype_str = gst_value_serialize (&v);
+
+  g_string_append_printf (res, " tt=%s", ttype_str);
+  g_value_reset (&v);
+  g_free (ttype_str);
+}
+
+static void
+_serialize_clip_effects (GESClip * clip, GString * res)
+{
+  GList *tmpeffect, *effects;
+
+  effects = ges_clip_get_top_effects (clip);
+  for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
+    gchar *bin_desc;
+
+    g_object_get (tmpeffect->data, "bin-description", &bin_desc, NULL);
+
+    g_string_append_printf (res, " +effect %s", bin_desc);
+    g_free (bin_desc);
+  }
+  g_list_free_full (effects, gst_object_unref);
+
+}
+
+/**
+ * ges_command_line_formatter_get_timeline_uri:
+ * @timeline: A GESTimeline to serialize
+ *
+ * Since: 1.20
+ */
+gchar *
+ges_command_line_formatter_get_timeline_uri (GESTimeline * timeline)
+{
+  gchar *tmpstr;
+  GList *tmp;
+  gint i;
+  GString *res = g_string_new ("ges:");
+  GESTrackType tt = 0;
+
+  if (!timeline)
+    goto done;
+
+  for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
+    GstCaps *caps, *default_caps;
+    GESTrack *tmptrack, *track = tmp->data;
+
+    if (GES_IS_VIDEO_TRACK (track))
+      tmptrack = GES_TRACK (ges_video_track_new ());
+    else if (GES_IS_AUDIO_TRACK (track))
+      tmptrack = GES_TRACK (ges_audio_track_new ());
+    else {
+      g_warning ("Unhandled track type: %s", G_OBJECT_TYPE_NAME (track));
+      continue;
+    }
+
+    tt |= track->type;
+
+    g_string_append_printf (res, " +track %s",
+        (track->type == GES_TRACK_TYPE_VIDEO) ? "video" : "audio");
+
+    default_caps = ges_track_get_restriction_caps (tmptrack);
+    caps = ges_track_get_restriction_caps (track);
+    if (!gst_caps_is_equal (caps, default_caps)) {
+      tmpstr = gst_caps_serialize (caps, 0);
+
+      g_string_append (res, " restrictions=");
+      _sanitize_argument (tmpstr, res);
+      g_free (tmpstr);
+    }
+    gst_caps_unref (default_caps);
+    gst_caps_unref (caps);
+    gst_object_unref (tmptrack);
+  }
+
+  for (tmp = timeline->layers, i = 0; tmp; tmp = tmp->next, i++) {
+    GList *tmpclip, *clips = ges_layer_get_clips (tmp->data);
+    GList *tmptrackelem;
+
+    for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
+      GESClip *clip = tmpclip->data;
+      GESCommandLineOption *option = NULL;
+
+      if (GES_IS_TEST_CLIP (clip)) {
+        GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
+        const gchar *id = ges_asset_get_id (asset);
+
+        g_string_append (res, " +test-clip ");
+
+        _sanitize_argument (g_enum_get_value (g_type_class_peek
+                (GES_VIDEO_TEST_PATTERN_TYPE),
+                ges_test_clip_get_vpattern (GES_TEST_CLIP (clip)))->value_nick,
+            res);
+
+        if (g_strcmp0 (id, "GESTestClip")) {
+          g_string_append (res, " asset-id=");
+          _sanitize_argument (id, res);
+        }
+
+        option = &options[TEST_CLIP];
+      } else if (GES_IS_TITLE_CLIP (clip)) {
+        g_string_append (res, " +title ");
+        _sanitize_argument (ges_title_clip_get_text (GES_TITLE_CLIP (clip)),
+            res);
+        option = &options[TITLE];
+      } else if (GES_IS_URI_CLIP (clip)) {
+        g_string_append (res, " +clip ");
+
+        _sanitize_argument (ges_uri_clip_get_uri (GES_URI_CLIP (clip)), res);
+        option = &options[CLIP];
+      } else {
+        g_warning ("Unhandled clip type: %s", G_OBJECT_TYPE_NAME (clip));
+        continue;
+      }
+
+      _serialize_clip_track_types (clip, tt, res);
+
+      if (i)
+        g_string_append_printf (res, " layer=%d", i);
+
+      _serialize_object_properties (G_OBJECT (clip), option, FALSE, res);
+      _serialize_clip_effects (clip, res);
+
+      for (tmptrackelem = GES_CONTAINER_CHILDREN (clip); tmptrackelem;
+          tmptrackelem = tmptrackelem->next)
+        _serialize_object_properties (G_OBJECT (tmptrackelem->data), option,
+            TRUE, res);
+    }
+    g_list_free_full (clips, gst_object_unref);
+  }
+
+done:
+  return g_string_free (res, FALSE);
+  {
+    GstUri *uri = gst_uri_from_string (res->str);
+    gchar *uri_str = gst_uri_to_string (uri);
+
+    g_string_free (res, TRUE);
+
+    return uri_str;
+  }
+}
index a1782ca..eaee523 100644 (file)
@@ -46,4 +46,7 @@ struct _GESCommandLineFormatter
 GES_API
 gchar * ges_command_line_formatter_get_help (gint nargs, gchar ** commands);
 
+GES_API
+gchar * ges_command_line_formatter_get_timeline_uri (GESTimeline *timeline);
+
 G_END_DECLS
index 692affc..3200892 100644 (file)
@@ -106,6 +106,11 @@ GstDebugCategory * _ges_debug (void);
       ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \
       GES_TIMELINE_ELEMENT_SET_SIMPLE)
 
+/************************
+ * Our property masks   *
+ ************************/
+#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1))
+
 #define SUPRESS_UNUSED_WARNING(a) (void)a
 
 G_GNUC_INTERNAL void
@@ -416,6 +421,8 @@ G_GNUC_INTERNAL gboolean
 ges_util_structure_get_clocktime (GstStructure *structure, const gchar *name,
                                   GstClockTime *val, GESFrameNumber *frames);
 
+G_GNUC_INTERNAL gboolean /* From ges-xml-formatter.c */
+ges_util_can_serialize_spec (GParamSpec * spec);
 
 /****************************************************
  *              GESContainer                        *
@@ -578,11 +585,6 @@ G_GNUC_INTERNAL gboolean ges_test_clip_asset_get_natural_size (GESAsset *self,
 G_GNUC_INTERNAL gchar *ges_test_source_asset_check_id         (GType type, const gchar *id,
                                                                GError **error);
 
-/************************
- * Our property masks   *
- ************************/
-#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1))
-
 /*******************************
  * GESMarkerList serialization *
  *******************************/
index e68da36..cfbddcc 100644 (file)
@@ -1063,8 +1063,8 @@ append_escaped (GString * str, gchar * tmpstr, guint depth)
   g_free (tmpstr);
 }
 
-static inline gboolean
-_can_serialize_spec (GParamSpec * spec)
+gboolean
+ges_util_can_serialize_spec (GParamSpec * spec)
 {
   if (!(spec->flags & G_PARAM_WRITABLE)) {
     GST_LOG ("%s from %s is not writable",
@@ -1127,7 +1127,7 @@ _serialize_properties (GObject * object, gint * ret_n_props,
     GValue val = { 0 };
 
     spec = pspecs[j];
-    if (!_can_serialize_spec (spec))
+    if (!ges_util_can_serialize_spec (spec))
       continue;
 
     _init_value_from_spec_for_serialization (&val, spec);
@@ -1434,7 +1434,7 @@ _save_children_properties (GString * str, GESTimelineElement * element,
     GValue val = { 0 };
     spec = pspecs[i];
 
-    if (_can_serialize_spec (spec)) {
+    if (ges_util_can_serialize_spec (spec)) {
       gchar *spec_name =
           g_strdup_printf ("%s::%s", g_type_name (spec->owner_type),
           spec->name);
index 103efe9..3e5cae8 100644 (file)
@@ -82,8 +82,7 @@ ges_src_uri_get_uri (GstURIHandler * handler)
   GESSrc *self = GES_SRC (handler);
   GESTimeline *timeline = ges_base_bin_get_timeline (GES_BASE_BIN (self));
 
-  return timeline ? g_strdup_printf ("ges://%s",
-      GST_OBJECT_NAME (timeline)) : NULL;
+  return ges_command_line_formatter_get_timeline_uri (timeline);
 }
 
 static gboolean
index 2815ad2..f9d08ec 100644 (file)
@@ -436,12 +436,15 @@ describe_discoverer (GstDiscovererInfo * info)
 void
 print_timeline (GESTimeline * timeline)
 {
+  gchar *uri;
   GList *layer, *clip, *clips;
 
   if (!timeline->layers)
     return;
 
-  g_print ("\nTimeline description:\n");
+  uri = ges_command_line_formatter_get_timeline_uri (timeline);
+  g_print ("\nTimeline description: `%s`\n", &uri[5]);
+  g_free (uri);
   g_print ("====================\n\n");
   for (layer = timeline->layers; layer; layer = layer->next) {
     clips = ges_layer_get_clips (layer->data);