/* GStreamer Editing Services * * Copyright (C) <2015> Thibault Saunier * * 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 #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 (const GstIdStr * fieldname, const GValue * value, FieldsError * fields_error) { const gchar *field = gst_id_str_as_str (fieldname); if (g_strv_contains (fields_error->fields, gst_id_str_as_str (fieldname))) return TRUE; // The pointers to the field names are only valid for as long as the structure // is, but they're only used in the direct caller while the structure lives // longer. 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_id_str (structure, (GstStructureForeachIdStrFunc) _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_list_free (fields_error.invalid_fields); g_string_free (msg, TRUE); fields_error.invalid_fields = NULL; 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 (const GstIdStr * fieldname, const GValue * value, SetKeyframesData * d) { GError **error = &d->error; gchar *tmp; const gchar *valid_fields[] = { "element-name", "property-name", "value", "timestamp", "project-uri", "binding-type", "source-type", "interpolation-mode", "interpolation-mode", NULL }; const gchar *field = gst_id_str_as_str (fieldname); gdouble ts; if (g_strv_contains (valid_fields, gst_id_str_as_str (fieldname))) 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_id_str (structure, (GstStructureForeachIdStrFunc) 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