/* gst-editing-services * Copyright (C) <2013> Thibault Saunier * <2013> Collabora Ltd. * * gst-editing-services is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * gst-editing-services 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * SECTION:gestimelineelement * @title: GESTimelineElement * @short_description: Base Class for all elements with some temporal extent * within a #GESTimeline. * * A #GESTimelineElement will have some temporal extent in its * corresponding #GESTimelineElement:timeline, controlled by its * #GESTimelineElement:start and #GESTimelineElement:duration. This * determines when its content will be displayed, or its effect applied, * in the timeline. Several objects may overlap within a given * #GESTimeline, in which case their #GESTimelineElement:priority is used * to determine their ordering in the timeline. Priority is mostly handled * internally by #GESLayer-s and #GESClip-s. * * A timeline element can have a #GESTimelineElement:parent, * such as a #GESClip, which is responsible for controlling its timing. * * ## Editing * * Elements can be moved around in their #GESTimelineElement:timeline by * setting their #GESTimelineElement:start and * #GESTimelineElement:duration using ges_timeline_element_set_start() * and ges_timeline_element_set_duration(). Additionally, which parts of * the underlying content are played in the timeline can be adjusted by * setting the #GESTimelineElement:in-point using * ges_timeline_element_set_inpoint(). The library also provides * ges_timeline_element_edit(), with various #GESEditMode-s, which can * adjust these properties in a convenient way, as well as introduce * similar changes in neighbouring or later elements in the timeline. * * However, a timeline may refuse a change in these properties if they * would place the timeline in an unsupported configuration. For example, * it is not possible for three #GESSourceClip-s in the same layer and * with the same track types to overlap at any given position in the * timeline (only two may overlap, which corresponds to a single * #GESTransition). Similarly, a #GESSourceClip may not entirely cover * another #GESSourceClip in the same layer and with the same track types. * Additionally, an edit may be refused if it would place one of the * timing properties out of bounds (such as a negative time value for * #GESTimelineElement:start, or having insufficient internal * content to last for the desired #GESTimelineElement:duration). * * ## Children Properties * * If a timeline element owns another #GstObject and wishes to expose * some of its properties, it can do so by registering the property as one * of the timeline element's children properties using * ges_timeline_element_add_child_property(). The registered property of * the child can then be read and set using the * ges_timeline_element_get_child_property() and * ges_timeline_element_set_child_property() methods, respectively. Some * sub-classed objects will be created with pre-registered children * properties; for example, to expose part of an underlying #GstElement * that is used internally. The registered properties can be listed with * ges_timeline_element_list_children_properties(). */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ges-utils.h" #include "ges-timeline-element.h" #include "ges-extractable.h" #include "ges-meta-container.h" #include "ges-internal.h" #include "ges-effect.h" #include #include /* maps type name quark => count */ static GData *object_name_counts = NULL; static void extractable_set_asset (GESExtractable * extractable, GESAsset * asset) { GES_TIMELINE_ELEMENT (extractable)->asset = asset; } static void ges_extractable_interface_init (GESExtractableInterface * iface) { iface->set_asset = extractable_set_asset; } enum { PROP_0, PROP_PARENT, PROP_TIMELINE, PROP_START, PROP_INPOINT, PROP_DURATION, PROP_MAX_DURATION, PROP_PRIORITY, PROP_NAME, PROP_SERIALIZE, PROP_LAST }; enum { DEEP_NOTIFY, LAST_SIGNAL }; static guint ges_timeline_element_signals[LAST_SIGNAL] = { 0 }; static GParamSpec *properties[PROP_LAST] = { NULL, }; typedef struct { GObject *child; GESTimelineElement *owner; gulong handler_id; } ChildPropHandler; struct _GESTimelineElementPrivate { gboolean serialize; /* We keep a link between properties name and elements internally * The hashtable should look like * {GParamaSpec ---> child}*/ GHashTable *children_props; GESTimelineElement *copied_from; GESTimelineElementFlags flags; }; typedef struct { GObject *child; GParamSpec *arg; GESTimelineElement *self; } EmitDeepNotifyInIdleData; G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTimelineElement, ges_timeline_element, G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESTimelineElement) G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init) G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL)); /********************************************* * Virtual methods implementation * *********************************************/ static void _set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child, GParamSpec * pspec, GValue * value) { if (G_VALUE_TYPE (value) != pspec->value_type && G_VALUE_TYPE (value) == G_TYPE_STRING) gst_util_set_object_arg (child, pspec->name, g_value_get_string (value)); else g_object_set_property (child, pspec->name, value); } static gboolean _lookup_child (GESTimelineElement * self, const gchar * prop_name, GObject ** child, GParamSpec ** pspec) { GHashTableIter iter; gpointer key, value; gchar **names, *name, *classename; gboolean res; classename = NULL; res = FALSE; names = g_strsplit (prop_name, "::", 2); if (names[1] != NULL) { classename = names[0]; name = names[1]; } else name = names[0]; g_hash_table_iter_init (&iter, self->priv->children_props); while (g_hash_table_iter_next (&iter, &key, &value)) { if (g_strcmp0 (G_PARAM_SPEC (key)->name, name) == 0) { ChildPropHandler *handler = (ChildPropHandler *) value; if (classename == NULL || g_strcmp0 (G_OBJECT_TYPE_NAME (G_OBJECT (handler->child)), classename) == 0 || g_strcmp0 (g_type_name (G_PARAM_SPEC (key)->owner_type), classename) == 0) { GST_DEBUG_OBJECT (self, "The %s property from %s has been found", name, classename); if (child) *child = gst_object_ref (handler->child); if (pspec) *pspec = g_param_spec_ref (key); res = TRUE; break; } } } g_strfreev (names); return res; } static GParamSpec ** default_list_children_properties (GESTimelineElement * self, guint * n_properties) { GParamSpec **pspec, *spec; GHashTableIter iter; gpointer key, value; guint i = 0; *n_properties = g_hash_table_size (self->priv->children_props); pspec = g_new (GParamSpec *, *n_properties); g_hash_table_iter_init (&iter, self->priv->children_props); while (g_hash_table_iter_next (&iter, &key, &value)) { spec = G_PARAM_SPEC (key); pspec[i] = g_param_spec_ref (spec); i++; } return pspec; } static void _get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); switch (property_id) { case PROP_PARENT: g_value_take_object (value, self->parent); break; case PROP_TIMELINE: g_value_take_object (value, self->timeline); break; case PROP_START: g_value_set_uint64 (value, self->start); break; case PROP_INPOINT: g_value_set_uint64 (value, self->inpoint); break; case PROP_DURATION: g_value_set_uint64 (value, self->duration); break; case PROP_MAX_DURATION: g_value_set_uint64 (value, self->maxduration); break; case PROP_PRIORITY: g_value_set_uint (value, self->priority); break; case PROP_NAME: g_value_take_string (value, ges_timeline_element_get_name (self)); break; case PROP_SERIALIZE: g_value_set_boolean (value, self->priv->serialize); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); } } static void _set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); switch (property_id) { case PROP_PARENT: ges_timeline_element_set_parent (self, g_value_get_object (value)); break; case PROP_TIMELINE: ges_timeline_element_set_timeline (self, g_value_get_object (value)); break; case PROP_START: ges_timeline_element_set_start (self, g_value_get_uint64 (value)); break; case PROP_INPOINT: ges_timeline_element_set_inpoint (self, g_value_get_uint64 (value)); break; case PROP_DURATION: ges_timeline_element_set_duration (self, g_value_get_uint64 (value)); break; case PROP_PRIORITY: ges_timeline_element_set_priority (self, g_value_get_uint (value)); break; case PROP_MAX_DURATION: ges_timeline_element_set_max_duration (self, g_value_get_uint64 (value)); break; case PROP_NAME: ges_timeline_element_set_name (self, g_value_get_string (value)); break; case PROP_SERIALIZE: self->priv->serialize = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); } } static void ges_timeline_element_dispose (GObject * object) { GESTimelineElement *self = GES_TIMELINE_ELEMENT (object); if (self->priv->children_props) { g_hash_table_unref (self->priv->children_props); self->priv->children_props = NULL; } g_clear_object (&self->priv->copied_from); G_OBJECT_CLASS (ges_timeline_element_parent_class)->dispose (object); } static void ges_timeline_element_finalize (GObject * self) { GESTimelineElement *tle = GES_TIMELINE_ELEMENT (self); g_free (tle->name); G_OBJECT_CLASS (ges_timeline_element_parent_class)->finalize (self); } static void _child_prop_handler_free (ChildPropHandler * handler) { g_object_freeze_notify (handler->child); if (handler->handler_id) g_signal_handler_disconnect (handler->child, handler->handler_id); g_object_thaw_notify (handler->child); gst_object_unref (handler->child); g_slice_free (ChildPropHandler, handler); } static void ges_timeline_element_init (GESTimelineElement * self) { self->priv = ges_timeline_element_get_instance_private (self); self->priv->serialize = TRUE; self->priv->children_props = g_hash_table_new_full ((GHashFunc) ges_pspec_hash, ges_pspec_equal, (GDestroyNotify) g_param_spec_unref, (GDestroyNotify) _child_prop_handler_free); } static void ges_timeline_element_class_init (GESTimelineElementClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = _get_property; object_class->set_property = _set_property; /** * GESTimelineElement:parent: * * The parent container of the element. */ properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent", "The parent container of the object", GES_TYPE_TIMELINE_ELEMENT, G_PARAM_READWRITE); /** * GESTimelineElement:timeline: * * The timeline that the element lies within. */ properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline", "The timeline the object is in", GES_TYPE_TIMELINE, G_PARAM_READWRITE); /** * GESTimelineElement:start: * * The starting position of the element in the timeline (in nanoseconds * and in the time coordinates of the timeline). For example, for a * source element, this would determine the time at which it should * start outputting its internal content. For an operation element, this * would determine the time at which it should start applying its effect * to any source content. */ properties[PROP_START] = g_param_spec_uint64 ("start", "Start", "The position in the timeline", 0, G_MAXUINT64, 0, G_PARAM_READWRITE); /** * GESTimelineElement:in-point: * * The initial offset to use internally when outputting content (in * nanoseconds, but in the time coordinates of the internal content). * * For example, for a #GESVideoUriSource that references some media * file, the "internal content" is the media file data, and the * in-point would correspond to some timestamp in the media file. * When playing the timeline, and when the element is first reached at * timeline-time #GESTimelineElement:start, it will begin outputting the * data from the timestamp in-point **onwards**, until it reaches the * end of its #GESTimelineElement:duration in the timeline. * * For elements that have no internal content, this should be kept * as 0. */ properties[PROP_INPOINT] = g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0, G_MAXUINT64, 0, G_PARAM_READWRITE); /** * GESTimelineElement:duration: * * The duration that the element is in effect for in the timeline (a * time difference in nanoseconds using the time coordinates of the * timeline). For example, for a source element, this would determine * for how long it should output its internal content for. For an * operation element, this would determine for how long its effect * should be applied to any source content. */ properties[PROP_DURATION] = g_param_spec_uint64 ("duration", "Duration", "The play duration", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE); /** * GESTimelineElement:max-duration: * * The full duration of internal content that is available (a time * difference in nanoseconds using the time coordinates of the internal * content). * * For example, for a #GESVideoUriSource that references some media * file, this would be the length of the media file. * * For elements that have no internal content, or whose content is * indefinite, this should be kept as #GST_CLOCK_TIME_NONE. */ properties[PROP_MAX_DURATION] = g_param_spec_uint64 ("max-duration", "Maximum duration", "The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GESTimelineElement:priority: * * The priority of the element. * * Deprecated: 1.10: Priority management is now done by GES itself. */ properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE); /** * GESTimelineElement:name: * * The name of the element. This should be unique within its timeline. */ properties[PROP_NAME] = g_param_spec_string ("name", "Name", "The name of the timeline object", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); /** * GESTimelineElement:serialize: * * Whether the element should be serialized. */ properties[PROP_SERIALIZE] = g_param_spec_boolean ("serialize", "Serialize", "Whether the element should be serialized", TRUE, G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION); g_object_class_install_properties (object_class, PROP_LAST, properties); /** * GESTimelineElement::deep-notify: * @timeline_element: A #GESTtimelineElement * @prop_object: The object that originated the signal * @prop: The specification for the property that changed * * Emitted when a child of @timeline_element has one of its registered * properties set. See ges_timeline_element_add_child_property(). * Note that unlike #GObject::notify, a child property name can not be * used as a signal detail. */ ges_timeline_element_signals[DEEP_NOTIFY] = g_signal_new ("deep-notify", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM); object_class->dispose = ges_timeline_element_dispose; object_class->finalize = ges_timeline_element_finalize; klass->set_parent = NULL; klass->set_start = NULL; klass->set_inpoint = NULL; klass->set_duration = NULL; klass->set_max_duration = NULL; klass->set_priority = NULL; klass->ripple = NULL; klass->ripple_end = NULL; klass->roll_start = NULL; klass->roll_end = NULL; klass->trim = NULL; klass->list_children_properties = default_list_children_properties; klass->lookup_child = _lookup_child; klass->set_child_property = _set_child_property; } static void _set_name (GESTimelineElement * self, const gchar * wanted_name) { const gchar *type_name; gchar *lowcase_type; gint count; GQuark q; guint i, l; gchar *name = NULL; if (!object_name_counts) { g_datalist_init (&object_name_counts); } q = g_type_qname (G_OBJECT_TYPE (self)); count = GPOINTER_TO_INT (g_datalist_id_get_data (&object_name_counts, q)); /* GstFooSink -> foosink */ type_name = g_quark_to_string (q); if (strncmp (type_name, "GES", 3) == 0) type_name += 3; lowcase_type = g_strdup (type_name); l = strlen (lowcase_type); for (i = 0; i < l; i++) lowcase_type[i] = g_ascii_tolower (lowcase_type[i]); if (wanted_name == NULL) { /* give the 20th "uriclip" element and the first "uriclip2" (if needed in the future) * different names */ l = strlen (type_name); if (l > 0 && g_ascii_isdigit (type_name[l - 1])) { name = g_strdup_printf ("%s-%d", lowcase_type, count++); } else { name = g_strdup_printf ("%s%d", lowcase_type, count++); } } else { /* If the wanted name uses the same 'namespace' as default, make * sure it does not badly interfere with our counting system */ /* FIXME: should we really be allowing a user to set the name * "uriclip1" for, say, a GESTransition? The below code *does not* * capture this case (because the prefix does not match "transition"). * If the user subsequently calls _set_name with name == NULL, on a * GESClip *for the first time*, then the GES library will * automatically choose the *same* name "uriclip1", but this is not * unique! */ if (g_str_has_prefix (wanted_name, lowcase_type)) { guint64 tmpcount = g_ascii_strtoull (&wanted_name[strlen (lowcase_type)], NULL, 10); if (tmpcount > count) { count = tmpcount + 1; GST_DEBUG_OBJECT (self, "Using same naming %s but updated count to %i", wanted_name, count); } else if (tmpcount < count) { /* FIXME: this can unexpectedly change names given by the user * E.g. if "transition2" already exists, and a user then wants to * set a GESTransition to have the name "transition-custom" or * "transition 1 too many" then tmpcount would in fact be 0 or 1, * and the name would then be changed to "transition3"! */ name = g_strdup_printf ("%s%d", lowcase_type, count); count++; GST_DEBUG_OBJECT (self, "Name %s already allocated, giving: %s instead" " New count is %i", wanted_name, name, count); } else { count++; GST_DEBUG_OBJECT (self, "Perfect name, just bumping object count"); } } if (name == NULL) name = g_strdup (wanted_name); } g_free (lowcase_type); g_datalist_id_set_data (&object_name_counts, q, GINT_TO_POINTER (count)); g_free (self->name); self->name = name; } /********************************************* * Internal and private helpers * *********************************************/ gdouble ges_timeline_element_get_media_duration_factor (GESTimelineElement * self) { gdouble media_duration_factor; GESEffectClass *class; GList *props; media_duration_factor = 1.0; class = GES_EFFECT_CLASS (g_type_class_ref (GES_TYPE_EFFECT)); for (props = class->rate_properties; props != NULL; props = props->next) { GObject *child; GParamSpec *pspec; if (ges_timeline_element_lookup_child (self, props->data, &child, &pspec)) { if (G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_FLOAT) { gfloat rate_change; g_object_get (child, pspec->name, &rate_change, NULL); media_duration_factor *= rate_change; } else if (G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_DOUBLE) { gdouble rate_change; g_object_get (child, pspec->name, &rate_change, NULL); media_duration_factor *= rate_change; } else { GST_WARNING_OBJECT (self, "Rate property %s in child %" GST_PTR_FORMAT " is of unsupported type %s", pspec->name, child, G_VALUE_TYPE_NAME (pspec->value_type)); } gst_object_unref (child); g_param_spec_unref (pspec); GST_DEBUG_OBJECT (self, "Added rate changing property %s, set to value %lf", (const char *) props->data, media_duration_factor); } } g_type_class_unref (class); return media_duration_factor; } GESTimelineElement * ges_timeline_element_get_copied_from (GESTimelineElement * self) { GESTimelineElement *copied_from = self->priv->copied_from; self->priv->copied_from = NULL; return copied_from; } GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement * self) { return self->priv->flags; } void ges_timeline_element_set_flags (GESTimelineElement * self, GESTimelineElementFlags flags) { self->priv->flags = flags; } static gboolean emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data) { g_signal_emit (data->self, ges_timeline_element_signals[DEEP_NOTIFY], 0, data->child, data->arg); gst_object_unref (data->child); g_param_spec_unref (data->arg); gst_object_unref (data->self); g_slice_free (EmitDeepNotifyInIdleData, data); return FALSE; } static void child_prop_changed_cb (GObject * child, GParamSpec * arg G_GNUC_UNUSED, GESTimelineElement * self) { EmitDeepNotifyInIdleData *data; /* Emit "deep-notify" right away if in main thread */ if (g_main_context_acquire (g_main_context_default ())) { g_main_context_release (g_main_context_default ()); g_signal_emit (self, ges_timeline_element_signals[DEEP_NOTIFY], 0, child, arg); return; } data = g_slice_new (EmitDeepNotifyInIdleData); data->child = gst_object_ref (child); data->arg = g_param_spec_ref (arg); data->self = gst_object_ref (self); ges_idle_add ((GSourceFunc) emit_deep_notify_in_idle, data, NULL); } static gboolean set_child_property_by_pspec (GESTimelineElement * self, GParamSpec * pspec, const GValue * value) { GESTimelineElementClass *klass; GESTimelineElement *setter = self; ChildPropHandler *handler = g_hash_table_lookup (self->priv->children_props, pspec); if (!handler) { GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name); return FALSE; } if (handler->owner) { klass = GES_TIMELINE_ELEMENT_GET_CLASS (handler->owner); setter = handler->owner; } else { klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); } g_assert (klass->set_child_property); klass->set_child_property (setter, handler->child, pspec, (GValue *) value); return TRUE; } gboolean ges_timeline_element_add_child_property_full (GESTimelineElement * self, GESTimelineElement * owner, GParamSpec * pspec, GObject * child) { gchar *signame; ChildPropHandler *handler; if (g_hash_table_contains (self->priv->children_props, pspec)) { GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name); return FALSE; } GST_DEBUG_OBJECT (self, "Adding child property: %" GST_PTR_FORMAT "::%s", child, pspec->name); signame = g_strconcat ("notify::", pspec->name, NULL); handler = (ChildPropHandler *) g_slice_new0 (ChildPropHandler); handler->child = gst_object_ref (child); handler->owner = owner; handler->handler_id = g_signal_connect (child, signame, G_CALLBACK (child_prop_changed_cb), self); g_hash_table_insert (self->priv->children_props, g_param_spec_ref (pspec), handler); g_free (signame); return TRUE; } /********************************************* * API implementation * *********************************************/ /** * ges_timeline_element_set_parent: * @self: A #GESTimelineElement * @parent (nullable): New parent of @self * * Sets the #GESTimelineElement:parent for the element. * * This is used internally and you should normally not call this. A * #GESContainer will set the #GESTimelineElement:parent of its children * in ges_container_add() and ges_container_remove(). * * Note, if @parent is not %NULL, @self must not already have a parent * set. Therefore, if you wish to switch parents, you will need to call * this function twice: first to set the parent to %NULL, and then to the * new parent. * * If @parent is not %NULL, you must ensure it already has a * (non-floating) reference to @self before calling this. * * Returns: %TRUE if @parent could be set for @self. */ gboolean ges_timeline_element_set_parent (GESTimelineElement * self, GESTimelineElement * parent) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); g_return_val_if_fail (parent == NULL || GES_IS_TIMELINE_ELEMENT (parent), FALSE); if (self == parent) { GST_INFO_OBJECT (self, "Trying to add %p in itself, not a good idea!", self); /* FIXME: why are we sinking and then unreffing self when we do not * own it? */ gst_object_ref_sink (self); gst_object_unref (self); return FALSE; } GST_DEBUG_OBJECT (self, "set parent to %" GST_PTR_FORMAT, parent); if (self->parent != NULL && parent != NULL) goto had_parent; if (GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_parent) { if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_parent (self, parent)) return FALSE; } self->parent = parent; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PARENT]); return TRUE; /* ERROR handling */ had_parent: { GST_WARNING_OBJECT (self, "set parent failed, object already had a parent"); /* FIXME: why are we sinking and then unreffing self when we do not * own it? */ gst_object_ref_sink (self); gst_object_unref (self); return FALSE; } } /** * ges_timeline_element_get_parent: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:parent for the element. * * Returns: (transfer full) (nullable): The parent of @self, or %NULL if * @self has no parent. */ GESTimelineElement * ges_timeline_element_get_parent (GESTimelineElement * self) { GESTimelineElement *result = NULL; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); result = self->parent; if (G_LIKELY (result)) gst_object_ref (result); return result; } /** * ges_timeline_element_set_timeline: * @self: A #GESTimelineElement * @timeline (nullable): The #GESTimeline @self should be in * * Sets the #GESTimelineElement:timeline of the element. * * This is used internally and you should normally not call this. A * #GESClip will have its #GESTimelineElement:timeline set through its * #GESLayer. A #GESTrack will similarly take care of setting the * #GESTimelineElement:timeline of its #GESTrackElement-s. A #GESGroup * will adopt the same #GESTimelineElement:timeline as its children. * * If @timeline is %NULL, this will stop its current * #GESTimelineElement:timeline from tracking it, otherwise @timeline will * start tracking @self. Note, in the latter case, @self must not already * have a timeline set. Therefore, if you wish to switch timelines, you * will need to call this function twice: first to set the timeline to * %NULL, and then to the new timeline. * * Returns: %TRUE if @timeline could be set for @self. */ gboolean ges_timeline_element_set_timeline (GESTimelineElement * self, GESTimeline * timeline) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); g_return_val_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline), FALSE); GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline); if (timeline != NULL && G_UNLIKELY (self->timeline != NULL)) goto had_timeline; if (timeline == NULL) { if (self->timeline) { if (!timeline_remove_element (self->timeline, self)) { GST_INFO_OBJECT (self, "Could not remove from" " currently set timeline %" GST_PTR_FORMAT, self->timeline); return FALSE; } } } else { if (!timeline_add_element (timeline, self)) { GST_INFO_OBJECT (self, "Could not add to timeline %" GST_PTR_FORMAT, self); return FALSE; } } self->timeline = timeline; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMELINE]); return TRUE; /* ERROR handling */ had_timeline: { GST_DEBUG_OBJECT (self, "set timeline failed, object already had a " "timeline"); return FALSE; } } /** * ges_timeline_element_get_timeline: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:timeline for the element. * * Returns: (transfer full) (nullable): The timeline of @self, or %NULL * if @self has no timeline. */ GESTimeline * ges_timeline_element_get_timeline (GESTimelineElement * self) { GESTimeline *result = NULL; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); result = self->timeline; if (G_LIKELY (result)) gst_object_ref (result); return result; } /** * ges_timeline_element_set_start: * @self: A #GESTimelineElement * @start: The desired start position of the element in its timeline * * Sets #GESTimelineElement:start for the element. This may fail if it * would place the timeline in an unsupported configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the element's * #GESTimelineElement:start may instead be set to the edge of some other * element in the neighbourhood of @start. In such a case, the return * value will still be %TRUE on success. * * Returns: %TRUE if @start could be set for @self. */ gboolean ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start) { gboolean emit_notify = TRUE; GESTimelineElementClass *klass; GESTimelineElement *toplevel_container, *parent; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); if (self->start == start) return TRUE; GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT " new start: %" GST_TIME_FORMAT, GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)), GST_TIME_ARGS (start)); toplevel_container = ges_timeline_element_get_toplevel_parent (self); if (self->timeline && !ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE) && !ELEMENT_FLAG_IS_SET (toplevel_container, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { if (!ges_timeline_move_object_simple (self->timeline, self, NULL, GES_EDGE_NONE, start)) { gst_object_unref (toplevel_container); return FALSE; } emit_notify = FALSE; } parent = self->parent; /* FIXME This should not belong to GESTimelineElement */ if (toplevel_container && ((gint64) (_START (toplevel_container) + start - _START (self))) < 0 && parent && GES_CONTAINER (parent)->children_control_mode == GES_CHILDREN_UPDATE) { GST_INFO_OBJECT (self, "Can not move the object as it would imply its " "container to have a negative start value"); gst_object_unref (toplevel_container); return FALSE; } gst_object_unref (toplevel_container); klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->set_start) { gint res = klass->set_start (self, start); if (res == TRUE && emit_notify) { self->start = start; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]); } GST_DEBUG_OBJECT (self, "New start: %" GST_TIME_FORMAT, GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self))); return ! !res; } GST_WARNING_OBJECT (self, "No set_start virtual method implementation" " on class %s. Can not set start %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start)); return FALSE; } /** * ges_timeline_element_set_inpoint: * @self: A #GESTimelineElement * @inpoint: The in-point, in internal time coordinates * * Sets #GESTimelineElement:in-point for the element. This may fail if * the element does not have enough internal content to last for the * current #GESTimelineElement:duration after @inpoint. * * Returns: %TRUE if @inpoint could be set for @self. */ gboolean ges_timeline_element_set_inpoint (GESTimelineElement * self, GstClockTime inpoint) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); GST_DEBUG_OBJECT (self, "current inpoint: %" GST_TIME_FORMAT " new inpoint: %" GST_TIME_FORMAT, GST_TIME_ARGS (inpoint), GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self))); klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->set_inpoint) { /* FIXME: Could we instead use g_object_freeze_notify() to prevent * duplicate notify signals? Rather than relying on the return value * being -1 for setting that succeeds but does not want a notify * signal because it will call this method on itself a second time. */ gint res = klass->set_inpoint (self, inpoint); if (res == TRUE) { self->inpoint = inpoint; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPOINT]); } return ! !res; } GST_DEBUG_OBJECT (self, "No set_inpoint virtual method implementation" " on class %s. Can not set inpoint %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (inpoint)); return FALSE; } /** * ges_timeline_element_set_max_duration: * @self: A #GESTimelineElement * @maxduration: The maximum duration, in internal time coordinates * * Sets #GESTimelineElement:max-duration for the element. * * Returns: %TRUE if @maxduration could be set for @self. */ gboolean ges_timeline_element_set_max_duration (GESTimelineElement * self, GstClockTime maxduration) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT " new duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (GES_TIMELINE_ELEMENT_MAX_DURATION (self)), GST_TIME_ARGS (maxduration)); if (klass->set_max_duration) { if (klass->set_max_duration (self, maxduration) == FALSE) return FALSE; } self->maxduration = maxduration; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_DURATION]); return TRUE; } /** * ges_timeline_element_set_duration: * @self: A #GESTimelineElement * @duration: The desired duration in its timeline * * Sets #GESTimelineElement:duration for the element. This may fail if it * would place the timeline in an unsupported configuration, or if the * element does not have enough internal content to last for the desired * @duration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the element's * #GESTimelineElement:duration may instead be adjusted around @duration * such that the edge of @self matches the edge of some other element in * the neighbourhood. In such a case, the return value will still be %TRUE * on success. * * Returns: %TRUE if @duration could be set for @self. */ gboolean ges_timeline_element_set_duration (GESTimelineElement * self, GstClockTime duration) { GESTimelineElementClass *klass; gboolean emit_notify = TRUE; GESTimelineElement *toplevel; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); toplevel = ges_timeline_element_get_toplevel_parent (self); if (self->timeline && !ELEMENT_FLAG_IS_SET (self, GES_TIMELINE_ELEMENT_SET_SIMPLE) && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) { gboolean res; res = timeline_trim_object (self->timeline, self, GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self), NULL, GES_EDGE_END, self->start + duration); if (!res) { gst_object_unref (toplevel); return FALSE; } emit_notify = res == -1; } gst_object_unref (toplevel); GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT " new duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)), GST_TIME_ARGS (duration)); klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->set_duration) { gint res = klass->set_duration (self, duration); if (res == TRUE && emit_notify) { self->duration = duration; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); } return ! !res; } GST_WARNING_OBJECT (self, "No set_duration virtual method implementation" " on class %s. Can not set duration %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (duration)); return FALSE; } /** * ges_timeline_element_get_start: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:start for the element. * * Returns: The start of @self (in nanoseconds). */ GstClockTime ges_timeline_element_get_start (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); return self->start; } /** * ges_timeline_element_get_inpoint: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:in-point for the element. * * Returns: The in-point of @self (in nanoseconds). */ GstClockTime ges_timeline_element_get_inpoint (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); return self->inpoint; } /** * ges_timeline_element_get_duration: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:duration for the element. * * Returns: The duration of @self (in nanoseconds). */ GstClockTime ges_timeline_element_get_duration (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); return self->duration; } /** * ges_timeline_element_get_max_duration: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:max-duration for the element. * * Returns: The max-duration of @self (in nanoseconds). */ GstClockTime ges_timeline_element_get_max_duration (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE); return self->maxduration; } /** * ges_timeline_element_get_priority: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:priority for the element. * * Returns: The priority of @self. */ guint32 ges_timeline_element_get_priority (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0); return self->priority; } /** * ges_timeline_element_set_priority: * @self: A #GESTimelineElement * @priority: The priority * * Sets the priority of the element within the containing layer. * * Deprecated:1.10: All priority management is done by GES itself now. * To set #GESEffect priorities #ges_clip_set_top_effect_index should * be used. * * Returns: %TRUE if @priority could be set for @self. */ gboolean ges_timeline_element_set_priority (GESTimelineElement * self, guint32 priority) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); GST_DEBUG_OBJECT (self, "current priority: %d new priority: %d", self->priority, priority); if (klass->set_priority) { gboolean res = klass->set_priority (self, priority); if (res) { self->priority = priority; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIORITY]); } return res; } GST_WARNING_OBJECT (self, "No set_priority virtual method implementation" " on class %s. Can not set priority %d", G_OBJECT_CLASS_NAME (klass), priority); return FALSE; } /** * ges_timeline_element_ripple: * @self: The #GESTimelineElement to ripple * @start: The new start time of @self in ripple mode * * Edits the start time of an element within its timeline in ripple mode. * The element is shifted to @start, and later elements are also shifted * by the same amount (see #GES_EDIT_MODE_RIPPLE). An edit may fail if it * would place the timeline in an unsupported configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the start time may be set * to the edge of some element in the neighbourhood of @start. * * Returns: %TRUE if the ripple edit of @self completed, %FALSE on * failure. */ gboolean ges_timeline_element_ripple (GESTimelineElement * self, GstClockTime start) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); /* FIXME: why are we calling a vmethod to ripple, when * ges_timeline_element_edit() with a GESEditMode of RIPPLE does not do * so? */ klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->ripple) return klass->ripple (self, start); GST_WARNING_OBJECT (self, "No ripple virtual method implementation" " on class %s. Can not ripple to %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start)); return FALSE; } /** * ges_timeline_element_ripple_end: * @self: The #GESTimelineElement to ripple * @end: The new end time of @self in ripple mode * * Edits the end time of an element within its timeline in ripple mode. * The element's duration time is shifted until its end time matches @end, * and later elements have their start time shifted by the same amount * (see #GES_EDIT_MODE_RIPPLE). An edit may fail if it would place the * duration time out of bounds, or if it would place the timeline in an * unsupported configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the end time may be set * to the edge of some element in the neighbourhood of @end. * * Returns: %TRUE if the ripple edit of @self completed, %FALSE on * failure. */ gboolean ges_timeline_element_ripple_end (GESTimelineElement * self, GstClockTime end) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); /* FIXME: why are we calling a vmethod to ripple_end, when * ges_timeline_element_edit() with a GESEditMode of RIPPLE does not do * so? */ klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->ripple_end) { return klass->ripple_end (self, end); } GST_WARNING_OBJECT (self, "No ripple virtual method implementation" " on class %s. Can not ripple end to %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (end)); return FALSE; } /** * ges_timeline_element_roll_start: * @self: The #GESTimelineElement to roll * @start: The new start time of @self in roll mode * * Edits the start time of an element within its timeline in roll mode. * The element is trimmed to @start, and any other element whose end edge * matched the start edge of the element is also trimmed to @start (see * #GES_EDIT_MODE_ROLL). An edit may fail if it would place an in-point * time or duration time out of bounds, or if it would place the timeline * in an unsupported configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the start time may be set * to the edge of some element in the neighbourhood of @start. * * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure. */ gboolean ges_timeline_element_roll_start (GESTimelineElement * self, GstClockTime start) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); /* FIXME: why are we calling a vmethod to roll_start, when * ges_timeline_element_edit() with a GESEditMode of ROLL does not do * so? */ klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->roll_start) { return klass->roll_start (self, start); } GST_WARNING_OBJECT (self, "No ripple virtual method implementation" " on class %s. Can not roll to %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start)); return FALSE; } /** * ges_timeline_element_roll_end: * @self: The #GESTimelineElement to roll * @end: The new end time of @self in roll mode * * Edits the end time of an element within its timeline in roll mode. * The end of the element is trimmed to @end, and any other element whose * start edge matched the end edge of the element is also trimmed to @end * (see #GES_EDIT_MODE_ROLL). An edit may fail if it would place an * in-point time or duration time out of bounds, or if it would place the * timeline in an unsupported configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the end time may be set * to the edge of some element in the neighbourhood of @end. * * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure. */ gboolean ges_timeline_element_roll_end (GESTimelineElement * self, GstClockTime end) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); /* FIXME: why are we calling a vmethod to roll_end, when * ges_timeline_element_edit() with a GESEditMode of ROLL does not do * so? */ klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->roll_end) return klass->roll_end (self, end); GST_WARNING_OBJECT (self, "No ripple virtual method implementation" " on class %s. Can not roll end to %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (end)); return FALSE; } /** * ges_timeline_element_trim: * @self: The #GESTimelineElement to trim * @start: The new start time of @self in trim mode * * Edits the start time of an element within its timeline in trim mode. * The element is shifted to @start, and its in-point time is similarly * shifted to ensure that its internal content will appear at the same * timeline time when it is played (see #GES_EDIT_MODE_TRIM). An edit may * fail if it would place the in-point time out of bounds, or if it would * place the timeline in an unsupported configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the start time may be set * to the edge of some element in the neighbourhood of @start. * * Returns: %TRUE if the trim edit of @self completed, %FALSE on failure. */ gboolean ges_timeline_element_trim (GESTimelineElement * self, GstClockTime start) { GESTimelineElementClass *klass; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); /* FIXME: why are we calling a vmethod to trim, when * ges_timeline_element_edit() with a GESEditMode of TRIM does not do * so? */ klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (klass->trim) return klass->trim (self, start); GST_WARNING_OBJECT (self, "No ripple virtual method implementation" " on class %s. Can not trim to %" GST_TIME_FORMAT, G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start)); return FALSE; } /** * ges_timeline_element_copy: * @self: The #GESTimelineElement to copy * @deep: Whether the copy is needed for pasting * * Create a copy of @self. All the properties of @self are copied into * a new element, with the exception of #GESTimelineElement:parent, * #GESTimelineElement:timeline and #GESTimelineElement:name. Other data, * such the list of a #GESContainer's children, is **not** copied. * * If @deep is %TRUE, then the new element is prepared so that it can be * used in ges_timeline_element_paste() or ges_timeline_paste_element(). * In the case of copying a #GESContainer, this ensures that the children * of @self will also be pasted. The new element should not be used for * anything else and can only be used **once** in a pasting operation. In * particular, the new element itself is not an actual 'deep' copy of * @self, but should be thought of as an intermediate object used for a * single paste operation. * * Returns: (transfer floating): The newly create element, * copied from @self. */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS; /* Start ignoring GParameter deprecation */ GESTimelineElement * ges_timeline_element_copy (GESTimelineElement * self, gboolean deep) { GESAsset *asset; GParameter *params; GParamSpec **specs; GESTimelineElementClass *klass; guint n, n_specs, n_params; GESTimelineElement *ret = NULL; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); klass = GES_TIMELINE_ELEMENT_GET_CLASS (self); specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_specs); params = g_new0 (GParameter, n_specs); n_params = 0; for (n = 0; n < n_specs; ++n) { /* We do not want the timeline or the name to be copied */ if (g_strcmp0 (specs[n]->name, "parent") && g_strcmp0 (specs[n]->name, "timeline") && g_strcmp0 (specs[n]->name, "name") && (specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE) { params[n_params].name = g_intern_string (specs[n]->name); g_value_init (¶ms[n_params].value, specs[n]->value_type); g_object_get_property (G_OBJECT (self), specs[n]->name, ¶ms[n_params].value); ++n_params; } } #if GLIB_CHECK_VERSION(2, 53, 1) { gint i; GValue *values; const gchar **names; values = g_malloc0 (sizeof (GValue) * n_specs); names = g_malloc0 (sizeof (gchar *) * n_specs); for (i = 0; i < n_params; i++) { values[i] = params[i].value; names[i] = params[i].name; } ret = GES_TIMELINE_ELEMENT (g_object_new_with_properties (G_OBJECT_TYPE (self), n_params, names, values)); g_free (names); g_free (values); } #else ret = g_object_newv (G_OBJECT_TYPE (self), n_params, params); #endif while (n_params--) g_value_unset (¶ms[n_params].value); g_free (specs); g_free (params); asset = ges_extractable_get_asset (GES_EXTRACTABLE (self)); if (asset) ges_extractable_set_asset (GES_EXTRACTABLE (ret), asset); if (deep) { if (klass->deep_copy) klass->deep_copy (self, ret); else GST_WARNING_OBJECT (self, "No deep_copy virtual method implementation" " on class %s. Can not finish the copy", G_OBJECT_CLASS_NAME (klass)); } if (deep) { ret->priv->copied_from = gst_object_ref (self); } return ret; } G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */ /** * ges_timeline_element_get_toplevel_parent: * @self: The #GESTimelineElement to get the toplevel parent from * * Gets the toplevel #GESTimelineElement:parent of the element. * * Returns: (transfer full): The toplevel parent of @self. */ GESTimelineElement * ges_timeline_element_get_toplevel_parent (GESTimelineElement * self) { GESTimelineElement *toplevel = self; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); while (GES_TIMELINE_ELEMENT_PARENT (toplevel)) toplevel = GES_TIMELINE_ELEMENT_PARENT (toplevel); return gst_object_ref (toplevel); } /** * ges_timeline_element_get_name: * @self: A #GESTimelineElement * * Gets the #GESTimelineElement:name for the element. * * Returns: (transfer full): The name of @self. */ gchar * ges_timeline_element_get_name (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); return g_strdup (self->name); } /** * ges_timeline_element_set_name: * @self: A #GESTimelineElement * @name: (allow-none): The name @self should take * * Sets the #GESTimelineElement:name for the element. If %NULL is given * for @name, then the library will instead generate a new name based on * the type name of the element, such as the name "uriclip3" for a * #GESUriClip, and will set that name instead. * * If @self already has a #GESTimelineElement:timeline, you should not * call this function with @name set to %NULL. * * You should ensure that, within each #GESTimeline, every element has a * unique name. If you call this function with @name as %NULL, then * the library should ensure that the set generated name is unique from * previously **generated** names. However, if you choose a @name that * interferes with the naming conventions of the library, the library will * attempt to ensure that the generated names will not conflict with the * chosen name, which may lead to a different name being set instead, but * the uniqueness between generated and user-chosen names is not * guaranteed. * * Returns: %TRUE if @name or a generated name for @self could be set. */ gboolean ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name) { gboolean result = TRUE, readd_to_timeline = FALSE; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); if (name != NULL && !g_strcmp0 (name, self->name)) { GST_DEBUG_OBJECT (self, "Same name!"); return TRUE; } /* parented objects cannot be renamed */ if (self->timeline != NULL && name) { GESTimelineElement *tmp = ges_timeline_get_element (self->timeline, name); /* FIXME: if tmp == self then this means that we setting the name of * self to its existing name. There is no need to throw an error */ if (tmp) { gst_object_unref (tmp); goto had_timeline; } timeline_remove_element (self->timeline, self); readd_to_timeline = TRUE; } /* FIXME: if self already has a timeline and name is NULL, then it also * needs to be re-added to the timeline (or, at least its entry in * timeline->priv->all_elements needs its key to be updated) using the * new generated name */ _set_name (self, name); /* FIXME: the set name may not always be unique in a given timeline, see * _set_name(). This can cause timeline_add_element to fail! */ if (readd_to_timeline) timeline_add_element (self->timeline, self); return result; /* error */ had_timeline: { /* FIXME: message is misleading. We are here if some other object in * the timeline was added under @name (see above) */ GST_WARNING ("Object %s already in a timeline can't be renamed to %s", self->name, name); return FALSE; } } /** * ges_timeline_element_add_child_property: * @self: A #GESTimelineElement * @pspec: The specification for the property to add * @child: The #GstObject who the property belongs to * * Register a property of a child of the element to allow it to be * written with ges_timeline_element_set_child_property() and read with * ges_timeline_element_get_child_property(). A change in the property * will also appear in the #GESTimelineElement::deep-notify signal. * * @pspec should be unique from other children properties that have been * registered on @self. * * Returns: %TRUE if the property was successfully registered. */ gboolean ges_timeline_element_add_child_property (GESTimelineElement * self, GParamSpec * pspec, GObject * child) { return ges_timeline_element_add_child_property_full (self, NULL, pspec, child); } /** * ges_timeline_element_get_child_property_by_pspec: * @self: A #GESTimelineElement * @pspec: The specification of a registered child property to get * @value: (out): The return location for the value * * Gets the property of a child of the element. Specifically, the property * corresponding to the @pspec used in * ges_timeline_element_add_child_property() is copied into @value. */ void ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self, GParamSpec * pspec, GValue * value) { ChildPropHandler *handler; g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); handler = g_hash_table_lookup (self->priv->children_props, pspec); if (!handler) goto not_found; g_object_get_property (G_OBJECT (handler->child), pspec->name, value); return; not_found: { GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name); return; } } /** * ges_timeline_element_set_child_property_by_pspec: * @self: A #GESTimelineElement * @pspec: The specification of a registered child property to set * @value: The value to set the property to * * Sets the property of a child of the element. Specifically, the property * corresponding to the @pspec used in * ges_timeline_element_add_child_property() is set to @value. */ void ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self, GParamSpec * pspec, const GValue * value) { g_return_if_fail (GES_IS_TRACK_ELEMENT (self)); set_child_property_by_pspec (self, pspec, value); } /** * ges_timeline_element_set_child_property: * @self: A #GESTimelineElement * @property_name: The name of the child property to set * @value: The value to set the property to * * Sets the property of a child of the element. * * @property_name can either be in the format "prop-name" or * "TypeName::prop-name", where "prop-name" is the name of the property * to set (as used in g_object_set()), and "TypeName" is the type name of * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is * useful when two children of different types share the same property * name. * * The first child found with the given "prop-name" property that was * registered with ges_timeline_element_add_child_property() (and of the * type "TypeName", if it was given) will have the corresponding * property set to @value. Other children that may have also matched the * property name (and type name) are left unchanged! * * Note that ges_timeline_element_set_child_properties() may be more * convenient for C programming. * * Returns: %TRUE if the property was found and set. */ gboolean ges_timeline_element_set_child_property (GESTimelineElement * self, const gchar * property_name, const GValue * value) { GParamSpec *pspec; GObject *child; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec)) goto not_found; return set_child_property_by_pspec (self, pspec, value); not_found: { GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name); return FALSE; } } /** * ges_timeline_element_get_child_property: * @self: A #GESTimelineElement * @property_name: The name of the child property to get * @value: (out): The return location for the value * * Gets the property of a child of the element. * * @property_name can either be in the format "prop-name" or * "TypeName::prop-name", where "prop-name" is the name of the property * to get (as used in g_object_get()), and "TypeName" is the type name of * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is * useful when two children of different types share the same property * name. * * The first child found with the given "prop-name" property that was * registered with ges_timeline_element_add_child_property() (and of the * type "TypeName", if it was given) will have the corresponding * property copied into @value. * * Note that ges_timeline_element_get_child_properties() may be more * convenient for C programming. * * Returns: %TRUE if the property was found and copied to @value. */ gboolean ges_timeline_element_get_child_property (GESTimelineElement * self, const gchar * property_name, GValue * value) { GParamSpec *pspec; GObject *child; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec)) goto not_found; /* FIXME: since GLib 2.60, g_object_get_property() will automatically * initialize the type */ if (G_VALUE_TYPE (value) == G_TYPE_INVALID) g_value_init (value, pspec->value_type); g_object_get_property (child, pspec->name, value); gst_object_unref (child); g_param_spec_unref (pspec); return TRUE; not_found: { GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name); return FALSE; } } /** * ges_timeline_element_lookup_child: * @self: A #GESTimelineElement * @prop_name: The name of a child property * @child: (out) (optional) (transfer full): The return location for the * found child * @pspec: (out) (optional) (transfer full): The return location for the * specification of the child property * * Looks up a child property of the element. * * @prop_name can either be in the format "prop-name" or * "TypeName::prop-name", where "prop-name" is the name of the property * to look up (as used in g_object_get()), and "TypeName" is the type name * of the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is * useful when two children of different types share the same property * name. * * The first child found with the given "prop-name" property that was * registered with ges_timeline_element_add_child_property() (and of the * type "TypeName", if it was given) will be passed to @child, and the * registered specification of this property will be passed to @pspec. * * Returns: %TRUE if a child corresponding to the property was found, in * which case @child and @pspec are set. */ gboolean ges_timeline_element_lookup_child (GESTimelineElement * self, const gchar * prop_name, GObject ** child, GParamSpec ** pspec) { GESTimelineElementClass *class; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); class = GES_TIMELINE_ELEMENT_GET_CLASS (self); g_return_val_if_fail (class->lookup_child, FALSE); return class->lookup_child (self, prop_name, child, pspec); } /** * ges_timeline_element_set_child_property_valist: * @self: A #GESTimelineElement * @first_property_name: The name of the first child property to set * @var_args: The value for the first property, followed optionally by more * name/value pairs, followed by %NULL * * Sets several of the children properties of the element. See * ges_timeline_element_set_child_property(). */ void ges_timeline_element_set_child_property_valist (GESTimelineElement * self, const gchar * first_property_name, va_list var_args) { const gchar *name; GParamSpec *pspec; gchar *error = NULL; GValue value = { 0, }; g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); name = first_property_name; /* Note: This part is in big part copied from the gst_child_object_set_valist * method. */ /* iterate over pairs */ while (name) { if (!ges_timeline_element_lookup_child (self, name, NULL, &pspec)) goto not_found; G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args, G_VALUE_NOCOPY_CONTENTS, &error); if (error) goto cant_copy; set_child_property_by_pspec (self, pspec, &value); g_param_spec_unref (pspec); g_value_unset (&value); name = va_arg (var_args, gchar *); } return; not_found: { GST_WARNING_OBJECT (self, "No property %s in OBJECT\n", name); return; } cant_copy: { GST_WARNING_OBJECT (self, "error copying value %s in %p: %s", pspec->name, self, error); g_param_spec_unref (pspec); g_value_unset (&value); return; } } /** * ges_timeline_element_set_child_properties: * @self: A #GESTimelineElement * @first_property_name: The name of the first child property to set * @...: The value for the first property, followed optionally by more * name/value pairs, followed by %NULL * * Sets several of the children properties of the element. See * ges_timeline_element_set_child_property(). */ void ges_timeline_element_set_child_properties (GESTimelineElement * self, const gchar * first_property_name, ...) { va_list var_args; g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); va_start (var_args, first_property_name); ges_timeline_element_set_child_property_valist (self, first_property_name, var_args); va_end (var_args); } /** * ges_timeline_element_get_child_property_valist: * @self: A #GESTimelineElement * @first_property_name: The name of the first child property to get * @var_args: The return location for the first property, followed * optionally by more name/return location pairs, followed by %NULL * * Gets several of the children properties of the element. See * ges_timeline_element_get_child_property(). */ void ges_timeline_element_get_child_property_valist (GESTimelineElement * self, const gchar * first_property_name, va_list var_args) { const gchar *name; gchar *error = NULL; GValue value = { 0, }; GParamSpec *pspec; GObject *child; g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); name = first_property_name; /* This part is in big part copied from the gst_child_object_get_valist method */ while (name) { if (!ges_timeline_element_lookup_child (self, name, &child, &pspec)) goto not_found; g_value_init (&value, pspec->value_type); g_object_get_property (child, pspec->name, &value); gst_object_unref (child); g_param_spec_unref (pspec); G_VALUE_LCOPY (&value, var_args, 0, &error); if (error) goto cant_copy; g_value_unset (&value); name = va_arg (var_args, gchar *); } return; not_found: { GST_WARNING_OBJECT (self, "no child property %s", name); return; } cant_copy: { GST_WARNING_OBJECT (self, "error copying value %s in %s", pspec->name, error); g_value_unset (&value); return; } } static gint compare_gparamspec (GParamSpec ** a, GParamSpec ** b, gpointer udata) { return g_strcmp0 ((*a)->name, (*b)->name); } /** * ges_timeline_element_list_children_properties: * @self: A #GESTimelineElement * @n_properties: (out): The return location for the length of the * returned array * * Get a list of children properties of the element, which is a list of * all the specifications passed to * ges_timeline_element_add_child_property(). * * Returns: (transfer full) (array length=n_properties): An array of * #GParamSpec corresponding to the child properties of @self, or %NULL if * something went wrong. */ GParamSpec ** ges_timeline_element_list_children_properties (GESTimelineElement * self, guint * n_properties) { GParamSpec **ret; GESTimelineElementClass *class; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL); class = GES_TIMELINE_ELEMENT_GET_CLASS (self); if (!class->list_children_properties) { GST_INFO_OBJECT (self, "No %s->list_children_properties implementation", G_OBJECT_TYPE_NAME (self)); *n_properties = 0; return NULL; } ret = class->list_children_properties (self, n_properties); g_qsort_with_data (ret, *n_properties, sizeof (GParamSpec *), (GCompareDataFunc) compare_gparamspec, NULL); return ret; } /** * ges_timeline_element_get_child_properties: * @self: A #GESTimelineElement * @first_property_name: The name of the first child property to get * @...: The return location for the first property, followed * optionally by more name/return location pairs, followed by %NULL * * Gets several of the children properties of the element. See * ges_timeline_element_get_child_property(). */ void ges_timeline_element_get_child_properties (GESTimelineElement * self, const gchar * first_property_name, ...) { va_list var_args; g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self)); va_start (var_args, first_property_name); ges_timeline_element_get_child_property_valist (self, first_property_name, var_args); va_end (var_args); } /** * ges_timeline_element_remove_child_property: * @self: A #GESTimelineElement * @pspec: The specification for the property to remove * * Remove a child property from the element. @pspec should be a * specification that was passed to * ges_timeline_element_add_child_property(). The corresponding property * will no longer be registered as a child property for the element. * * Returns: %TRUE if the property was successfully un-registered for @self. */ gboolean ges_timeline_element_remove_child_property (GESTimelineElement * self, GParamSpec * pspec) { return g_hash_table_remove (self->priv->children_props, pspec); } /** * ges_timeline_element_get_track_types: * @self: A #GESTimelineElement * * Gets the track types that the element can interact with, i.e. the type * of #GESTrack it can exist in, or will create #GESTrackElement-s for. * * Returns: The track types that @self supports. * * Since: 1.6.0 */ GESTrackType ges_timeline_element_get_track_types (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0); g_return_val_if_fail (GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types, 0); return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types (self); } /** * ges_timeline_element_paste: * @self: The #GESTimelineElement to paste * @paste_position: The position in the timeline @element should be pasted * to, i.e. the #GESTimelineElement:start value for the pasted element. * * Paste an element inside the same timeline and layer as @self. @self * **must** be the return of ges_timeline_element_copy() with `deep=TRUE`, * and it should not be changed before pasting. * @self is not placed in the timeline, instead a new element is created, * alike to the originally copied element. Note that the originally * copied element must stay within the same timeline and layer, at both * the point of copying and pasting. * * Pasting may fail if it would place the timeline in an unsupported * configuration. * * After calling this function @element should not be used. In particular, * @element can **not** be pasted again. Instead, you can copy the * returned element and paste that copy (although, this is only possible * if the paste was successful). * * See also ges_timeline_paste_element(). * * Returns: (transfer full) (nullable): The newly created element, or * %NULL if pasting fails. * * Since: 1.6.0 */ GESTimelineElement * ges_timeline_element_paste (GESTimelineElement * self, GstClockTime paste_position) { GESTimelineElement *res; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); if (!self->priv->copied_from) { GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!"); return NULL; } if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste) { GST_ERROR_OBJECT (self, "No paste vmethod implemented"); return NULL; } res = GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste (self, self->priv->copied_from, paste_position); g_clear_object (&self->priv->copied_from); return res ? g_object_ref (res) : res; } /** * ges_timeline_element_get_layer_priority: * @self: A #GESTimelineElement * * Gets the priority of the layer the element is in. A #GESGroup may span * several layers, so this would return the highest priority (numerically, * the smallest) amongst them. * * Returns: The priority of the layer @self is in, or * #GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY if @self does not exist in a * layer. * * Since: 1.16 */ guint32 ges_timeline_element_get_layer_priority (GESTimelineElement * self) { g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY); if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority) return self->priority; return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority (self); } /** * ges_timeline_element_edit: * @self: The #GESTimelineElement to edit * @layers: (element-type GESLayer) (nullable): A whitelist of layers * where the edit can be performed, %NULL allows all layers in the * timeline * @new_layer_priority: The priority/index of the layer @self should be * moved to. -1 means no move * @mode: The edit mode * @edge: The edge of @self where the edit should occur * @position: The edit position: a new location for the edge of @self * (in nanoseconds) * * Edits the element within its timeline by adjusting its * #GESTimelineElement:start, #GESTimelineElement:duration or * #GESTimelineElement:in-point, and potentially doing the same for * other elements in the timeline. See #GESEditMode for details about each * edit mode. An edit may fail if it would place one of these properties * out of bounds, or if it would place the timeline in an unsupported * configuration. * * Note that if the element's timeline has a * #GESTimeline:snapping-distance set, then the edit position may be set * to the edge of some element in the neighbourhood of @position. * * @new_layer_priority can be used to switch @self, and other elements * moved by the edit, to a new layer. New layers may be be created if the * the corresponding layer priority/index does not yet exist for the * timeline. * * @layers can be used as a whitelist to limit changes to elements that * exist in the corresponding layers. If you intend to also switch * elements between layers, then you must ensure that all the involved * layers are included for the switch to succeed (with the exception of * layers may be newly created). * * Returns: %TRUE if the edit of @self completed, %FALSE on failure. * * Since: 1.18 */ /* FIXME: handle the layers argument. Currently we always treat it as if * it is NULL in the ges-timeline code */ gboolean ges_timeline_element_edit (GESTimelineElement * self, GList * layers, gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) { GESTimeline *timeline; g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE); timeline = GES_TIMELINE_ELEMENT_TIMELINE (self); /* FIXME: handle a NULL timeline! */ switch (mode) { case GES_EDIT_MODE_RIPPLE: return timeline_ripple_object (timeline, self, new_layer_priority < 0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self) : new_layer_priority, layers, edge, position); case GES_EDIT_MODE_TRIM: return timeline_trim_object (timeline, self, new_layer_priority < 0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self) : new_layer_priority, layers, edge, position); case GES_EDIT_MODE_NORMAL: return timeline_move_object (timeline, self, new_layer_priority < 0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self) : new_layer_priority, layers, edge, position); case GES_EDIT_MODE_ROLL: return timeline_roll_object (timeline, self, layers, edge, position); case GES_EDIT_MODE_SLIDE: GST_ERROR_OBJECT (self, "Sliding not implemented."); return FALSE; } return FALSE; }