tools: Implement a new CLI interface for the timeline creation
authorThibault Saunier <tsaunier@gnome.org>
Thu, 19 Feb 2015 17:28:41 +0000 (18:28 +0100)
committerThibault Saunier <tsaunier@gnome.org>
Fri, 20 Mar 2015 12:53:40 +0000 (13:53 +0100)
tools/ges-launch.c
tools/ges-structure-parser.c

index 7b7ffd2..f82dde9 100644 (file)
@@ -27,6 +27,7 @@
 #include <glib.h>
 #include <glib/gprintf.h>
 #include <ges/ges.h>
+#include "../ges/ges-structured-interface.h"
 #include <gst/pbutils/encoding-profile.h>
 
 #include <locale.h>             /* for LC_ALL */
@@ -177,50 +178,197 @@ project_loaded_cb (GESProject * project, GESTimeline * timeline)
   }
 }
 
+static gint                     /*  -1: not present, 0: failure, 1: OK */
+_convert_to_clocktime (GstStructure * structure, const gchar * name,
+    GstClockTime default_value)
+{
+  gint res = 1;
+  gdouble val;
+  GValue d_val = { 0 };
+  GstClockTime timestamp;
+  const GValue *gvalue = gst_structure_get_value (structure, name);
+
+  if (gvalue == NULL) {
+    timestamp = default_value;
+
+    res = -1;
+
+    goto done;
+  }
+
+  if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
+    return 1;
+
+  g_value_init (&d_val, G_TYPE_DOUBLE);
+  if (!g_value_transform (gvalue, &d_val)) {
+    GST_ERROR ("Could not get timestamp for %s", name);
+
+    return 0;
+  }
+  val = g_value_get_double ((const GValue *) &d_val);
+
+  if (val == -1.0)
+    timestamp = GST_CLOCK_TIME_NONE;
+  else
+    timestamp = val * GST_SECOND;
+
+done:
+  gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
+
+  return res;
+}
+
+typedef struct
+{
+  const gchar *long_name;
+  const gchar *short_name;
+  GType type;
+  const gchar *new_name;
+} Properties;
+
 static gboolean
-check_time (char *time)
+_cleanup_fields (const Properties * filed_names, GstStructure * structure,
+    GError ** error)
 {
-  static GRegex *re = NULL;
+  guint i;
 
-  if (!re) {
-    if (NULL == (re = g_regex_new ("^[0-9]+(.[0-9]+)?$", G_REGEX_EXTENDED, 0,
-                NULL)))
-      return FALSE;
+  for (i = 0; filed_names[i].long_name; i++) {
+    gboolean exists = FALSE;
+
+    /* Move shortly named fields to longname variante */
+    if (gst_structure_has_field (structure, filed_names[i].short_name)) {
+      exists = TRUE;
+
+      if (gst_structure_has_field (structure, filed_names[i].long_name)) {
+        *error = g_error_new (GES_ERROR, 0, "Using short and long name"
+            " at the same time for property: %s, which one should I use?!",
+            filed_names[i].long_name);
+
+        return FALSE;
+      } else {
+        const GValue *val =
+            gst_structure_get_value (structure, filed_names[i].short_name);
+
+        gst_structure_set_value (structure, filed_names[i].long_name, val);
+        gst_structure_remove_field (structure, filed_names[i].short_name);
+      }
+    } else if (gst_structure_has_field (structure, filed_names[i].long_name)) {
+      exists = TRUE;
+    }
+
+    if (exists) {
+      if (filed_names[i].type == GST_TYPE_CLOCK_TIME) {
+        if (_convert_to_clocktime (structure, filed_names[i].long_name, 0) == 0) {
+          *error = g_error_new (GES_ERROR, 0, "Could not convert"
+              " %s to GstClockTime", filed_names[i].long_name);
+
+          return FALSE;
+        }
+      }
+    }
+
+    if (filed_names[i].new_name
+        && gst_structure_has_field (structure, filed_names[i].long_name)) {
+      const GValue *val =
+          gst_structure_get_value (structure, filed_names[i].long_name);
+
+      gst_structure_set_value (structure, filed_names[i].new_name, val);
+      gst_structure_remove_field (structure, filed_names[i].long_name);
+    }
   }
 
-  if (g_regex_match (re, time, 0, NULL))
-    return TRUE;
-  return FALSE;
+  return TRUE;
 }
 
-static guint64
-str_to_time (char *time)
+static gboolean
+_add_clip (GESTimeline * timeline, GstStructure * structure, GError ** error)
 {
-  gdouble nsecs;
+  const Properties filed_names[] = {
+    {"uri", "n", 0, "asset-id"},
+    {"name", "n", 0, NULL},
+    {"start", "s", GST_TYPE_CLOCK_TIME, NULL},
+    {"duration", "d", GST_TYPE_CLOCK_TIME, NULL},
+    {"inpoint", "i", GST_TYPE_CLOCK_TIME, NULL},
+    {"track-types", "tt", 0, NULL},
+    {NULL},
+  };
+
+  if (!_cleanup_fields (filed_names, structure, error))
+    return FALSE;
 
-  g_return_val_if_fail (check_time (time), 0);
+  gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
 
-  nsecs = g_ascii_strtod (time, NULL);
+  GST_ERROR ("Adding a clip %" GST_PTR_FORMAT, structure);
 
-  return nsecs * GST_SECOND;
+  return _ges_add_add_clip_from_struct (timeline, structure, error);
 }
 
-static void
-_clip_added_cb (GESLayer * layer, GESClip * clip, GESAsset * asset)
+static gboolean
+_add_effect (GESTimeline * timeline, GstStructure * structure, GError ** error)
+{
+  const Properties filed_names[] = {
+    {"element-name", "e", 0, NULL},
+    {"bin-description", "d", 0, "asset-id"},
+    {"name", "n", 0, "child-name"},
+    {NULL, NULL, 0, NULL},
+  };
+
+  if (!_cleanup_fields (filed_names, structure, error))
+    return FALSE;
+
+  gst_structure_set (structure, "child-type", G_TYPE_STRING, "GESEffect", NULL);
+
+  GST_ERROR ("Adding a clip %" GST_PTR_FORMAT, structure);
+
+  return _ges_container_add_child_from_struct (timeline, structure, error);
+}
+
+static gboolean
+_set_child_property (GESTimeline * timeline, GstStructure * structure,
+    GError ** error)
 {
-  if (GES_IS_TRANSITION_CLIP (clip))
-    ges_extractable_set_asset (GES_EXTRACTABLE (clip), asset);
+  return _ges_set_child_property_from_struct (timeline, structure, error);
 }
 
+static GOptionEntry timeline_parsing_options[] = {
+  {"clip", 'c', 0.0, G_OPTION_ARG_CALLBACK, &_add_clip,
+        "Adds a clip in the timeline",
+      " start - s: The start position of the element inside the layer.\n"
+        " duration - d: The duration of the clip.\n"
+        " inpoint - i: The inpoint of the clip\n."
+        " track-types - tt: The type of the tracks where the clip should be used:\n"
+        " Examples:\n"
+        "  * audio  / a\n"
+        "  * video / v\n"
+        "  * audio+video / a+v\n"
+        " Will default to all the media types in the clip that match the global track-types"},
+  {"effect", 'e', 0.0, G_OPTION_ARG_CALLBACK, &_add_effect,
+        "Adds an effect as decribed by 'bin-description'",
+        " bin-description - d: The description of the effect bin with a gst-launch-style pipeline description."
+        " element-name - d: The name of the element to apply the effect on."
+        /* TODO: Implement that:
+         * " start - s: The start position of the element inside the layer -- implies creation of effect *Clip*.\n"
+         * " duration - d: The duration of the clip -- implies creation of effect *Clip*.\n"
+         * " inpoint - i: The inpoint of the clip-- implies creation of effect *Clip*.\n" */
+      },
+};
+
+#define EXEC(func,structure,error) G_STMT_START { \
+  gboolean res = ((ActionFromStructureFunc)func)(timeline, structure, error); \
+  if (!res) {\
+    GST_ERROR ("Could not execute: %" GST_PTR_FORMAT ", error: %s", structure, (*error)->message); \
+    goto build_failure; \
+  } \
+} G_STMT_END
+
 static GESTimeline *
-create_timeline (int nbargs, gchar ** argv, const gchar * proj_uri,
+create_timeline (GList * structures, const gchar * proj_uri,
     const gchar * scenario)
 {
-  GESLayer *layer = NULL;
-  GESTrack *tracka = NULL, *trackv = NULL;
+  guint i;
+  GList *tmp;
   GESTimeline *timeline;
-  guint i, clip_added_sigid = 0;
-  GstClockTime next_trans_dur = 0;
+  GESTrack *tracka = NULL, *trackv = NULL;
   GESProject *project = ges_project_new (proj_uri);
 
   g_signal_connect (project, "error-loading-asset",
@@ -258,114 +406,24 @@ create_timeline (int nbargs, gchar ** argv, const gchar * proj_uri,
 
   /* Here we've finished initializing our timeline, we're
    * ready to start using it... by solely working with the layer !*/
+  for (tmp = structures; tmp; tmp = tmp->next) {
+    const gchar *name = gst_structure_get_name (tmp->data);
+    GError *error = NULL;
 
-  for (i = 0; i < nbargs / 3; i++) {
-    GESClip *clip;
-
-    char *source = argv[i * 3];
-    char *arg0 = argv[(i * 3) + 1];
-    guint64 duration = str_to_time (argv[(i * 3) + 2]);
-
-    if (i == 0) {
-      /* We are only going to be doing one layer of clips */
-      layer = (GESLayer *) ges_layer_new ();
-
-      /* Add the tracks and the layer to the timeline */
-      if (!ges_timeline_add_layer (timeline, layer))
-        goto build_failure;
-    }
-
-    if (duration == 0)
-      duration = GST_CLOCK_TIME_NONE;
-
-    if (!g_strcmp0 ("+pattern", source)) {
-      clip = GES_CLIP (ges_test_clip_new_for_nick (arg0));
-      if (!clip) {
-        g_error ("%s is an invalid pattern name!\n", arg0);
-        goto build_failure;
-      }
-
-      g_object_set (G_OBJECT (clip), "duration", duration, NULL);
-
-      g_printf ("Adding <pattern:%s> duration %" GST_TIME_FORMAT "\n", arg0,
-          GST_TIME_ARGS (duration));
-    }
-
-    else if (!g_strcmp0 ("+transition", source)) {
-      GESAsset *asset =
-          ges_asset_request (GES_TYPE_TRANSITION_CLIP, arg0, NULL);
-
-      if (asset == NULL) {
-        g_warning ("Can not create transition %s", arg0);
-      }
-
-      next_trans_dur = duration;
-      clip_added_sigid = g_signal_connect (layer, "clip-added",
-          (GCallback) _clip_added_cb, asset);
-
+    if (g_str_has_prefix (name, "set-")) {
+      EXEC (_set_child_property, tmp->data, &error);
       continue;
-    } else if (!g_strcmp0 ("+title", source)) {
-      clip = GES_CLIP (ges_title_clip_new ());
-
-      g_object_set (clip, "duration", duration, "text", arg0, NULL);
-
-      g_printf ("Adding <title:%s> duration %" GST_TIME_FORMAT "\n", arg0,
-          GST_TIME_ARGS (duration));
     }
 
-    else {
-      gchar *uri;
-      GESAsset *asset;
-      guint64 inpoint;
-
-      GError *error = NULL;
-
-      if (!(uri = ensure_uri (source))) {
-        GST_ERROR ("couldn't create uri for '%s'", source);
-        goto build_failure;
+    for (i = 0; i < G_N_ELEMENTS (timeline_parsing_options); i++) {
+      if (gst_structure_has_name (tmp->data,
+              timeline_parsing_options[i].long_name)
+          || (strlen (name) == 1 &&
+              *name == timeline_parsing_options[i].short_name)) {
+        EXEC (((ActionFromStructureFunc) timeline_parsing_options[i].arg_data),
+            tmp->data, &error);
       }
-
-      inpoint = str_to_time (argv[i * 3 + 1]);
-      asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, &error));
-      if (error) {
-        g_printerr ("Can not create asset for %s", uri);
-
-        return NULL;
-      }
-
-      ges_project_add_asset (project, asset);
-      clip = GES_CLIP (ges_asset_extract (asset, &error));
-      if (error) {
-        g_printerr ("Can not extract asset for %s", uri);
-
-        return NULL;
-      }
-
-      if (!GST_CLOCK_TIME_IS_VALID (duration))
-        duration =
-            GES_TIMELINE_ELEMENT_DURATION (clip) - (GstClockTime) inpoint;
-
-      g_object_set (clip,
-          "in-point", (guint64) inpoint, "duration", (guint64) duration, NULL);
-
-      g_printf ("Adding clip %s inpoint:%" GST_TIME_FORMAT " duration:%"
-          GST_TIME_FORMAT "\n", uri, GST_TIME_ARGS (inpoint),
-          GST_TIME_ARGS (duration));
-
-      g_free (uri);
     }
-
-    g_object_set (G_OBJECT (clip), "start",
-        ges_layer_get_duration (layer) - next_trans_dur, NULL);
-
-    ges_layer_add_clip (layer, clip);
-
-    if (clip_added_sigid) {
-      g_signal_handler_disconnect (layer, clip_added_sigid);
-      clip_added_sigid = 0;
-      next_trans_dur = 0;
-    }
-
   }
 
 done:
@@ -396,7 +454,7 @@ _save_timeline (GESTimeline * timeline, const gchar * load_path)
 
 static GESPipeline *
 create_pipeline (GESTimeline ** ret_timeline, gchar * load_path,
-    int argc, char **argv, const gchar * scenario)
+    GList * structures, const gchar * scenario)
 {
   gchar *uri = NULL;
   GESTimeline *timeline = NULL;
@@ -413,7 +471,7 @@ create_pipeline (GESTimeline ** ret_timeline, gchar * load_path,
 
   pipeline = ges_pipeline_new ();
 
-  if (!(timeline = create_timeline (argc, argv, uri, scenario)))
+  if (!(timeline = create_timeline (structures, uri, scenario)))
     goto failure;
 
   if (!load_path)
@@ -897,15 +955,6 @@ main (int argc, gchar ** argv)
     exit (1);
   }
 
-  parser = _parse_timeline (argc, argv);
-
-  {
-    GList *tmp;
-    for (tmp = parser->structures; tmp; tmp = tmp->next) {
-      /* Do stuff here */
-    }
-  }
-
   if (list_transitions) {
     print_transition_list ();
     exit (0);
@@ -920,7 +969,7 @@ main (int argc, gchar ** argv)
     return ges_validate_print_action_types ((const gchar **) argv + 1,
         argc - 1);
 
-  if (((!load_path && !scenario && (argc < 4)))) {
+  if (((!load_path && !scenario && (argc < 1)))) {
     g_printf ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     g_option_context_free (ctx);
     exit (1);
@@ -929,7 +978,8 @@ main (int argc, gchar ** argv)
   g_option_context_free (ctx);
 
   /* Create the pipeline */
-  create_pipeline (&timeline, load_path, argc - 1, argv + 1, scenario);
+  parser = _parse_timeline (argc, argv);
+  create_pipeline (&timeline, load_path, parser->structures, scenario);
   if (!pipeline)
     exit (1);
 
index aa33bfe..dc4eb3c 100644 (file)
@@ -76,6 +76,13 @@ _finish_structure (GESStructureParser * self)
   if (self->current_string) {
     GstStructure *structure =
         gst_structure_new_from_string (self->current_string);
+
+    if (structure == NULL) {
+      GST_ERROR ("Error creating structure from %s", self->current_string);
+
+      return;
+    }
+
     self->structures = g_list_append (self->structures, structure);
     g_free (self->current_string);
     self->current_string = NULL;