[Keyframes] Adds API to set a control binding on a track element, and the serialization code.

This commit is contained in:
Mathieu Duponchelle 2013-03-30 18:54:50 +01:00 committed by Thibault Saunier
parent 811e68811c
commit e655a75605
7 changed files with 486 additions and 1 deletions

View file

@ -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);

View file

@ -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);;

View file

@ -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;
}

View file

@ -26,6 +26,8 @@
#include <ges/ges-types.h>
#include <ges/ges-clip.h>
#include <ges/ges-track.h>
#include <gst/controller/gstdirectcontrolbinding.h>
#include <gst/controller/gstinterpolationcontrolsource.h>
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 */

View file

@ -22,6 +22,7 @@
/* TODO Determine error codes numbers */
#include "ges.h"
#include <string.h>
#include <errno.h>
#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,
"<binding type='direct' source_type='interpolation' property='%s'",
(gchar *) key);
g_object_get (source, "mode", &mode, NULL);
append_printf_escaped (str, " mode='%d'", mode);
append_printf_escaped (str, " values ='");
timed_values =
gst_timed_value_control_source_get_all
(GST_TIMED_VALUE_CONTROL_SOURCE (source));
for (tmp = timed_values; tmp; tmp = tmp->next) {
GstTimedValue *value;
value = (GstTimedValue *) tmp->data;
append_printf_escaped (str, " %lld:%f ",
(long long int) value->timestamp, value->value);
}
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, "</effect>\n");
gst_structure_free (structure);
}

View file

@ -21,6 +21,8 @@
#include "test-utils.h"
#include <ges/ges.h>
#include <gst/check/gstcheck.h>
#include <gst/controller/gstdirectcontrolbinding.h>
#include <gst/controller/gstinterpolationcontrolsource.h>
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);

View file

@ -0,0 +1,23 @@
<ges version="0.1">
<project metadatas='metadatas, name=(string)&quot;Example\ project&quot;;'>
<encoding-profiles>
<encoding-profile name='first_profile' description='(null)' type='container' format='application/ogg'>
<stream-profile parent='first_profile' id='0' type='video' presence='0' format='video/x-h264' pass='0' variableframerate='0' />
<stream-profile parent='first_profile' id='1' type='audio' presence='0' format='audio/x-aac' />
</encoding-profile>
</encoding-profiles>
<resources>
<asset id="file:///test/not/exisiting"
extractable-type-name="GESUriClip"/>
</resources>
<timeline>
<track track-type="2" caps="audio/x-raw" track-id="0"/>
<track track-type="4" caps="video/x-raw" track-id="1"/>
<layer priority="0" properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, a=(guint)3'>
<clip id="0" layer-priority='0' asset-id="file:///test/not/exisiting" type-name="GESUriClip" track-types="6" start="0" duration="10000000000">
<effect asset-id='agingtv' clip-id='0' type-name='GESEffect' track-type='4' track-id='1' metadatas='metadatas;' children-properties='properties, scratch-lines=(uint)12;'/>
</clip>
</layer>
</timeline>
</project>
</ges>