gstreamer/ges/ges-structured-interface.c
Thibault Saunier 32dae5e3da validate: Handle absolute control binding support when setting keyframes
And minor fix in set-control-source
2020-02-26 13:36:30 -03:00

756 lines
20 KiB
C

/* GStreamer Editing Services
*
* Copyright (C) <2015> Thibault Saunier <tsaunier@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ges-structured-interface.h"
#include <string.h>
#define LAST_CONTAINER_QDATA g_quark_from_string("ges-structured-last-container")
#define LAST_CHILD_QDATA g_quark_from_string("ges-structured-last-child")
static gboolean
_get_clocktime (GstStructure * structure, const gchar * name, gpointer var)
{
gboolean found = FALSE;
GstClockTime *val = (GstClockTime *) var;
const GValue *gvalue = gst_structure_get_value (structure, name);
if (gvalue) {
if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) {
*val = (GstClockTime) g_value_get_uint64 (gvalue);
found = TRUE;
} else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) {
*val = (GstClockTime) g_value_get_uint64 (gvalue);
found = TRUE;
} else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT) {
*val = (GstClockTime) g_value_get_uint (gvalue);
found = TRUE;
} else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT) {
*val = (GstClockTime) g_value_get_int (gvalue);
found = TRUE;
} else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64) {
*val = (GstClockTime) g_value_get_int64 (gvalue);
found = TRUE;
} else if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE) {
gdouble d = g_value_get_double (gvalue);
found = TRUE;
if (d == -1.0)
*val = GST_CLOCK_TIME_NONE;
else {
*val = d * GST_SECOND;
*val = GST_ROUND_UP_4 (*val);
}
}
}
return found;
}
#define GET_AND_CHECK(name,type,var,label) G_STMT_START {\
gboolean found = FALSE; \
\
if (type == GST_TYPE_CLOCK_TIME) {\
found = _get_clocktime(structure,name,var);\
}\
else { \
found = gst_structure_get (structure, name, type, var, NULL); \
}\
if (!found) {\
gchar *struct_str = gst_structure_to_string (structure); \
*error = g_error_new (GES_ERROR, 0, \
"Could not get the mandatory field '%s'" \
" of type %s - fields in %s", name, g_type_name (type), struct_str); \
g_free (struct_str); \
goto label;\
} \
} G_STMT_END
#define TRY_GET_STRING(name,var,def) G_STMT_START {\
*var = gst_structure_get_string (structure, name); \
if (*var == NULL) \
*var = def; \
} G_STMT_END
#define TRY_GET(name,type,var,def) G_STMT_START {\
if (type == GST_TYPE_CLOCK_TIME) {\
if (!_get_clocktime(structure,name,var))\
*var = def; \
} else if (!gst_structure_get (structure, name, type, var, NULL)) {\
*var = def; \
} \
} G_STMT_END
typedef struct
{
const gchar **fields;
GList *invalid_fields;
} FieldsError;
static gboolean
_check_field (GQuark field_id, const GValue * value, FieldsError * fields_error)
{
guint i;
const gchar *field = g_quark_to_string (field_id);
for (i = 0; fields_error->fields[i]; i++) {
if (g_strcmp0 (fields_error->fields[i], field) == 0) {
return TRUE;
}
}
fields_error->invalid_fields =
g_list_append (fields_error->invalid_fields, (gpointer) field);
return TRUE;
}
static gboolean
_check_fields (GstStructure * structure, FieldsError fields_error,
GError ** error)
{
gst_structure_foreach (structure,
(GstStructureForeachFunc) _check_field, &fields_error);
if (fields_error.invalid_fields) {
GList *tmp;
const gchar *struct_name = gst_structure_get_name (structure);
GString *msg = g_string_new (NULL);
g_string_append_printf (msg, "Unknown propert%s in %s%s:",
g_list_length (fields_error.invalid_fields) > 1 ? "ies" : "y",
strlen (struct_name) > 1 ? "--" : "-",
gst_structure_get_name (structure));
for (tmp = fields_error.invalid_fields; tmp; tmp = tmp->next)
g_string_append_printf (msg, " %s", (gchar *) tmp->data);
if (error)
*error = g_error_new_literal (GES_ERROR, 0, msg->str);
g_string_free (msg, TRUE);
return FALSE;
}
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,
GstStructure * structure, GError ** error)
{
GESTrackElement *element;
gboolean absolute;
gdouble value;
GstClockTime timestamp;
GstControlBinding *binding = NULL;
GstTimedValueControlSource *source = NULL;
gchar *element_name = NULL, *property_name = NULL;
gboolean ret = FALSE;
const gchar *valid_fields[] =
{ "element-name", "property-name", "value", "timestamp", "project-uri",
NULL
};
FieldsError fields_error = { valid_fields, NULL };
if (!_check_fields (structure, fields_error, error))
return FALSE;
GET_AND_CHECK ("element-name", G_TYPE_STRING, &element_name, done);
GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, done);
GET_AND_CHECK ("timestamp", GST_TYPE_CLOCK_TIME, &timestamp, done);
element =
GES_TRACK_ELEMENT (ges_timeline_get_element (timeline, element_name));
if (!GES_IS_TRACK_ELEMENT (element)) {
*error =
g_error_new (GES_ERROR, 0, "Could not find TrackElement %s",
element_name);
goto done;
}
binding = ges_track_element_get_control_binding (element, property_name);
if (binding == NULL) {
*error = g_error_new (GES_ERROR, 0, "No control binding found for %s:%s"
" you should first set-control-binding on it",
element_name, property_name);
goto done;
}
g_object_get (binding, "control-source", &source, NULL);
if (source == NULL) {
*error = g_error_new (GES_ERROR, 0, "No control source found for %s:%s"
" you should first set-control-binding on it",
element_name, property_name);
goto done;
}
if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
*error = g_error_new (GES_ERROR, 0, "You can use add-keyframe"
" only on GstTimedValueControlSource not %s",
G_OBJECT_TYPE_NAME (source));
goto done;
}
g_object_get (binding, "absolute", &absolute, NULL);
if (absolute) {
GParamSpec *pspec;
const GValue *v;
GValue v2 = G_VALUE_INIT;
if (!ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (element),
property_name, NULL, &pspec)) {
*error =
g_error_new (GES_ERROR, 0, "Could not get property %s for %s",
property_name, GES_TIMELINE_ELEMENT_NAME (element));
goto done;
}
v = gst_structure_get_value (structure, "value");
if (!v) {
gchar *struct_str = gst_structure_to_string (structure);
*error = g_error_new (GES_ERROR, 0,
"Could not get the mandatory field 'value'"
" of type %s - fields in %s", g_type_name (pspec->value_type),
struct_str);
g_free (struct_str);
goto done;
}
g_value_init (&v2, G_TYPE_DOUBLE);
if (!g_value_transform (v, &v2)) {
gchar *struct_str = gst_structure_to_string (structure);
*error = g_error_new (GES_ERROR, 0,
"Could not get the mandatory field 'value'"
" of type %s - fields in %s", g_type_name (pspec->value_type),
struct_str);
g_free (struct_str);
goto done;
}
value = g_value_get_double (&v2);
g_value_reset (&v2);
} else
GET_AND_CHECK ("value", G_TYPE_DOUBLE, &value, done);
if (!g_strcmp0 (gst_structure_get_name (structure), "add-keyframe"))
ret = gst_timed_value_control_source_set (source, timestamp, value);
else {
ret = gst_timed_value_control_source_unset (source, timestamp);
if (!ret) {
*error =
g_error_new (GES_ERROR, 0,
"Could not unset value for timestamp: %" GST_TIME_FORMAT,
GST_TIME_ARGS (timestamp));
}
}
ret = _ges_save_timeline_if_needed (timeline, structure, error);
done:
if (source)
gst_object_unref (source);
g_free (element_name);
g_free (property_name);
return ret;
}
GESAsset *
_ges_get_asset_from_timeline (GESTimeline * timeline, GType type,
const gchar * id, GError ** error)
{
GESAsset *asset;
GESProject *project = ges_timeline_get_project (timeline);
GError *err = NULL;
asset = ges_project_create_asset_sync (project, id, type, &err);
if (err)
g_propagate_error (error, err);
if (!asset || (error && *error)) {
if (error && !*error) {
*error = g_error_new (GES_ERROR, 0,
"There was an error requesting the asset with id %s and type %s (%s)",
id, g_type_name (type), "unknown");
}
GST_ERROR
("There was an error requesting the asset with id %s and type %s (%s)",
id, g_type_name (type), error ? (*error)->message : "unknown");
return NULL;
}
return asset;
}
/* Unref after usage */
GESLayer *
_ges_get_layer_by_priority (GESTimeline * timeline, gint priority)
{
GList *layers;
gint nlayers;
GESLayer *layer = NULL;
priority = MAX (priority, 0);
layers = ges_timeline_get_layers (timeline);
nlayers = (gint) g_list_length (layers);
if (priority >= nlayers) {
gint i = nlayers;
while (i <= priority) {
layer = ges_timeline_append_layer (timeline);
i++;
}
layer = gst_object_ref (layer);
goto done;
}
layer = ges_timeline_get_layer (timeline, priority);
done:
g_list_free_full (layers, gst_object_unref);
return layer;
}
static gchar *
ensure_uri (gchar * location)
{
if (gst_uri_is_valid (location))
return g_strdup (location);
else
return gst_filename_to_uri (location, NULL);
}
static gboolean
get_flags_from_string (GType type, const gchar * str_flags, guint * flags)
{
GValue value = G_VALUE_INIT;
g_value_init (&value, type);
if (!gst_value_deserialize (&value, str_flags)) {
g_value_unset (&value);
return FALSE;
}
*flags = g_value_get_flags (&value);
g_value_unset (&value);
return TRUE;
}
gboolean
_ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
GError ** error)
{
GESAsset *asset;
GESLayer *layer;
GESClip *clip;
gint layer_priority;
const gchar *name;
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;
GType type;
gboolean res = FALSE;
GESTrackType track_types = GES_TRACK_TYPE_UNKNOWN;
GstClockTime duration = 1 * GST_SECOND, inpoint = 0, start =
GST_CLOCK_TIME_NONE;
const gchar *valid_fields[] =
{ "asset-id", "pattern", "name", "layer-priority", "layer", "type",
"start", "inpoint", "duration", "text", "track-types", "project-uri",
NULL
};
FieldsError fields_error = { valid_fields, NULL };
if (!_check_fields (structure, fields_error, error))
return FALSE;
GET_AND_CHECK ("asset-id", G_TYPE_STRING, &check_asset_id, beach);
TRY_GET_STRING ("pattern", &pattern, NULL);
TRY_GET_STRING ("text", &text, NULL);
TRY_GET_STRING ("name", &name, NULL);
TRY_GET ("layer-priority", G_TYPE_INT, &layer_priority, -1);
if (layer_priority == -1)
TRY_GET ("layer", G_TYPE_INT, &layer_priority, -1);
TRY_GET_STRING ("type", &type_string, "GESUriClip");
TRY_GET ("start", GST_TYPE_CLOCK_TIME, &start, GST_CLOCK_TIME_NONE);
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,
&track_types)) {
*error =
g_error_new (GES_ERROR, 0, "Invalid track types: %s",
track_types_str);
}
}
if (!(type = g_type_from_name (type_string))) {
*error = g_error_new (GES_ERROR, 0, "This type doesn't exist : %s",
type_string);
goto beach;
}
if (type == GES_TYPE_URI_CLIP) {
asset_id = ensure_uri (check_asset_id);
} else {
asset_id = g_strdup (check_asset_id);
}
gst_structure_set (structure, "asset-id", G_TYPE_STRING, asset_id, NULL);
asset = _ges_get_asset_from_timeline (timeline, type, asset_id, error);
if (!asset) {
res = FALSE;
goto beach;
}
if (layer_priority == -1) {
GESContainer *container;
container = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA);
if (!container || !GES_IS_CLIP (container))
layer = _ges_get_layer_by_priority (timeline, 0);
else
layer = ges_clip_get_layer (GES_CLIP (container));
if (!layer)
layer = _ges_get_layer_by_priority (timeline, 0);
} else {
layer = _ges_get_layer_by_priority (timeline, layer_priority);
}
if (!layer) {
*error =
g_error_new (GES_ERROR, 0, "No layer with priority %d", layer_priority);
goto beach;
}
if (GES_IS_URI_CLIP_ASSET (asset) && !GST_CLOCK_TIME_IS_VALID (duration)) {
duration = GST_CLOCK_DIFF (inpoint,
ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset)));
}
clip = ges_layer_add_asset (layer, asset, start, inpoint, duration,
track_types);
if (clip) {
res = TRUE;
if (GES_TIMELINE_ELEMENT_DURATION (clip) == 0) {
*error = g_error_new (GES_ERROR, 0,
"Clip %s has 0 as duration, please provide a proper duration",
asset_id);
res = FALSE;
goto beach;
}
if (GES_IS_TEST_CLIP (clip)) {
if (pattern) {
GEnumClass *enum_class =
G_ENUM_CLASS (g_type_class_ref (GES_VIDEO_TEST_PATTERN_TYPE));
GEnumValue *value = g_enum_get_value_by_nick (enum_class, pattern);
if (!value) {
res = FALSE;
goto beach;
}
ges_test_clip_set_vpattern (GES_TEST_CLIP (clip), value->value);
g_type_class_unref (enum_class);
}
}
if (GES_IS_TITLE_CLIP (clip) && text)
ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip),
"text", text, NULL);
if (name
&& !ges_timeline_element_set_name (GES_TIMELINE_ELEMENT (clip), name)) {
res = FALSE;
*error =
g_error_new (GES_ERROR, 0, "couldn't set name %s on clip with id %s",
name, asset_id);
}
} else {
*error = g_error_new (GES_ERROR, 0,
"Couldn't add clip with id %s to layer with priority %d", asset_id,
layer_priority);
}
if (res) {
g_object_set_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA, clip);
g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, NULL);
}
gst_object_unref (layer);
res = _ges_save_timeline_if_needed (timeline, structure, error);
beach:
g_free (asset_id);
g_free (check_asset_id);
return res;
}
gboolean
_ges_container_add_child_from_struct (GESTimeline * timeline,
GstStructure * structure, GError ** error)
{
GESAsset *asset;
GESContainer *container;
GESTimelineElement *child = NULL;
const gchar *container_name, *child_name, *child_type, *id;
gboolean res = TRUE;
const gchar *valid_fields[] = { "container-name", "asset-id",
"child-type", "child-name", "project-uri", NULL
};
FieldsError fields_error = { valid_fields, NULL };
if (!_check_fields (structure, fields_error, error))
return FALSE;
container_name = gst_structure_get_string (structure, "container-name");
if (container_name == NULL) {
container = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA);
} else {
container =
GES_CONTAINER (ges_timeline_get_element (timeline, container_name));
}
if (!GES_IS_CONTAINER (container)) {
*error =
g_error_new (GES_ERROR, 0, "Could not find container: %s (%p)",
container_name, container);
res = FALSE;
goto beach;
}
id = gst_structure_get_string (structure, "asset-id");
child_type = gst_structure_get_string (structure, "child-type");
if (id && child_type) {
asset =
_ges_get_asset_from_timeline (timeline, g_type_from_name (child_type),
id, error);
if (asset == NULL) {
res = FALSE;
goto beach;
}
child = GES_TIMELINE_ELEMENT (ges_asset_extract (asset, NULL));
if (!GES_IS_TIMELINE_ELEMENT (child)) {
*error = g_error_new (GES_ERROR, 0, "Could not extract child element");
goto beach;
}
}
child_name = gst_structure_get_string (structure, "child-name");
if (!child && child_name) {
child = ges_timeline_get_element (timeline, child_name);
if (!GES_IS_TIMELINE_ELEMENT (child)) {
*error = g_error_new (GES_ERROR, 0, "Could not find child element");
goto beach;
}
}
if (!child) {
*error =
g_error_new (GES_ERROR, 0, "Wrong parameters, could not get a child");
return FALSE;
}
if (child_name)
ges_timeline_element_set_name (child, child_name);
else
child_name = GES_TIMELINE_ELEMENT_NAME (child);
res = ges_container_add (container, child);
if (res == FALSE) {
g_error_new (GES_ERROR, 0, "Could not add child to container");
} else {
g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, child);
}
res = _ges_save_timeline_if_needed (timeline, structure, error);
beach:
return res;
}
gboolean
_ges_set_child_property_from_struct (GESTimeline * timeline,
GstStructure * structure, GError ** error)
{
const GValue *value;
GESTimelineElement *element;
const gchar *property_name, *element_name;
const gchar *valid_fields[] =
{ "element-name", "property", "value", "project-uri", NULL };
FieldsError fields_error = { valid_fields, NULL };
if (!_check_fields (structure, fields_error, error))
return FALSE;
element_name = gst_structure_get_string (structure, "element-name");
if (element_name == NULL)
element = g_object_get_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA);
else
element = ges_timeline_get_element (timeline, element_name);
property_name = gst_structure_get_string (structure, "property");
if (property_name == NULL) {
const gchar *name = gst_structure_get_name (structure);
if (g_str_has_prefix (name, "set-"))
property_name = &name[4];
else {
gchar *struct_str = gst_structure_to_string (structure);
*error =
g_error_new (GES_ERROR, 0, "Could not find any property name in %s",
struct_str);
g_free (struct_str);
return FALSE;
}
}
if (element) {
if (!ges_track_element_lookup_child (GES_TRACK_ELEMENT (element),
property_name, NULL, NULL))
element = NULL;
}
if (!element) {
element = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA);
if (element == NULL) {
*error =
g_error_new (GES_ERROR, 0,
"Could not find anywhere to set property: %s", property_name);
return FALSE;
}
}
if (!GES_IS_TIMELINE_ELEMENT (element)) {
*error =
g_error_new (GES_ERROR, 0, "Could not find child %s", element_name);
return FALSE;
}
value = gst_structure_get_value (structure, "value");
g_print ("%s Setting %s property to %s\n", element->name, property_name,
gst_value_serialize (value));
if (!ges_timeline_element_set_child_property (element, property_name,
(GValue *) value)) {
guint n_specs, i;
GParamSpec **specs =
ges_timeline_element_list_children_properties (element, &n_specs);
GString *errstr = g_string_new (NULL);
g_string_append_printf (errstr,
"\n Could not set property `%s` on `%s`, valid properties:\n",
property_name, GES_TIMELINE_ELEMENT_NAME (element));
for (i = 0; i < n_specs; i++)
g_string_append_printf (errstr, " - %s\n", specs[i]->name);
g_free (specs);
*error = g_error_new_literal (GES_ERROR, 0, errstr->str);
g_string_free (errstr, TRUE);
return FALSE;
}
return _ges_save_timeline_if_needed (timeline, structure, error);
}
#undef GET_AND_CHECK
#undef TRY_GET