diff --git a/ges/ges-base-xml-formatter.c b/ges/ges-base-xml-formatter.c index 81b82f56d9..b905babd74 100644 --- a/ges/ges-base-xml-formatter.c +++ b/ges/ges-base-xml-formatter.c @@ -96,6 +96,9 @@ struct _GESBaseXmlFormatterPrivate /* List of asset waited to be created */ GList *pending_assets; + + /* current track element */ + GESTrackElement *current_track_element; }; static void @@ -339,6 +342,7 @@ ges_base_xml_formatter_init (GESBaseXmlFormatter * self) g_str_equal, g_free, gst_object_unref); priv->layers = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) _free_layer_entry); + priv->current_track_element = NULL; } static void @@ -828,6 +832,35 @@ ges_base_xml_formatter_add_track (GESBaseXmlFormatter * self, metadatas); } +void +ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self, + const gchar * binding_type, const gchar * source_type, + const gchar * property_name, gint mode, GSList * timed_values) +{ + GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self); + GESTrackElement *element; + + element = priv->current_track_element; + if (element == NULL) { + GST_WARNING ("No current track element to which we can append a binding"); + return; + } + + if (!g_strcmp0 (source_type, "interpolation")) { + GstControlSource *source; + + source = gst_interpolation_control_source_new (); + ges_track_element_set_property_controlling_parameters (element, source, + property_name, binding_type); + + g_object_set (source, "mode", mode, NULL); + + gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE + (source), timed_values); + } else + GST_WARNING ("This interpolation type is not supported\n"); +} + void ges_base_xml_formatter_add_track_element (GESBaseXmlFormatter * self, GType track_element_type, const gchar * asset_id, const gchar * track_id, @@ -895,6 +928,7 @@ ges_base_xml_formatter_add_track_element (GESBaseXmlFormatter * self, pend->effects = g_list_append (pend->effects, peffect); } + priv->current_track_element = trackelement; } ges_project_add_asset (GES_FORMATTER (self)->project, asset); diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 246471b611..0dcbf569d1 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -89,6 +89,9 @@ ges_asset_cache_put (GESAsset * asset, GSimpleAsyncResult *res); G_GNUC_INTERNAL gboolean ges_asset_cache_set_loaded(GType extractable_type, const gchar * id, GError *error); +G_GNUC_INTERNAL GHashTable * +ges_track_element_get_bindings_hashtable(GESTrackElement *element); + GESAsset* ges_asset_cache_lookup(GType extractable_type, const gchar * id); @@ -214,6 +217,14 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_track_element (GESBaseXmlForm GstStructure *properties, const gchar *metadatas, GError **error); + +G_GNUC_INTERNAL void ges_base_xml_formatter_add_control_binding(GESBaseXmlFormatter * self, + const gchar * binding_type, + const gchar * source_type, + const gchar * property_name, + gint mode, + GSList * timed_values); + G_GNUC_INTERNAL void set_property_foreach (GQuark field_id, const GValue * value, GObject * object);; diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index 26ca17c210..3881193865 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -66,6 +66,9 @@ struct _GESTrackElementPrivate gboolean locked; /* If TRUE, then moves in sync with its controlling * GESClip */ + + GHashTable *bindings_hashtable; /* We need this if we want to be able to serialize + and deserialize keyframes */ }; enum @@ -181,6 +184,9 @@ ges_track_element_dispose (GObject * object) if (priv->properties_hashtable) g_hash_table_destroy (priv->properties_hashtable); + if (priv->bindings_hashtable) + g_hash_table_destroy (priv->bindings_hashtable); + if (priv->gnlobject) { GstState cstate; @@ -301,6 +307,8 @@ ges_track_element_init (GESTrackElement * self) priv->pending_active = TRUE; priv->locked = TRUE; priv->properties_hashtable = NULL; + priv->bindings_hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); } static gboolean @@ -748,6 +756,14 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track) return ret; } +GHashTable * +ges_track_element_get_bindings_hashtable (GESTrackElement * trackelement) +{ + GESTrackElementPrivate *priv = GES_TRACK_ELEMENT (trackelement)->priv; + + return priv->bindings_hashtable; +} + /** * ges_track_element_get_track: * @object: a #GESTrackElement @@ -1449,3 +1465,99 @@ ges_track_element_edit (GESTrackElement * object, return TRUE; } + + +/** + * ges_track_element_set_property_controlling_parameters: + * @object: the #GESTrackElement on which to set a control binding + * @source: (element-type GstControlSource): the #GstControlSource to set on the binding. + * @property_name: The name of the property to control. + * @binding_type: The type of binding to create. Only "direct" is available for now. + * + * Creates a #GstControlBinding and adds it to the #GstElement concerned by the + * property. Use the same syntax as #ges_track_element_lookup_child for + * the property name. + * + * Returns: %TRUE if the binding could be created and added, %FALSE if an error + * occured + * + * Since: 1.0.XX + */ +gboolean +ges_track_element_set_property_controlling_parameters (GESTrackElement * object, + GstControlSource * source, + const gchar * property_name, const gchar * binding_type) +{ + GESTrackElementPrivate *priv; + GstElement *element; + GParamSpec *pspec; + GstControlBinding *binding; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE); + priv = GES_TRACK_ELEMENT (object)->priv; + + if (G_UNLIKELY (!(GST_IS_CONTROL_SOURCE (source)))) { + GST_WARNING + ("You need to provide a non-null control source to build a new control binding"); + return FALSE; + } + + if (!ges_track_element_lookup_child (object, property_name, &element, &pspec)) { + GST_WARNING ("You need to provide a valid and controllable property name"); + return FALSE; + } + + /* TODO : update this according to new types of bindings */ + if (!g_strcmp0 (binding_type, "direct")) { + /* First remove existing binding */ + binding = + (GstControlBinding *) g_hash_table_lookup (priv->bindings_hashtable, + property_name); + if (binding) { + GST_LOG ("Removing old binding %p for property %s", binding, + property_name); + gst_object_remove_control_binding (GST_OBJECT (element), binding); + } + binding = + gst_direct_control_binding_new (GST_OBJECT (element), property_name, + source); + gst_object_add_control_binding (GST_OBJECT (element), binding); + g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name), + binding); + return TRUE; + } + + GST_WARNING ("Binding type must be in [direct]"); + + return FALSE; +} + +/** + * ges_track_element_get_control_binding: + * @object: the #GESTrackElement in which to lookup the bindings. + * @property_name: The property_name to which the binding is associated. + * + * Looks up the various controlled properties for that #GESTrackElement, + * and returns the #GstControlBinding which controls @property_name. + * + * Returns: the #GstControlBinding associated with @property_name, or %NULL + * if that property is not controlled. + * + * Since: 1.0.XX + */ +GstControlBinding * +ges_track_element_get_control_binding (GESTrackElement * object, + const gchar * property_name) +{ + GESTrackElementPrivate *priv; + GstControlBinding *binding; + + g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), NULL); + + priv = GES_TRACK_ELEMENT (object)->priv; + + binding = + (GstControlBinding *) g_hash_table_lookup (priv->bindings_hashtable, + property_name); + return binding; +} diff --git a/ges/ges-track-element.h b/ges/ges-track-element.h index 1086dcc36b..8e4ee9488c 100644 --- a/ges/ges-track-element.h +++ b/ges/ges-track-element.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include G_BEGIN_DECLS @@ -184,5 +186,15 @@ ges_track_element_edit (GESTrackElement * object, GList *layers, GESEditMode mode, GESEdge edge, guint64 position); +gboolean +ges_track_element_set_property_controlling_parameters(GESTrackElement *object, + GstControlSource *source, + const gchar *property_name, + const gchar *binding_type); + +GstControlBinding * +ges_track_element_get_control_binding (GESTrackElement *object, + const gchar *property_name); + G_END_DECLS #endif /* _GES_TRACK_ELEMENT */ diff --git a/ges/ges-xml-formatter.c b/ges/ges-xml-formatter.c index 117fde1bac..145a40c71c 100644 --- a/ges/ges-xml-formatter.c +++ b/ges/ges-xml-formatter.c @@ -22,6 +22,7 @@ /* TODO Determine error codes numbers */ #include "ges.h" +#include #include #include "ges-internal.h" @@ -513,6 +514,54 @@ wrong_type: "element '%s', %s not a GESClip'", element_name, strtype); } +static inline void +_parse_binding (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GESXmlFormatter * self, GError ** error) +{ + const gchar *type = NULL, *source_type = NULL, *timed_values = + NULL, *property_name = NULL, *mode = NULL; + gchar **pairs, **tmp; + gchar *pair; + GSList *list = NULL; + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRING, "type", &type, + G_MARKUP_COLLECT_STRING, "source_type", &source_type, + G_MARKUP_COLLECT_STRING, "property", &property_name, + G_MARKUP_COLLECT_STRING, "mode", &mode, + G_MARKUP_COLLECT_STRING, "values", &timed_values, + G_MARKUP_COLLECT_INVALID)) { + return; + } + + pairs = g_strsplit (timed_values, " ", 0); + for (tmp = pairs; tmp != NULL; tmp += 1) { + gchar **value_pair; + + pair = *tmp; + if (pair == NULL) + break; + if (strlen (pair)) { + GstTimedValue *value; + + value = g_slice_new (GstTimedValue); + value_pair = g_strsplit (pair, ":", 0); + value->timestamp = g_ascii_strtoull (value_pair[0], NULL, 10); + value->value = g_ascii_strtod (value_pair[1], NULL); + list = g_slist_append (list, value); + g_strfreev (value_pair); + } + } + + g_strfreev (pairs); + ges_base_xml_formatter_add_control_binding (GES_BASE_XML_FORMATTER (self), + type, + source_type, + property_name, (gint) g_ascii_strtoll (mode, NULL, 10), list); +} + static inline void _parse_effect (GMarkupParseContext * context, const gchar * element_name, const gchar ** attribute_names, const gchar ** attribute_values, @@ -623,6 +672,9 @@ _parse_element_start (GMarkupParseContext * context, const gchar * element_name, else if (g_strcmp0 (element_name, "effect") == 0) _parse_effect (context, element_name, attribute_names, attribute_values, self, error); + else if (g_strcmp0 (element_name, "binding") == 0) + _parse_binding (context, element_name, attribute_names, + attribute_values, self, error); else GST_LOG_OBJECT (self, "Element %s not handled", element_name); } @@ -767,6 +819,56 @@ _save_tracks (GString * str, GESTimeline * timeline) g_list_free_full (tracks, gst_object_unref); } +/* TODO : Use this function for every track element with controllable properties */ +static inline void +_save_keyframes (GString * str, GESTrackElement * trackelement) +{ + GHashTable *bindings_hashtable; + GHashTableIter iter; + gpointer key, value; + + bindings_hashtable = ges_track_element_get_bindings_hashtable (trackelement); + + g_hash_table_iter_init (&iter, bindings_hashtable); + + /* We iterate over the bindings, and save the timed values */ + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (GST_IS_DIRECT_CONTROL_BINDING ((GstControlBinding *) value)) { + GstControlSource *source; + GstDirectControlBinding *binding; + + binding = (GstDirectControlBinding *) value; + + g_object_get (binding, "control-source", &source, NULL); + + if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) { + GList *timed_values, *tmp; + GstInterpolationMode mode; + + append_printf_escaped (str, + "\n"); + } else + GST_DEBUG ("control source not in [interpolation]"); + } else + GST_DEBUG ("Binding type not in [direct]"); + } +} + static inline void _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement, GESTimeline * timeline) @@ -822,8 +924,12 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement, } g_free (pspecs); - append_printf_escaped (str, " children-properties='%s'/>\n", + append_printf_escaped (str, " children-properties='%s'>\n", gst_structure_to_string (structure)); + + _save_keyframes (str, trackelement); + + append_printf_escaped (str, "\n"); gst_structure_free (structure); } diff --git a/tests/check/ges/project.c b/tests/check/ges/project.c index 4d14d8f3f3..dd88a8a667 100644 --- a/tests/check/ges/project.c +++ b/tests/check/ges/project.c @@ -21,6 +21,8 @@ #include "test-utils.h" #include #include +#include +#include static void project_loaded_cb (GESProject * project, GESTimeline * timeline, @@ -270,6 +272,190 @@ _test_project (GESProject * project, GESTimeline * timeline) assert_equals_int (g_list_length ((GList *) profiles), 2); } +static void +_add_keyframes (GESTimeline * timeline) +{ + GList *tracks; + GList *tmp; + + tracks = ges_timeline_get_tracks (timeline); + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track; + GList *track_elements; + GList *tmp_tck; + + track = GES_TRACK (tmp->data); + switch (track->type) { + case GES_TRACK_TYPE_VIDEO: + track_elements = ges_track_get_elements (track); + + for (tmp_tck = track_elements; tmp_tck; tmp_tck = tmp_tck->next) { + GESTrackElement *element = GES_TRACK_ELEMENT (tmp_tck->data); + + if (GES_IS_EFFECT (element)) { + GstControlSource *source; + GstControlBinding *tmp_binding, *binding; + + source = gst_interpolation_control_source_new (); + + /* Check binding creation and replacement */ + binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (binding == NULL); + ges_track_element_set_property_controlling_parameters (element, + source, "scratch-lines", "direct"); + tmp_binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (tmp_binding != NULL); + ges_track_element_set_property_controlling_parameters (element, + source, "scratch-lines", "direct"); + binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (binding != tmp_binding); + + + g_object_set (source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE + (source), 0 * GST_SECOND, 0.); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE + (source), 5 * GST_SECOND, 0.); + gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE + (source), 10 * GST_SECOND, 1.); + } + } + break; + default: + break; + } + } +} + +static void +_check_keyframes (GESTimeline * timeline) +{ + GList *tracks; + GList *tmp; + + tracks = ges_timeline_get_tracks (timeline); + for (tmp = tracks; tmp; tmp = tmp->next) { + GESTrack *track; + GList *track_elements; + GList *tmp_tck; + + track = GES_TRACK (tmp->data); + switch (track->type) { + case GES_TRACK_TYPE_VIDEO: + track_elements = ges_track_get_elements (track); + + for (tmp_tck = track_elements; tmp_tck; tmp_tck = tmp_tck->next) { + GESTrackElement *element = GES_TRACK_ELEMENT (tmp_tck->data); + + if (GES_IS_EFFECT (element)) { + GstControlBinding *binding; + GstControlSource *source; + GList *timed_values; + GstTimedValue *value; + + binding = + ges_track_element_get_control_binding (element, + "scratch-lines"); + fail_unless (binding != NULL); + g_object_get (binding, "control-source", &source, NULL); + fail_unless (source != NULL); + + /* Now check keyframe position */ + timed_values = + gst_timed_value_control_source_get_all + (GST_TIMED_VALUE_CONTROL_SOURCE (source)); + value = timed_values->data; + fail_unless (value->value == 0.); + fail_unless (value->timestamp == 0 * GST_SECOND); + timed_values = timed_values->next; + value = timed_values->data; + fail_unless (value->value == 0.); + fail_unless (value->timestamp == 5 * GST_SECOND); + timed_values = timed_values->next; + value = timed_values->data; + fail_unless (value->value == 1.); + fail_unless (value->timestamp == 10 * GST_SECOND); + } + } + break; + default: + break; + } + } +} + +GST_START_TEST (test_project_add_keyframes) +{ + GMainLoop *mainloop; + //GESTimelinePipeline *pipeline; + GESProject *project; + GESTimeline *timeline; + GESAsset *formatter_asset; + gboolean saved; + gchar *uri = ges_test_file_uri ("test-keyframes.xges"); + + project = ges_project_new (uri); + mainloop = g_main_loop_new (NULL, FALSE); + + /* Connect the signals */ + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + g_signal_connect (project, "missing-uri", (GCallback) _set_new_uri, NULL); + + /* Now extract a timeline from it */ + GST_LOG ("Loading project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + + g_main_loop_run (mainloop); + + GST_LOG ("Test first loading"); + + g_free (uri); + + _add_keyframes (timeline); + + uri = ges_test_file_uri ("test-keyframes-save.xges"); + + formatter_asset = ges_asset_request (GES_TYPE_FORMATTER, "ges", NULL); + saved = + ges_project_save (project, timeline, uri, formatter_asset, TRUE, NULL); + fail_unless (saved); + + gst_object_unref (timeline); + gst_object_unref (project); + + project = ges_project_new (uri); + + ASSERT_OBJECT_REFCOUNT (project, "Our + cache", 2); + + g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, mainloop); + + GST_LOG ("Loading saved project"); + timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL)); + fail_unless (GES_IS_TIMELINE (timeline)); + + g_main_loop_run (mainloop); + + _check_keyframes (timeline); + + gst_object_unref (timeline); + gst_object_unref (project); + g_free (uri); + + g_main_loop_unref (mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) project_loaded_cb, + mainloop); + g_signal_handlers_disconnect_by_func (project, (GCallback) asset_added_cb, + NULL); +} + +GST_END_TEST; + GST_START_TEST (test_project_load_xges) { gboolean saved; @@ -435,6 +621,7 @@ ges_suite (void) tcase_add_test (tc_chain, test_project_simple); tcase_add_test (tc_chain, test_project_add_assets); tcase_add_test (tc_chain, test_project_load_xges); + tcase_add_test (tc_chain, test_project_add_keyframes); /*tcase_add_test (tc_chain, test_load_xges_and_play); */ tcase_add_test (tc_chain, test_project_unexistant_effect); diff --git a/tests/check/ges/test-keyframes.xges b/tests/check/ges/test-keyframes.xges new file mode 100644 index 0000000000..1fde7412da --- /dev/null +++ b/tests/check/ges/test-keyframes.xges @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + +