ges:validate: Add a way to execute actions on serialized timelines
authorThibault Saunier <tsaunier@igalia.com>
Wed, 10 Jul 2019 14:15:31 +0000 (10:15 -0400)
committerThibault Saunier <tsaunier@igalia.com>
Fri, 26 Jul 2019 17:48:51 +0000 (13:48 -0400)
This way we can modify nested timelines.

ges/ges-structured-interface.c
ges/ges-structured-interface.h
ges/ges-validate.c

index fea9f36..45502c6 100644 (file)
@@ -160,6 +160,21 @@ _check_fields (GstStructure * structure, FieldsError fields_error,
   return TRUE;
 }
 
+gboolean
+_ges_save_timeline_if_needed (GESTimeline * timeline, GstStructure * structure,
+    GError ** error)
+{
+  gboolean res = TRUE;
+  const gchar *nested_timeline_id =
+      gst_structure_get_string (structure, "project-uri");
+
+  if (nested_timeline_id) {
+    res = ges_timeline_save_to_uri (timeline, nested_timeline_id, NULL, TRUE,
+        error);
+  }
+
+  return res;
+}
 
 gboolean
 _ges_add_remove_keyframe_from_struct (GESTimeline * timeline,
@@ -176,7 +191,7 @@ _ges_add_remove_keyframe_from_struct (GESTimeline * timeline,
   gboolean ret = FALSE;
 
   const gchar *valid_fields[] =
-      { "element-name", "property-name", "value", "timestamp",
+      { "element-name", "property-name", "value", "timestamp", "project-uri",
     NULL
   };
 
@@ -240,6 +255,7 @@ _ges_add_remove_keyframe_from_struct (GESTimeline * timeline,
           GST_TIME_ARGS (timestamp));
     }
   }
+  ret = _ges_save_timeline_if_needed (timeline, structure, error);
 
 done:
   if (source)
@@ -353,6 +369,7 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
   const gchar *text;
   const gchar *pattern;
   const gchar *track_types_str;
+  const gchar *nested_timeline_id;
   gchar *asset_id = NULL;
   gchar *check_asset_id = NULL;
   const gchar *type_string;
@@ -365,7 +382,8 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
 
   const gchar *valid_fields[] =
       { "asset-id", "pattern", "name", "layer-priority", "layer", "type",
-    "start", "inpoint", "duration", "text", "track-types", NULL
+    "start", "inpoint", "duration", "text", "track-types", "project-uri",
+    NULL
   };
 
   FieldsError fields_error = { valid_fields, NULL };
@@ -386,6 +404,7 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
   TRY_GET ("inpoint", GST_TYPE_CLOCK_TIME, &inpoint, 0);
   TRY_GET ("duration", GST_TYPE_CLOCK_TIME, &duration, GST_CLOCK_TIME_NONE);
   TRY_GET_STRING ("track-types", &track_types_str, NULL);
+  TRY_GET_STRING ("project-uri", &nested_timeline_id, NULL);
 
   if (track_types_str) {
     if (!get_flags_from_string (GES_TYPE_TRACK_TYPE, track_types_str,
@@ -483,6 +502,7 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
   }
 
   gst_object_unref (layer);
+  res = _ges_save_timeline_if_needed (timeline, structure, error);
 
 beach:
   g_free (asset_id);
@@ -501,7 +521,7 @@ _ges_container_add_child_from_struct (GESTimeline * timeline,
 
   gboolean res = TRUE;
   const gchar *valid_fields[] = { "container-name", "asset-id",
-    "child-type", "child-name", NULL
+    "child-type", "child-name", "project-uri", NULL
   };
 
   FieldsError fields_error = { valid_fields, NULL };
@@ -568,6 +588,7 @@ _ges_container_add_child_from_struct (GESTimeline * timeline,
   } else {
     g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, child);
   }
+  res = _ges_save_timeline_if_needed (timeline, structure, error);
 
 beach:
   return res;
@@ -581,7 +602,8 @@ _ges_set_child_property_from_struct (GESTimeline * timeline,
   GESTimelineElement *element;
   const gchar *property_name, *element_name;
 
-  const gchar *valid_fields[] = { "element-name", "property", "value", NULL };
+  const gchar *valid_fields[] =
+      { "element-name", "property", "value", "project-uri", NULL };
 
   FieldsError fields_error = { valid_fields, NULL };
 
@@ -644,8 +666,7 @@ _ges_set_child_property_from_struct (GESTimeline * timeline,
 
   ges_timeline_element_set_child_property (element, property_name,
       (GValue *) value);
-
-  return TRUE;
+  return _ges_save_timeline_if_needed (timeline, structure, error);
 }
 
 #undef GET_AND_CHECK
index 6963bc2..2c5f212 100644 (file)
@@ -56,6 +56,8 @@ _ges_get_asset_from_timeline                  (GESTimeline * timeline,
 G_GNUC_INTERNAL GESLayer *
 _ges_get_layer_by_priority                    (GESTimeline * timeline,
                                                gint priority);
+G_GNUC_INTERNAL gboolean
+_ges_save_timeline_if_needed (GESTimeline* timeline, GstStructure* structure, GError** error);
 
 G_END_DECLS
 
index c34308c..f775448 100644 (file)
 #define MONITOR_ON_PIPELINE "validate-monitor"
 #define RUNNER_ON_PIPELINE "runner-monitor"
 
-#define DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action) \
-  GESTimeline *timeline; \
-  GstElement * pipeline = gst_validate_scenario_get_pipeline (scenario); \
-  if (pipeline == NULL) { \
-    GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,\
-            "Can't execute a '%s' action after the pipeline "\
-            "has been destroyed.", action->type);\
-    return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;\
-  }\
-  g_object_get (pipeline, "timeline", &timeline, NULL);
-
-#define DECLARE_AND_GET_TIMELINE(scenario, action) \
-  DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action);\
-  gst_object_unref(pipeline);
+typedef struct
+{
+  GMainLoop *ml;
+  GError *error;
+} LoadTimelineData;
+
+static void
+project_loaded_cb (GESProject * project, GESTimeline * timeline,
+    LoadTimelineData * data)
+{
+  g_main_loop_quit (data->ml);
+}
+
+static void
+error_loading_asset_cb (GESProject * project, GError * err,
+    const gchar * unused_id, GType extractable_type, LoadTimelineData * data)
+{
+  data->error = g_error_copy (err);
+  g_main_loop_quit (data->ml);
+}
+
+static GESTimeline *
+_ges_load_timeline (GstValidateScenario * scenario, const gchar * project_uri)
+{
+  GESProject *project = ges_project_new (project_uri);
+  GESTimeline *timeline;
+  LoadTimelineData data = { 0 };
+
+  data.ml = g_main_loop_new (NULL, TRUE);
+  timeline =
+      GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data.error));
+  if (!timeline)
+    goto done;
+
+  g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, &data);
+  g_signal_connect (project, "error-loading-asset",
+      (GCallback) error_loading_asset_cb, &data);
+  g_main_loop_run (data.ml);
+  g_signal_handlers_disconnect_by_func (project, project_loaded_cb, &data);
+  g_signal_handlers_disconnect_by_func (project, error_loading_asset_cb, &data);
+  GST_INFO_OBJECT (scenario, "Loaded timeline from %s", project_uri);
+
+done:
+  if (data.error) {
+    GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
+        "Can not load timeline from: %s (%s)", project_uri,
+        data.error->message);
+    g_clear_error (&data.error);
+    gst_clear_object (&timeline);
+  }
+
+  g_main_loop_unref (data.ml);
+  gst_object_unref (project);
+  return timeline;
+}
+
+#define DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action)                                   \
+    GESTimeline* timeline;                                                                        \
+    GstElement* pipeline = NULL;                                                                  \
+    const gchar* project_uri = gst_structure_get_string(action->structure, "project-uri"); \
+    if (!project_uri) {                                                                    \
+        pipeline = gst_validate_scenario_get_pipeline(scenario);                                  \
+        if (pipeline == NULL) {                                                                   \
+            GST_VALIDATE_REPORT(scenario, SCENARIO_ACTION_EXECUTION_ERROR,                        \
+                "Can't execute a '%s' action after the pipeline "                                 \
+                "has been destroyed.",                                                            \
+                action->type);                                                                    \
+            return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;                                    \
+        }                                                                                         \
+        g_object_get(pipeline, "timeline", &timeline, NULL);                                      \
+    } else {                                                                                      \
+        timeline = _ges_load_timeline(scenario, project_uri);                              \
+        if (!timeline)                                                                            \
+            return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;                                    \
+    }
+
+#define DECLARE_AND_GET_TIMELINE(scenario, action)         \
+  DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action); \
+  if (pipeline)                                            \
+      gst_object_unref(pipeline);                          \
+
+#define SAVE_TIMELINE_IF_NEEDED(scenario, timeline, action)                                                  \
+    {                                                                                                        \
+        if (!_ges_save_timeline_if_needed(timeline, action->structure, NULL)) {                              \
+            GST_VALIDATE_REPORT(scenario,                                                                    \
+                g_quark_from_string("scenario::execution-error"),                                            \
+                "Could not save timeline to %s", gst_structure_get_string(action->structure, "project-id")); \
+            gst_object_unref(timeline);                                                                      \
+            return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;                                               \
+        }                                                                                                    \
+    }
 
 static gboolean
 _serialize_project (GstValidateScenario * scenario, GstValidateAction * action)
@@ -104,7 +181,7 @@ _remove_asset (GstValidateScenario * scenario, GstValidateAction * action)
   }
 
   res = ges_project_remove_asset (project, asset);
-
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 beach:
   g_object_unref (timeline);
   return res;
@@ -148,6 +225,7 @@ _add_asset (GstValidateScenario * scenario, GstValidateAction * action)
   }
 
   res = ges_project_add_asset (project, asset);
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 
 beach:
   g_object_unref (timeline);
@@ -176,6 +254,7 @@ _add_layer (GstValidateScenario * scenario, GstValidateAction * action)
       NULL);
 
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 beach:
   g_object_unref (timeline);
   return res;
@@ -206,6 +285,7 @@ _remove_layer (GstValidateScenario * scenario, GstValidateAction * action)
     GST_ERROR ("No layer with priority %d", priority);
   }
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 beach:
   g_object_unref (timeline);
   return res;
@@ -234,6 +314,7 @@ _remove_clip (GstValidateScenario * scenario, GstValidateAction * action)
     GST_ERROR ("No layer for clip %s", ges_timeline_element_get_name (clip));
   }
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
   g_object_unref (timeline);
   return res;
 }
@@ -294,6 +375,7 @@ _edit_container (GstValidateScenario * scenario, GstValidateAction * action)
   }
   gst_object_unref (container);
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 beach:
   g_object_unref (timeline);
   return res;
@@ -335,6 +417,7 @@ _commit (GstValidateScenario * scenario, GstValidateAction * action)
   }
   gst_object_unref (bus);
   gst_object_unref (timeline);
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 
   return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
 }
@@ -399,6 +482,7 @@ _set_track_restriction_caps (GstValidateScenario * scenario,
     }
   }
   gst_caps_unref (caps);
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
   gst_object_unref (timeline);
 
   return res;
@@ -433,6 +517,7 @@ _set_asset_on_element (GstValidateScenario * scenario,
   }
 
   res = ges_extractable_set_asset (GES_EXTRACTABLE (element), asset);
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
 
 beach:
   gst_object_unref (timeline);
@@ -467,6 +552,7 @@ _container_remove_child (GstValidateScenario * scenario,
 
   res = ges_container_remove (container, child);
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
   gst_object_unref (timeline);
 
   return res;
@@ -496,6 +582,7 @@ _ungroup (GstValidateScenario * scenario, GstValidateAction * action)
 
   g_list_free (ges_container_ungroup (container, recursive));
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
   gst_object_unref (timeline);
 
   return res;
@@ -604,14 +691,15 @@ _set_control_source (GstValidateScenario * scenario, GstValidateAction * action)
   ret = ges_track_element_set_control_source (element,
       source, property_name, binding_type);
 
-
 done:
-  gst_object_unref (timeline);
   g_free (element_name);
   g_free (binding_type);
   g_free (source_type);
   g_free (interpolation_mode);
 
+  SAVE_TIMELINE_IF_NEEDED (scenario, timeline, action);
+  gst_object_unref (timeline);
+
   return ret;
 }
 
@@ -810,6 +898,12 @@ ges_validate_register_action_types (void)
           .types = "int",
           .def = "-1",
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
        },
        "Allows to edit a container (like a GESClip), for more details, have a look at:\n"
@@ -831,6 +925,12 @@ ges_validate_register_action_types (void)
           .mandatory = TRUE,
           NULL
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       },
       "Allows to add an asset to the current project", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -849,6 +949,12 @@ ges_validate_register_action_types (void)
           .mandatory = TRUE,
           NULL
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         { NULL }
       },
       "Allows to remove an asset from the current project", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -863,6 +969,12 @@ ges_validate_register_action_types (void)
           .mandatory = FALSE,
           NULL
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         { NULL }
       },
       "Allows to add a layer to the current timeline", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -882,6 +994,12 @@ ges_validate_register_action_types (void)
           .types="boolean",
           .def = "False"
         },
+        {
+          .name = "project-uri",
+          .description = "The nested timeline to add clip to",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         { NULL }
       },
       "Allows to remove a layer from the current timeline", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -930,6 +1048,12 @@ ges_validate_register_action_types (void)
           .types = "double or string",
           .mandatory = FALSE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Allows to add a clip to a given layer", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -941,6 +1065,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Allows to remove a clip from a given layer", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -975,6 +1105,12 @@ ges_validate_register_action_types (void)
           .types = "gvalue",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Allows to change child property of an object", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -992,6 +1128,12 @@ ges_validate_register_action_types (void)
           .types = "double or string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Split a clip at a specified position.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1009,6 +1151,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Sets restriction caps on tracks of a specific type.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1026,6 +1174,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Sets restriction caps on tracks of a specific type.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1059,6 +1213,12 @@ ges_validate_register_action_types (void)
           .mandatory = FALSE,
           .def = "NULL"
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Add a child to @container-name. If asset-id and child-type are specified,"
        " the child will be created and added. Otherwize @child-name has to be specified"
@@ -1078,6 +1238,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Remove a child from @container-name.", FALSE);
 
@@ -1095,6 +1261,12 @@ ges_validate_register_action_types (void)
           .types = "boolean",
           .mandatory = FALSE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Ungroup children of @container-name.", FALSE);
 
@@ -1133,6 +1305,12 @@ ges_validate_register_action_types (void)
           .mandatory = FALSE,
           .def = "linear",
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Adds a GstControlSource on @element-name::@property-name"
          " allowing you to then add keyframes on that property.", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -1163,6 +1341,12 @@ ges_validate_register_action_types (void)
           .types = "float",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1193,6 +1377,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = FALSE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1216,6 +1406,12 @@ ges_validate_register_action_types (void)
           .types = "string or float",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE);