mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 05:31:15 +00:00
5575d9cb02
Until now when doing `+test-clip blue +effect effect1 + effect effect2` leaded to the following pseudo pipeline videotestsrc ! effect2 ! effect1 given the `ges-launch` syntax this is quite unexpected and this needed fixing Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5735>
1177 lines
33 KiB
C
1177 lines
33 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 "ges-internal.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")
|
|
|
|
#ifdef G_HAVE_ISO_VARARGS
|
|
#define REPORT_UNLESS(condition, errpoint, ...) \
|
|
G_STMT_START { \
|
|
if (!(condition)) { \
|
|
gchar *tmp = gst_info_strdup_printf(__VA_ARGS__); \
|
|
*error = g_error_new_literal (GES_ERROR, 0, tmp); \
|
|
g_free (tmp); \
|
|
goto errpoint; \
|
|
} \
|
|
} \
|
|
G_STMT_END
|
|
#else /* G_HAVE_GNUC_VARARGS */
|
|
#ifdef G_HAVE_GNUC_VARARGS
|
|
#define REPORT_UNLESS(condition, errpoint, args...) \
|
|
G_STMT_START { \
|
|
if (!(condition)) { \
|
|
gchar *tmp = gst_info_strdup_printf(##args); \
|
|
*error = g_error_new_literal (GES_ERROR, 0, tmp); \
|
|
g_free (tmp); \
|
|
goto errpoint; \
|
|
} \
|
|
} \
|
|
G_STMT_END
|
|
#endif /* G_HAVE_ISO_VARARGS */
|
|
#endif /* G_HAVE_GNUC_VARARGS */
|
|
|
|
#define GET_AND_CHECK(name,type,var,label) G_STMT_START {\
|
|
gboolean found = FALSE; \
|
|
\
|
|
if (type == GST_TYPE_CLOCK_TIME) {\
|
|
found = ges_util_structure_get_clocktime (structure,name, (GstClockTime*)var,NULL);\
|
|
}\
|
|
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_TIME(name, var, var_frames, def) G_STMT_START { \
|
|
if (!ges_util_structure_get_clocktime (structure, name, var, var_frames)) { \
|
|
*var = def; \
|
|
*var_frames = GES_FRAME_NUMBER_NONE; \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
static gboolean
|
|
_get_structure_value (GstStructure * structure, const gchar * field, GType type,
|
|
gpointer v)
|
|
{
|
|
gboolean res = TRUE;
|
|
const gchar *value_str;
|
|
const GValue *value;
|
|
GValue nvalue = G_VALUE_INIT;
|
|
|
|
if (gst_structure_get (structure, field, type, v, NULL))
|
|
return res;
|
|
|
|
g_value_init (&nvalue, type);
|
|
value = gst_structure_get_value (structure, field);
|
|
if (!value)
|
|
goto fail;
|
|
|
|
if (g_value_transform (value, &nvalue))
|
|
goto set_and_get_value;
|
|
|
|
if (!G_VALUE_HOLDS_STRING (value))
|
|
goto fail;
|
|
|
|
value_str = g_value_get_string (value);
|
|
if (!gst_value_deserialize (&nvalue, value_str))
|
|
goto done;
|
|
|
|
set_and_get_value:
|
|
gst_structure_set_value (structure, field, &nvalue);
|
|
gst_structure_get (structure, field, type, v, NULL);
|
|
|
|
done:
|
|
g_value_reset (&nvalue);
|
|
return res;
|
|
|
|
fail:
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
#define TRY_GET(name, type, var, def) G_STMT_START {\
|
|
g_assert (type != GST_TYPE_CLOCK_TIME); \
|
|
if (!_get_structure_value (structure, name, type, var))\
|
|
*var = def; \
|
|
} G_STMT_END
|
|
|
|
typedef struct
|
|
{
|
|
const gchar **fields;
|
|
GList *invalid_fields;
|
|
} FieldsError;
|
|
|
|
static gboolean
|
|
enum_from_str (GType type, const gchar * str_enum, guint * enum_value)
|
|
{
|
|
GValue value = G_VALUE_INIT;
|
|
g_value_init (&value, type);
|
|
|
|
if (!gst_value_deserialize (&value, str_enum))
|
|
return FALSE;
|
|
|
|
*enum_value = g_value_get_enum (&value);
|
|
g_value_unset (&value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static GESTimelineElement *
|
|
find_element_for_property (GESTimeline * timeline, GstStructure * structure,
|
|
gchar ** property_name, gboolean require_track_element, GError ** error)
|
|
{
|
|
GList *tmp;
|
|
const gchar *element_name;
|
|
GESTimelineElement *element;
|
|
|
|
element_name = gst_structure_get_string (structure, "element-name");
|
|
if (element_name == NULL) {
|
|
element = g_object_get_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA);
|
|
if (element)
|
|
gst_object_ref (element);
|
|
} else {
|
|
element = ges_timeline_get_element (timeline, element_name);
|
|
}
|
|
|
|
if (*property_name == NULL) {
|
|
gchar *tmpstr = *property_name;
|
|
const gchar *name = gst_structure_get_name (structure);
|
|
|
|
REPORT_UNLESS (g_str_has_prefix (name, "set-"), err,
|
|
"Could not find any property name in %" GST_PTR_FORMAT, structure);
|
|
|
|
*property_name = g_strdup (&tmpstr[4]);
|
|
|
|
g_free (tmpstr);
|
|
}
|
|
|
|
if (element) {
|
|
if (!ges_timeline_element_lookup_child (element,
|
|
*property_name, NULL, NULL)) {
|
|
gst_clear_object (&element);
|
|
}
|
|
}
|
|
|
|
if (!element) {
|
|
element = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA);
|
|
if (element)
|
|
gst_object_ref (element);
|
|
}
|
|
|
|
REPORT_UNLESS (GES_IS_TIMELINE_ELEMENT (element), err,
|
|
"Could not find child %s from %" GST_PTR_FORMAT, element_name, structure);
|
|
|
|
if (!require_track_element || GES_IS_TRACK_ELEMENT (element))
|
|
return element;
|
|
|
|
|
|
REPORT_UNLESS (GES_IS_CONTAINER (element), err,
|
|
"Could not find child %s from %" GST_PTR_FORMAT, element_name, structure);
|
|
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
|
|
if (ges_timeline_element_lookup_child (tmp->data, *property_name, NULL,
|
|
NULL)) {
|
|
gst_object_replace ((GstObject **) & element, tmp->data);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
REPORT_UNLESS (GES_IS_TRACK_ELEMENT (element), err,
|
|
"Could not find TrackElement from %" GST_PTR_FORMAT, structure);
|
|
|
|
return element;
|
|
|
|
err:
|
|
g_clear_object (&element);
|
|
return element;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GstTimedValueControlSource *source;
|
|
GstStructure *structure;
|
|
GError *error;
|
|
const gchar *property_name;
|
|
gboolean res;
|
|
} SetKeyframesData;
|
|
|
|
|
|
typedef struct
|
|
{
|
|
gboolean ok;
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
gdouble v;
|
|
} Ok;
|
|
|
|
struct
|
|
{
|
|
const gchar *err;
|
|
} Err;
|
|
};
|
|
} ValueToDoubleRes;
|
|
|
|
static ValueToDoubleRes
|
|
value_to_double (const GValue * v)
|
|
{
|
|
GValue v2 = G_VALUE_INIT;
|
|
ValueToDoubleRes res = { 0, };
|
|
|
|
if (G_VALUE_HOLDS_STRING (v)) {
|
|
errno = 0;
|
|
res.Ok.v = g_ascii_strtod (g_value_get_string (v), NULL);
|
|
|
|
if (errno)
|
|
res.Err.err = g_strerror (errno);
|
|
else
|
|
res.ok = TRUE;
|
|
|
|
return res;
|
|
}
|
|
|
|
g_value_init (&v2, G_TYPE_DOUBLE);
|
|
res.ok = g_value_transform (v, &v2);
|
|
if (res.ok) {
|
|
res.Ok.v = g_value_get_double (&v2);
|
|
} else {
|
|
res.Err.err = "unsupported conversion";
|
|
}
|
|
g_value_reset (&v2);
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
un_set_keyframes_foreach (GQuark field_id, const GValue * value,
|
|
SetKeyframesData * d)
|
|
{
|
|
GError **error = &d->error;
|
|
gchar *tmp;
|
|
gint i;
|
|
const gchar *valid_fields[] = {
|
|
"element-name", "property-name", "value", "timestamp", "project-uri",
|
|
"binding-type", "source-type", "interpolation-mode", "interpolation-mode",
|
|
NULL
|
|
};
|
|
const gchar *field = g_quark_to_string (field_id);
|
|
gdouble ts;
|
|
|
|
for (i = 0; valid_fields[i]; i++) {
|
|
if (g_quark_from_string (valid_fields[i]) == field_id)
|
|
return TRUE;
|
|
}
|
|
|
|
errno = 0;
|
|
ts = g_strtod (field, &tmp);
|
|
|
|
REPORT_UNLESS (errno == 0 && field != tmp, err,
|
|
"Could not convert `%s` to GstClockTime (%s)", field, g_strerror (errno));
|
|
|
|
if (gst_structure_has_name (d->structure, "remove-keyframe")) {
|
|
REPORT_UNLESS (gst_timed_value_control_source_unset (d->source,
|
|
ts * GST_SECOND), err, "Could not unset keyframe at %f", ts);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
ValueToDoubleRes res = value_to_double (value);
|
|
REPORT_UNLESS (res.ok, err,
|
|
"Could not convert keyframe %f value (%s)%s to double (%s)", ts,
|
|
G_VALUE_TYPE_NAME (value), gst_value_serialize (value), res.Err.err);
|
|
|
|
REPORT_UNLESS (gst_timed_value_control_source_set (d->source, ts * GST_SECOND,
|
|
res.Ok.v), err, "Could not set keyframe %f=%f", ts, res.Ok.v);
|
|
|
|
return TRUE;
|
|
|
|
err:
|
|
d->res = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
_ges_add_remove_keyframe_from_struct (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
GESTimelineElement *element = NULL;
|
|
|
|
gboolean absolute;
|
|
gdouble value;
|
|
GstClockTime timestamp;
|
|
GstControlBinding *binding = NULL;
|
|
GstTimedValueControlSource *source = NULL;
|
|
gchar *property_name = NULL;
|
|
|
|
gboolean ret = FALSE;
|
|
gboolean setting_value;
|
|
|
|
const gchar *valid_fields[] =
|
|
{ "element-name", "property-name", "value", "timestamp", "project-uri",
|
|
NULL
|
|
};
|
|
|
|
FieldsError fields_error = { valid_fields, NULL };
|
|
|
|
if (gst_structure_has_field (structure, "value")) {
|
|
if (!_check_fields (structure, fields_error, error))
|
|
return FALSE;
|
|
GET_AND_CHECK ("timestamp", GST_TYPE_CLOCK_TIME, ×tamp, done);
|
|
} else {
|
|
REPORT_UNLESS (!gst_structure_has_field (structure, "timestamp"), done,
|
|
"Doesn't have a `value` field in %" GST_PTR_FORMAT
|
|
" but has a `timestamp`" " that can't work!", structure);
|
|
}
|
|
|
|
GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, done);
|
|
if (!(element =
|
|
find_element_for_property (timeline, structure, &property_name, TRUE,
|
|
error)))
|
|
goto done;
|
|
|
|
REPORT_UNLESS (binding =
|
|
ges_track_element_get_control_binding (GES_TRACK_ELEMENT (element),
|
|
property_name),
|
|
done, "No control binding found for %" GST_PTR_FORMAT, structure);
|
|
|
|
g_object_get (binding, "control-source", &source, NULL);
|
|
REPORT_UNLESS (source, done,
|
|
"No control source found for '%" GST_PTR_FORMAT
|
|
"' you should first set-control-binding on it", structure);
|
|
REPORT_UNLESS (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source), done,
|
|
"You can use add-keyframe"
|
|
" only on GstTimedValueControlSource not %s",
|
|
G_OBJECT_TYPE_NAME (source));
|
|
|
|
if (!gst_structure_has_field (structure, "value")) {
|
|
SetKeyframesData d = {
|
|
source, structure, NULL, property_name, TRUE,
|
|
};
|
|
gst_structure_foreach (structure,
|
|
(GstStructureForeachFunc) un_set_keyframes_foreach, &d);
|
|
if (!d.res)
|
|
g_propagate_error (error, d.error);
|
|
|
|
return d.res;
|
|
}
|
|
|
|
g_object_get (binding, "absolute", &absolute, NULL);
|
|
if (absolute) {
|
|
GParamSpec *pspec;
|
|
const GValue *v;
|
|
|
|
if (!ges_timeline_element_lookup_child (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;
|
|
}
|
|
|
|
ValueToDoubleRes res = value_to_double (v);
|
|
if (!res.ok) {
|
|
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 = res.Ok.v;
|
|
} else {
|
|
GET_AND_CHECK ("value", G_TYPE_DOUBLE, &value, done);
|
|
}
|
|
|
|
setting_value =
|
|
!g_strcmp0 (gst_structure_get_name (structure), "add-keyframe");
|
|
if (setting_value)
|
|
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 %sset value for timestamp: %" GST_TIME_FORMAT,
|
|
setting_value ? "" : "un", GST_TIME_ARGS (timestamp));
|
|
}
|
|
ret = _ges_save_timeline_if_needed (timeline, structure, error);
|
|
|
|
done:
|
|
gst_clear_object (&source);
|
|
gst_clear_object (&element);
|
|
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 = NULL;
|
|
GESLayer *layer = NULL;
|
|
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;
|
|
|
|
GESFrameNumber start_frame = GES_FRAME_NUMBER_NONE, inpoint_frame =
|
|
GES_FRAME_NUMBER_NONE, duration_frame = GES_FRAME_NUMBER_NONE;
|
|
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_TIME ("start", &start, &start_frame, GST_CLOCK_TIME_NONE);
|
|
TRY_GET_TIME ("inpoint", &inpoint, &inpoint_frame, 0);
|
|
TRY_GET_TIME ("duration", &duration, &duration_frame, 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_FRAME_NUMBER_IS_VALID (start_frame))
|
|
start = ges_timeline_get_frame_time (timeline, start_frame);
|
|
|
|
if (GES_FRAME_NUMBER_IS_VALID (inpoint_frame)) {
|
|
inpoint =
|
|
ges_clip_asset_get_frame_time (GES_CLIP_ASSET (asset), inpoint_frame);
|
|
if (!GST_CLOCK_TIME_IS_VALID (inpoint)) {
|
|
*error =
|
|
g_error_new (GES_ERROR, 0, "Could not get inpoint from frame %"
|
|
G_GINT64_FORMAT, inpoint_frame);
|
|
goto beach;
|
|
}
|
|
}
|
|
|
|
if (GES_FRAME_NUMBER_IS_VALID (duration_frame)) {
|
|
duration = ges_timeline_get_frame_time (timeline, duration_frame);
|
|
}
|
|
|
|
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) {
|
|
guint v;
|
|
|
|
REPORT_UNLESS (enum_from_str (GES_VIDEO_TEST_PATTERN_TYPE, pattern, &v),
|
|
beach, "Invalid pattern: %s", pattern);
|
|
ges_test_clip_set_vpattern (GES_TEST_CLIP (clip), v);
|
|
}
|
|
}
|
|
|
|
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);
|
|
res = FALSE;
|
|
goto beach;
|
|
}
|
|
|
|
if (res) {
|
|
g_object_set_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA, clip);
|
|
g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, NULL);
|
|
}
|
|
|
|
res = _ges_save_timeline_if_needed (timeline, structure, error);
|
|
|
|
beach:
|
|
gst_clear_object (&layer);
|
|
gst_clear_object (&asset);
|
|
g_free (asset_id);
|
|
g_free (check_asset_id);
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
_ges_add_track_from_struct (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
const gchar *ttype;
|
|
GESTrack *track;
|
|
GstCaps *caps;
|
|
|
|
const gchar *valid_fields[] = { "type", "restrictions", NULL };
|
|
|
|
FieldsError fields_error = { valid_fields, NULL };
|
|
|
|
if (!_check_fields (structure, fields_error, error))
|
|
return FALSE;
|
|
|
|
ttype = gst_structure_get_string (structure, "type");
|
|
if (!g_strcmp0 (ttype, "video")) {
|
|
track = GES_TRACK (ges_video_track_new ());
|
|
} else if (!g_strcmp0 (ttype, "audio")) {
|
|
track = GES_TRACK (ges_audio_track_new ());
|
|
} else {
|
|
g_set_error (error, GES_ERROR, 0, "Unhandled track type: `%s`", ttype);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (gst_structure_has_field (structure, "restrictions")) {
|
|
GstStructure *restriction_struct;
|
|
gchar *restriction_str;
|
|
|
|
if (gst_structure_get (structure, "restrictions", GST_TYPE_STRUCTURE,
|
|
&restriction_struct, NULL)) {
|
|
caps = gst_caps_new_full (restriction_struct, NULL);
|
|
} else if (gst_structure_get (structure, "restrictions", G_TYPE_STRING,
|
|
&restriction_str, NULL)) {
|
|
caps = gst_caps_from_string (restriction_str);
|
|
|
|
if (!caps) {
|
|
g_set_error (error, GES_ERROR, 0, "Invalid restrictions caps: %s",
|
|
restriction_str);
|
|
|
|
g_free (restriction_str);
|
|
return FALSE;
|
|
}
|
|
g_free (restriction_str);
|
|
} else if (!gst_structure_get (structure, "restrictions", GST_TYPE_CAPS,
|
|
&caps, NULL)) {
|
|
gchar *tmp = gst_structure_to_string (structure);
|
|
|
|
g_set_error (error, GES_ERROR, 0, "Can't use restrictions caps from %s",
|
|
tmp);
|
|
|
|
g_object_unref (track);
|
|
return FALSE;
|
|
}
|
|
|
|
ges_track_set_restriction_caps (track, caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
return ges_timeline_add_track (timeline, track);
|
|
}
|
|
|
|
gboolean
|
|
_ges_container_add_child_from_struct (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
GESAsset *asset = NULL;
|
|
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", "inpoint",
|
|
"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);
|
|
|
|
if (gst_structure_has_field (structure, "inpoint")) {
|
|
GstClockTime inpoint;
|
|
GESFrameNumber finpoint;
|
|
|
|
if (!GES_IS_TRACK_ELEMENT (child)) {
|
|
*error = g_error_new (GES_ERROR, 0, "Child %s is not a trackelement"
|
|
", can't set inpoint.", child_name);
|
|
|
|
gst_object_unref (child);
|
|
|
|
goto beach;
|
|
}
|
|
|
|
if (!ges_util_structure_get_clocktime (structure, "inpoint", &inpoint,
|
|
&finpoint)) {
|
|
*error = g_error_new (GES_ERROR, 0, "Could not use inpoint.");
|
|
gst_object_unref (child);
|
|
|
|
goto beach;
|
|
}
|
|
|
|
if (!ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child),
|
|
TRUE)) {
|
|
*error =
|
|
g_error_new (GES_ERROR, 0,
|
|
"Could not set inpoint as %s can't have an internal source",
|
|
child_name);
|
|
gst_object_unref (child);
|
|
|
|
goto beach;
|
|
}
|
|
|
|
if (GES_FRAME_NUMBER_IS_VALID (finpoint))
|
|
inpoint = ges_timeline_get_frame_time (timeline, finpoint);
|
|
|
|
ges_timeline_element_set_inpoint (child, inpoint);
|
|
|
|
}
|
|
|
|
if (GES_IS_CLIP (container) && GES_IS_BASE_EFFECT (child)) {
|
|
GList *effects = ges_clip_get_top_effects (GES_CLIP (container));
|
|
|
|
res =
|
|
ges_clip_add_top_effect (GES_CLIP (container), GES_BASE_EFFECT (child),
|
|
0, error);
|
|
|
|
g_list_free_full (effects, gst_object_unref);
|
|
} else {
|
|
res = ges_container_add (container, child);
|
|
}
|
|
|
|
if (res == FALSE) {
|
|
if (!*error)
|
|
*error = g_error_new (GES_ERROR, 0, "Could not add child to container");
|
|
|
|
goto beach;
|
|
} else {
|
|
g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, child);
|
|
}
|
|
res = _ges_save_timeline_if_needed (timeline, structure, error);
|
|
|
|
beach:
|
|
gst_clear_object (&asset);
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
_ges_set_child_property_from_struct (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
const GValue *value;
|
|
GValue prop_value = G_VALUE_INIT;
|
|
gboolean prop_value_set = FALSE;
|
|
gchar *property_name;
|
|
GESTimelineElement *element;
|
|
gchar *serialized;
|
|
gboolean res;
|
|
|
|
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;
|
|
|
|
GET_AND_CHECK ("property", G_TYPE_STRING, &property_name, err);
|
|
|
|
if (!(element =
|
|
find_element_for_property (timeline, structure, &property_name, FALSE,
|
|
error)))
|
|
goto err;
|
|
|
|
value = gst_structure_get_value (structure, "value");
|
|
|
|
if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
|
|
GParamSpec *pspec;
|
|
if (ges_timeline_element_lookup_child (element, property_name, NULL,
|
|
&pspec)) {
|
|
GType p_type = pspec->value_type;
|
|
g_param_spec_unref (pspec);
|
|
if (p_type != G_TYPE_STRING) {
|
|
const gchar *val_string = g_value_get_string (value);
|
|
g_value_init (&prop_value, p_type);
|
|
if (!gst_value_deserialize (&prop_value, val_string)) {
|
|
*error = g_error_new (GES_ERROR, 0, "Could not set the property %s "
|
|
"because the value %s could not be deserialized to the %s type",
|
|
property_name, val_string, g_type_name (p_type));
|
|
return FALSE;
|
|
}
|
|
prop_value_set = TRUE;
|
|
}
|
|
}
|
|
/* else, let the setter fail below */
|
|
}
|
|
|
|
if (!prop_value_set) {
|
|
g_value_init (&prop_value, G_VALUE_TYPE (value));
|
|
g_value_copy (value, &prop_value);
|
|
}
|
|
|
|
serialized = gst_value_serialize (&prop_value);
|
|
GST_INFO_OBJECT (element, "Setting property %s to %s\n", property_name,
|
|
serialized);
|
|
g_free (serialized);
|
|
|
|
res = ges_timeline_element_set_child_property (element, property_name,
|
|
&prop_value);
|
|
g_value_unset (&prop_value);
|
|
if (!res) {
|
|
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;
|
|
}
|
|
g_free (property_name);
|
|
return _ges_save_timeline_if_needed (timeline, structure, error);
|
|
|
|
err:
|
|
g_free (property_name);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
_ges_set_control_source_from_struct (GESTimeline * timeline,
|
|
GstStructure * structure, GError ** error)
|
|
{
|
|
guint mode;
|
|
gboolean res = FALSE;
|
|
GESTimelineElement *element = NULL;
|
|
|
|
GstControlSource *source = NULL;
|
|
gchar *property_name, *binding_type = NULL,
|
|
*source_type = NULL, *interpolation_mode = NULL;
|
|
|
|
GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, beach);
|
|
|
|
if (!(element =
|
|
find_element_for_property (timeline, structure, &property_name, TRUE,
|
|
error)))
|
|
goto beach;
|
|
|
|
if (GES_IS_CLIP (element)) {
|
|
GList *tmp;
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
|
|
if (ges_timeline_element_lookup_child (tmp->data, property_name, NULL,
|
|
NULL)) {
|
|
gst_object_replace ((GstObject **) & element, tmp->data);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
REPORT_UNLESS (GES_IS_TRACK_ELEMENT (element), beach,
|
|
"Could not find TrackElement from %" GST_PTR_FORMAT, structure);
|
|
|
|
TRY_GET ("binding-type", G_TYPE_STRING, &binding_type, NULL);
|
|
TRY_GET ("source-type", G_TYPE_STRING, &source_type, NULL);
|
|
TRY_GET ("interpolation-mode", G_TYPE_STRING, &interpolation_mode, NULL);
|
|
|
|
if (!binding_type)
|
|
binding_type = g_strdup ("direct");
|
|
|
|
REPORT_UNLESS (source_type == NULL
|
|
|| !g_strcmp0 (source_type, "interpolation"), beach,
|
|
"Interpolation type %s not supported", source_type);
|
|
source = gst_interpolation_control_source_new ();
|
|
|
|
if (interpolation_mode)
|
|
REPORT_UNLESS (enum_from_str (GST_TYPE_INTERPOLATION_MODE,
|
|
interpolation_mode, &mode), beach,
|
|
"Wrong interpolation mode: %s", interpolation_mode);
|
|
else
|
|
mode = GST_INTERPOLATION_MODE_LINEAR;
|
|
|
|
g_object_set (source, "mode", mode, NULL);
|
|
|
|
res = ges_track_element_set_control_source (GES_TRACK_ELEMENT (element),
|
|
source, property_name, binding_type);
|
|
|
|
beach:
|
|
gst_clear_object (&element);
|
|
gst_clear_object (&source);
|
|
g_free (property_name);
|
|
g_free (binding_type);
|
|
g_free (source_type);
|
|
g_free (interpolation_mode);
|
|
|
|
return res;
|
|
}
|
|
|
|
#undef GET_AND_CHECK
|
|
#undef TRY_GET
|