/* GStreamer Editing Services * Copyright (C) 2009 Edward Hervey * 2009 Nokia Corporation * * 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. */ /** * SECTION:ges-track-object * @short_description: Base Class for objects contained in a #GESTrack * * #GESTrackObject is the Base Class for any object that can be contained in a * #GESTrack. * * It contains the basic information as to the location of the object within * its container, like the start position, the in-point, the duration and the * priority. */ #include "ges-internal.h" #include "gesmarshal.h" #include "ges-track-object.h" #include "ges-timeline-object.h" #include G_DEFINE_ABSTRACT_TYPE (GESTrackObject, ges_track_object, G_TYPE_INITIALLY_UNOWNED); struct _GESTrackObjectPrivate { /* These fields are only used before the gnlobject is available */ guint64 pending_start; guint64 pending_inpoint; guint64 pending_duration; guint32 pending_priority; gboolean pending_active; GstElement *gnlobject; /* The GnlObject */ GstElement *element; /* The element contained in the gnlobject (can be NULL) */ /* We keep a link between properties name and elements internally * The hashtable should look like * {GParamaSpec ---> element,}*/ GHashTable *properties_hashtable; GESTimelineObject *timelineobj; GESTrack *track; gboolean valid; guint64 maxduration; gboolean locked; /* If TRUE, then moves in sync with its controlling * GESTimelineObject */ }; enum { PROP_0, PROP_START, PROP_INPOINT, PROP_DURATION, PROP_PRIORITY, PROP_ACTIVE, PROP_LOCKED, PROP_MAX_DURATION, PROP_LAST }; static GParamSpec *properties[PROP_LAST]; enum { DEEP_NOTIFY, LAST_SIGNAL }; static guint ges_track_object_signals[LAST_SIGNAL] = { 0 }; static GstElement *ges_track_object_create_gnl_object_func (GESTrackObject * object); static void gnlobject_start_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj); static void gnlobject_media_start_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj); static void gnlobject_priority_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj); static void gnlobject_duration_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj); static void gnlobject_active_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj); static void connect_properties_signals (GESTrackObject * object); static void connect_signal (gpointer key, gpointer value, gpointer user_data); static void gst_element_prop_changed_cb (GstElement * element, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj); static inline gboolean ges_track_object_set_start_internal (GESTrackObject * object, guint64 start); static inline gboolean ges_track_object_set_inpoint_internal (GESTrackObject * object, guint64 inpoint); static inline gboolean ges_track_object_set_duration_internal (GESTrackObject * object, guint64 duration); static inline gboolean ges_track_object_set_priority_internal (GESTrackObject * object, guint32 priority); static inline void ges_track_object_set_locked_internal (GESTrackObject * object, gboolean locked); static GParamSpec **default_list_children_properties (GESTrackObject * object, guint * n_properties); static void ges_track_object_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GESTrackObject *tobj = GES_TRACK_OBJECT (object); switch (property_id) { case PROP_START: g_value_set_uint64 (value, ges_track_object_get_start (tobj)); break; case PROP_INPOINT: g_value_set_uint64 (value, ges_track_object_get_inpoint (tobj)); break; case PROP_DURATION: g_value_set_uint64 (value, ges_track_object_get_duration (tobj)); break; case PROP_PRIORITY: g_value_set_uint (value, ges_track_object_get_priority (tobj)); break; case PROP_ACTIVE: g_value_set_boolean (value, ges_track_object_is_active (tobj)); break; case PROP_LOCKED: g_value_set_boolean (value, ges_track_object_is_locked (tobj)); break; case PROP_MAX_DURATION: g_value_set_uint64 (value, tobj->priv->maxduration); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void ges_track_object_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { GESTrackObject *tobj = GES_TRACK_OBJECT (object); switch (property_id) { case PROP_START: ges_track_object_set_start_internal (tobj, g_value_get_uint64 (value)); break; case PROP_INPOINT: ges_track_object_set_inpoint_internal (tobj, g_value_get_uint64 (value)); break; case PROP_DURATION: ges_track_object_set_duration_internal (tobj, g_value_get_uint64 (value)); break; case PROP_PRIORITY: ges_track_object_set_priority_internal (tobj, g_value_get_uint (value)); break; case PROP_ACTIVE: ges_track_object_set_active (tobj, g_value_get_boolean (value)); break; case PROP_LOCKED: ges_track_object_set_locked_internal (tobj, g_value_get_boolean (value)); break; case PROP_MAX_DURATION: ges_track_object_set_max_duration (tobj, g_value_get_uint64 (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void ges_track_object_dispose (GObject * object) { GESTrackObjectPrivate *priv = GES_TRACK_OBJECT (object)->priv; if (priv->properties_hashtable) g_hash_table_destroy (priv->properties_hashtable); G_OBJECT_CLASS (ges_track_object_parent_class)->dispose (object); } static void ges_track_object_finalize (GObject * object) { G_OBJECT_CLASS (ges_track_object_parent_class)->finalize (object); } static void ges_track_object_class_init (GESTrackObjectClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (GESTrackObjectPrivate)); object_class->get_property = ges_track_object_get_property; object_class->set_property = ges_track_object_set_property; object_class->dispose = ges_track_object_dispose; object_class->finalize = ges_track_object_finalize; /** * GESTrackObject:start * * The position of the object in the container #GESTrack (in nanoseconds). */ properties[PROP_START] = g_param_spec_uint64 ("start", "Start", "The position in the container", 0, G_MAXUINT64, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_START, properties[PROP_START]); /** * GESTrackObject:in-point * * The in-point at which this #GESTrackObject will start outputting data * from its contents (in nanoseconds). * * Ex: an in-point of 5 seconds means that the first outputted buffer will * be the one located 5 seconds in the controlled resource. */ properties[PROP_INPOINT] = g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0, G_MAXUINT64, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_INPOINT, properties[PROP_INPOINT]); /** * GESTrackObject:duration * * The duration (in nanoseconds) which will be used in the container #GESTrack * starting from 'in-point'. * */ properties[PROP_DURATION] = g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0, G_MAXUINT64, GST_SECOND, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_DURATION, properties[PROP_DURATION]); /** * GESTrackObject:priority * * The priority of the object within the containing #GESTrack. * If two objects intersect over the same region of time, the @priority * property is used to decide which one takes precedence. * * The highest priority (that supercedes everything) is 0, and then lowering * priorities go in increasing numerical value (with #G_MAXUINT64 being the * lowest priority). */ properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_PRIORITY, properties[PROP_PRIORITY]); /** * GESTrackObject:active * * Whether the object should be taken into account in the #GESTrack output. * If #FALSE, then its contents will not be used in the resulting track. */ properties[PROP_ACTIVE] = g_param_spec_boolean ("active", "Active", "Use object in output", TRUE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_ACTIVE, properties[PROP_ACTIVE]); /** * GESTrackObject:locked * * If %TRUE, then moves in sync with its controlling #GESTimelineObject */ properties[PROP_LOCKED] = g_param_spec_boolean ("locked", "Locked", "Moves in sync with its controling TimelineObject", TRUE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_LOCKED, properties[PROP_LOCKED]); /** * GESTrackObject:max-duration: * * The maximum duration (in nanoseconds) of the #GESTrackObject. * * Since: 0.10.XX */ g_object_class_install_property (object_class, PROP_MAX_DURATION, g_param_spec_uint64 ("max-duration", "Maximum duration", "The duration of the object", GST_CLOCK_TIME_NONE, G_MAXUINT64, G_MAXUINT64, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); /** * GESTrackObject::deep-notify: * @track_object: a #GESTrackObject * @prop_object: the object that originated the signal * @prop: the property that changed * * The deep notify signal is used to be notified of property changes of all * the childs of @track_object * * Since: 0.10.2 */ ges_track_object_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, gst_marshal_VOID__OBJECT_PARAM, G_TYPE_NONE, 2, GST_TYPE_ELEMENT, G_TYPE_PARAM); klass->create_gnl_object = ges_track_object_create_gnl_object_func; /* There is no 'get_props_hashtable' default implementation */ klass->get_props_hastable = NULL; klass->list_children_properties = default_list_children_properties; } static void ges_track_object_init (GESTrackObject * self) { GESTrackObjectPrivate *priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GES_TYPE_TRACK_OBJECT, GESTrackObjectPrivate); /* Sane default values */ priv->pending_start = 0; priv->pending_inpoint = 0; priv->pending_duration = GST_SECOND; priv->pending_priority = 1; priv->pending_active = TRUE; priv->locked = TRUE; priv->properties_hashtable = NULL; priv->maxduration = GST_CLOCK_TIME_NONE; } static inline gboolean ges_track_object_set_start_internal (GESTrackObject * object, guint64 start) { GST_DEBUG ("object:%p, start:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (start)); if (object->priv->gnlobject != NULL) { if (G_UNLIKELY (start == object->start)) return FALSE; g_object_set (object->priv->gnlobject, "start", start, NULL); } else object->priv->pending_start = start; return TRUE; }; /** * ges_track_object_set_start: * @object: a #GESTrackObject * @start: the start position (in #GstClockTime) * * Sets the position of the object in the container #GESTrack. */ void ges_track_object_set_start (GESTrackObject * object, guint64 start) { g_return_if_fail (GES_IS_TRACK_OBJECT (object)); if (ges_track_object_set_start_internal (object, start)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_START]); #else g_object_notify (G_OBJECT (object), "start"); #endif } static inline gboolean ges_track_object_set_inpoint_internal (GESTrackObject * object, guint64 inpoint) { GST_DEBUG ("object:%p, inpoint:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (inpoint)); if (object->priv->gnlobject != NULL) { if (G_UNLIKELY (inpoint == object->inpoint)) return FALSE; g_object_set (object->priv->gnlobject, "media-start", inpoint, NULL); } else object->priv->pending_inpoint = inpoint; return TRUE; } /** * ges_track_object_set_inpoint: * @object: a #GESTrackObject * @inpoint: the in-point (in #GstClockTime) * * Set the offset within the contents of this #GESTrackObject */ void ges_track_object_set_inpoint (GESTrackObject * object, guint64 inpoint) { g_return_if_fail (GES_IS_TRACK_OBJECT (object)); if (ges_track_object_set_inpoint_internal (object, inpoint)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_INPOINT]); #else g_object_notify (G_OBJECT (object), "in-point"); #endif } static inline gboolean ges_track_object_set_duration_internal (GESTrackObject * object, guint64 duration) { GESTrackObjectPrivate *priv = object->priv; GST_DEBUG ("object:%p, duration:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (duration)); if (GST_CLOCK_TIME_IS_VALID (priv->maxduration) && duration > object->inpoint + priv->maxduration) duration = priv->maxduration - object->inpoint; if (priv->gnlobject != NULL) { if (G_UNLIKELY (duration == object->duration)) return FALSE; g_object_set (priv->gnlobject, "duration", duration, "media-duration", duration, NULL); } else priv->pending_duration = duration; return TRUE; } /** * ges_track_object_set_duration: * @object: a #GESTrackObject * @duration: the duration (in #GstClockTime) * * Set the duration which will be used in the container #GESTrack * starting from the 'in-point' */ void ges_track_object_set_duration (GESTrackObject * object, guint64 duration) { g_return_if_fail (GES_IS_TRACK_OBJECT (object)); if (ges_track_object_set_duration_internal (object, duration)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_DURATION]); #else g_object_notify (G_OBJECT (object), "duration"); #endif } static inline gboolean ges_track_object_set_priority_internal (GESTrackObject * object, guint32 priority) { GST_DEBUG ("object:%p, priority:%" G_GUINT32_FORMAT, object, priority); if (object->priv->gnlobject != NULL) { if (G_UNLIKELY (priority == object->priority)) return FALSE; g_object_set (object->priv->gnlobject, "priority", priority, NULL); } else object->priv->pending_priority = priority; return TRUE; } /** * ges_track_object_set_priority: * @object: a #GESTrackObject * @priority: the priority * * Sets the priority of the object withing the containing #GESTrack. * If two objects intersect over the same region of time, the priority * property is used to decide which one takes precedence. * * The highest priority (that supercedes everything) is 0, and then * lowering priorities go in increasing numerical value (with G_MAXUINT32 * being the lowest priority). */ void ges_track_object_set_priority (GESTrackObject * object, guint32 priority) { if (ges_track_object_set_priority_internal (object, priority)) #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_PRIORITY]); #else g_object_notify (G_OBJECT (object), "priority"); #endif } /** * ges_track_object_set_active: * @object: a #GESTrackObject * @active: visibility * * Sets the usage of the @object. If @active is %TRUE, the object will be used for * playback and rendering, else it will be ignored. * * Returns: %TRUE if the property was toggled, else %FALSE */ gboolean ges_track_object_set_active (GESTrackObject * object, gboolean active) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); GST_DEBUG ("object:%p, active:%d", object, active); if (object->priv->gnlobject != NULL) { if (G_UNLIKELY (active == object->active)) return FALSE; g_object_set (object->priv->gnlobject, "active", active, NULL); } else object->priv->pending_active = active; return TRUE; } /* Callbacks from the GNonLin object */ static void gnlobject_start_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj) { guint64 start; GESTrackObjectClass *klass; klass = GES_TRACK_OBJECT_GET_CLASS (obj); g_object_get (gnlobject, "start", &start, NULL); GST_DEBUG ("gnlobject start : %" GST_TIME_FORMAT " current : %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (obj->start)); if (start != obj->start) { obj->start = start; if (klass->start_changed) klass->start_changed (obj, start); } } static void gst_element_prop_changed_cb (GstElement * element, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj) { g_signal_emit (obj, ges_track_object_signals[DEEP_NOTIFY], 0, GST_ELEMENT (element), arg); } static void connect_signal (gpointer key, gpointer value, gpointer user_data) { gchar *signame = g_strconcat ("notify::", G_PARAM_SPEC (key)->name, NULL); g_signal_connect (G_OBJECT (value), signame, G_CALLBACK (gst_element_prop_changed_cb), GES_TRACK_OBJECT (user_data)); g_free (signame); } static void connect_properties_signals (GESTrackObject * object) { if (G_UNLIKELY (!object->priv->properties_hashtable)) { GST_WARNING ("The properties_hashtable hasn't been set"); return; } g_hash_table_foreach (object->priv->properties_hashtable, (GHFunc) connect_signal, object); } /* Callbacks from the GNonLin object */ static void gnlobject_media_start_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj) { guint64 start; GESTrackObjectClass *klass; klass = GES_TRACK_OBJECT_GET_CLASS (obj); g_object_get (gnlobject, "media-start", &start, NULL); GST_DEBUG ("gnlobject in-point : %" GST_TIME_FORMAT " current : %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (obj->inpoint)); if (start != obj->inpoint) { obj->inpoint = start; if (klass->media_start_changed) klass->media_start_changed (obj, start); } } static void gnlobject_priority_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj) { guint32 priority; GESTrackObjectClass *klass; klass = GES_TRACK_OBJECT_GET_CLASS (obj); g_object_get (gnlobject, "priority", &priority, NULL); GST_DEBUG ("gnlobject priority : %d current : %d", priority, obj->priority); if (priority != obj->priority) { obj->priority = priority; if (klass->gnl_priority_changed) klass->gnl_priority_changed (obj, priority); } } static void gnlobject_duration_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj) { guint64 duration; GESTrackObjectClass *klass; klass = GES_TRACK_OBJECT_GET_CLASS (obj); g_object_get (gnlobject, "duration", &duration, NULL); GST_DEBUG_OBJECT (gnlobject, "duration : %" GST_TIME_FORMAT " current : %" GST_TIME_FORMAT, GST_TIME_ARGS (duration), GST_TIME_ARGS (obj->duration)); if (duration != obj->duration) { obj->duration = duration; if (klass->duration_changed) klass->duration_changed (obj, duration); } } static void gnlobject_active_cb (GstElement * gnlobject, GParamSpec * arg G_GNUC_UNUSED, GESTrackObject * obj) { gboolean active; GESTrackObjectClass *klass; klass = GES_TRACK_OBJECT_GET_CLASS (obj); g_object_get (gnlobject, "active", &active, NULL); GST_DEBUG ("gnlobject active : %d current : %d", active, obj->active); if (active != obj->active) { obj->active = active; if (klass->active_changed) klass->active_changed (obj, active); } } /* default 'create_gnl_object' virtual method implementation */ static GstElement * ges_track_object_create_gnl_object_func (GESTrackObject * self) { GESTrackObjectClass *klass = NULL; GstElement *child = NULL; GstElement *gnlobject; klass = GES_TRACK_OBJECT_GET_CLASS (self); if (G_UNLIKELY (self->priv->gnlobject != NULL)) goto already_have_gnlobject; if (G_UNLIKELY (klass->gnlobject_factorytype == NULL)) goto no_gnlfactory; GST_DEBUG ("Creating a supporting gnlobject of type '%s'", klass->gnlobject_factorytype); gnlobject = gst_element_factory_make (klass->gnlobject_factorytype, NULL); if (G_UNLIKELY (gnlobject == NULL)) goto no_gnlobject; if (klass->create_element) { GST_DEBUG ("Calling subclass 'create_element' vmethod"); child = klass->create_element (self); if (G_UNLIKELY (!child)) goto child_failure; if (!gst_bin_add (GST_BIN (gnlobject), child)) goto add_failure; GST_DEBUG ("Succesfully got the element to put in the gnlobject"); self->priv->element = child; } GST_DEBUG ("done"); return gnlobject; /* ERROR CASES */ already_have_gnlobject: { GST_ERROR ("Already controlling a GnlObject %s", GST_ELEMENT_NAME (self->priv->gnlobject)); return NULL; } no_gnlfactory: { GST_ERROR ("No GESTrackObject::gnlobject_factorytype implementation!"); return NULL; } no_gnlobject: { GST_ERROR ("Error creating a gnlobject of type '%s'", klass->gnlobject_factorytype); return NULL; } child_failure: { GST_ERROR ("create_element returned NULL"); gst_object_unref (gnlobject); return NULL; } add_failure: { GST_ERROR ("Error adding the contents to the gnlobject"); gst_object_unref (child); gst_object_unref (gnlobject); return NULL; } } static gboolean ensure_gnl_object (GESTrackObject * object) { GESTrackObjectClass *class; GstElement *gnlobject; GHashTable *props_hash; gboolean res = TRUE; if (object->priv->gnlobject && object->priv->valid) return FALSE; /* 1. Create the GnlObject */ GST_DEBUG ("Creating GnlObject"); class = GES_TRACK_OBJECT_GET_CLASS (object); if (G_UNLIKELY (class->create_gnl_object == NULL)) { GST_ERROR ("No 'create_gnl_object' implementation !"); goto done; } GST_DEBUG ("Calling virtual method"); /* 2. Fill in the GnlObject */ if (object->priv->gnlobject == NULL) { /* call the create_gnl_object virtual method */ gnlobject = class->create_gnl_object (object); if (G_UNLIKELY (gnlobject == NULL)) { GST_ERROR ("'create_gnl_object' implementation returned TRUE but no GnlObject is available"); goto done; } GST_DEBUG_OBJECT (object, "Got a valid GnlObject, now filling it in"); object->priv->gnlobject = gnlobject; if (object->priv->timelineobj) res = ges_timeline_object_fill_track_object (object->priv->timelineobj, object, object->priv->gnlobject); else res = TRUE; if (res) { /* Connect to property notifications */ /* FIXME : remember the signalids so we can remove them later on !!! */ g_signal_connect (G_OBJECT (object->priv->gnlobject), "notify::start", G_CALLBACK (gnlobject_start_cb), object); g_signal_connect (G_OBJECT (object->priv->gnlobject), "notify::media-start", G_CALLBACK (gnlobject_media_start_cb), object); g_signal_connect (G_OBJECT (object->priv->gnlobject), "notify::duration", G_CALLBACK (gnlobject_duration_cb), object); g_signal_connect (G_OBJECT (object->priv->gnlobject), "notify::priority", G_CALLBACK (gnlobject_priority_cb), object); g_signal_connect (G_OBJECT (object->priv->gnlobject), "notify::active", G_CALLBACK (gnlobject_active_cb), object); /* Set some properties on the GnlObject */ g_object_set (object->priv->gnlobject, "duration", object->priv->pending_duration, "media-duration", object->priv->pending_duration, "start", object->priv->pending_start, "media-start", object->priv->pending_inpoint, "priority", object->priv->pending_priority, "active", object->priv->pending_active, NULL); if (object->priv->track != NULL) g_object_set (object->priv->gnlobject, "caps", ges_track_get_caps (object->priv->track), NULL); /* We feed up the props_hashtable if possible */ if (class->get_props_hastable) { props_hash = class->get_props_hastable (object); if (props_hash == NULL) { GST_DEBUG ("'get_props_hastable' implementation returned TRUE but no" "properties_hashtable is available"); } else { object->priv->properties_hashtable = props_hash; connect_properties_signals (object); } } } } done: object->priv->valid = res; GST_DEBUG ("Returning res:%d", res); return res; } /* INTERNAL USAGE */ gboolean ges_track_object_set_track (GESTrackObject * object, GESTrack * track) { GST_DEBUG ("object:%p, track:%p", object, track); object->priv->track = track; if (object->priv->track) { /* If we already have a gnlobject, we just set its caps properly */ if (object->priv->gnlobject) { g_object_set (object->priv->gnlobject, "caps", ges_track_get_caps (object->priv->track), NULL); return TRUE; } else { return ensure_gnl_object (object); } } return TRUE; } /** * ges_track_object_get_track: * @object: a #GESTrackObject * * Get the #GESTrack to which this object belongs. * * Returns: (transfer none): The #GESTrack to which this object belongs. Can be %NULL if it * is not in any track */ GESTrack * ges_track_object_get_track (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), NULL); return object->priv->track; } /** * ges_track_object_set_timeline_object: * @object: The #GESTrackObject to set the parent to * @tlobject: The #GESTimelineObject, parent of @tlobj or %NULL * * Set the #GESTimelineObject to which @object belongs. */ void ges_track_object_set_timeline_object (GESTrackObject * object, GESTimelineObject * tlobject) { GST_DEBUG ("object:%p, timeline-object:%p", object, tlobject); object->priv->timelineobj = tlobject; } /** * ges_track_object_get_timeline_object: * @object: a #GESTrackObject * * Get the #GESTimelineObject which is controlling this track object * * Returns: (transfer none): the #GESTimelineObject which is controlling * this track object */ GESTimelineObject * ges_track_object_get_timeline_object (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), NULL); return object->priv->timelineobj; } /** * ges_track_object_get_gnlobject: * @object: a #GESTrackObject * * Get the GNonLin object this object is controlling. * * Returns: (transfer none): the GNonLin object this object is controlling. */ GstElement * ges_track_object_get_gnlobject (GESTrackObject * object) { return object->priv->gnlobject; } /** * ges_track_object_get_element: * @object: a #GESTrackObject * * Get the #GstElement this track object is controlling within GNonLin. * * Returns: (transfer none): the #GstElement this track object is controlling * within GNonLin. */ GstElement * ges_track_object_get_element (GESTrackObject * object) { return object->priv->element; } static inline void ges_track_object_set_locked_internal (GESTrackObject * object, gboolean locked) { object->priv->locked = locked; } /** * ges_track_object_set_locked: * @object: a #GESTrackObject * @locked: whether the object is lock to its parent * * Set the locking status of the @object in relationship to its controlling * #GESTimelineObject. If @locked is %TRUE, then this object will move synchronously * with its controlling #GESTimelineObject. */ void ges_track_object_set_locked (GESTrackObject * object, gboolean locked) { g_return_if_fail (GES_IS_TRACK_OBJECT (object)); GST_DEBUG_OBJECT (object, "%s object", locked ? "Locking" : "Unlocking"); ges_track_object_set_locked_internal (object, locked); #if GLIB_CHECK_VERSION(2,26,0) g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_LOCKED]); #else g_object_notify (G_OBJECT (object), "locked"); #endif } /** * ges_track_object_is_locked: * @object: a #GESTrackObject * * Let you know if object us locked or not (moving synchronously). * * Returns: %TRUE if the object is moving synchronously to its controlling * #GESTimelineObject, else %FALSE. */ gboolean ges_track_object_is_locked (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); return object->priv->locked; } /** * ges_track_object_get_start: * @object: a #GESTrackObject * * Get the position of the object in the container #GESTrack. * * Returns: the start position (in #GstClockTime) or #GST_CLOCK_TIME_NONE * if something went wrong. * * Since: 0.10.2 */ guint64 ges_track_object_get_start (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), GST_CLOCK_TIME_NONE); if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_start; else return object->start; } /** * ges_track_object_get_inpoint: * @object: a #GESTrackObject * * Get the offset within the contents of this #GESTrackObject * * Returns: the in-point (in #GstClockTime) or #GST_CLOCK_TIME_NONE * if something went wrong. * * Since: 0.10.2 */ guint64 ges_track_object_get_inpoint (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), GST_CLOCK_TIME_NONE); if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_inpoint; else return object->inpoint; } /** * ges_track_object_get_duration: * @object: a #GESTrackObject * * Get the duration which will be used in the container #GESTrack * starting from the 'in-point' * * Returns: the duration (in #GstClockTime) or #GST_CLOCK_TIME_NONE * if something went wrong. * * Since: 0.10.2 */ guint64 ges_track_object_get_duration (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), GST_CLOCK_TIME_NONE); if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_duration; else return object->duration; } /** * ges_track_object_get_priority: * @object: a #GESTrackObject * * Get the priority of the object withing the containing #GESTrack. * * Returns: the priority of @object or -1 if something went wrong * * Since: 0.10.2 */ guint32 ges_track_object_get_priority (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), -1); if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_priority; else return object->priority; } /** * ges_track_object_is_active: * @object: a #GESTrackObject * * Lets you know if @object will be used for playback and rendering, * or not. * * Returns: %TRUE if @object is active, %FALSE otherwize * * Since: 0.10.2 */ gboolean ges_track_object_is_active (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); if (G_UNLIKELY (object->priv->gnlobject == NULL)) return object->priv->pending_active; else return object->active; } /** * ges_track_object_lookup_child: * @object: object to lookup the property in * @prop_name: name of the property to look up. You can specify the name of the * class as such: "ClassName::property-name", to guarantee that you get the * proper GParamSpec in case various GstElement-s contain the same property * name. If you don't do so, you will get the first element found, having * this property and the and the corresponding GParamSpec. * @element: (out) (allow-none) (transfer full): pointer to a #GstElement that * takes the real object to set property on * @pspec: (out) (allow-none) (transfer full): pointer to take the #GParamSpec * describing the property * * Looks up which @element and @pspec would be effected by the given @name. If various * contained elements have this property name you will get the first one, unless you * specify the class name in @name. * * Returns: TRUE if @element and @pspec could be found. FALSE otherwise. In that * case the values for @pspec and @element are not modified. Unref @element after * usage. * * Since: 0.10.2 */ gboolean ges_track_object_lookup_child (GESTrackObject * object, const gchar * prop_name, GstElement ** element, GParamSpec ** pspec) { GHashTableIter iter; gpointer key, value; gchar **names, *name, *classename; gboolean res; GESTrackObjectPrivate *priv; g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); priv = object->priv; 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, priv->properties_hashtable); while (g_hash_table_iter_next (&iter, &key, &value)) { if (g_strcmp0 (G_PARAM_SPEC (key)->name, name) == 0) { if (classename == NULL || g_strcmp0 (G_OBJECT_TYPE_NAME (G_OBJECT (value)), classename) == 0) { GST_DEBUG ("The %s property from %s has been found", name, classename); if (element) *element = g_object_ref (value); *pspec = g_param_spec_ref (key); res = TRUE; break; } } } g_strfreev (names); return res; } /** * ges_track_object_set_child_property_by_pspec: * @object: a #GESTrackObject * @pspec: The #GParamSpec that specifies the property you want to set * @value: the value * * Sets a property of a child of @object. * * Since: 0.10.2 */ void ges_track_object_set_child_property_by_pspec (GESTrackObject * object, GParamSpec * pspec, GValue * value) { GstElement *element; GESTrackObjectPrivate *priv; g_return_if_fail (GES_IS_TRACK_OBJECT (object)); priv = object->priv; if (!priv->properties_hashtable) goto prop_hash_not_set; element = g_hash_table_lookup (priv->properties_hashtable, pspec); if (!element) goto not_found; g_object_set_property (G_OBJECT (element), pspec->name, value); return; not_found: { GST_ERROR ("The %s property doesn't exist", pspec->name); return; } prop_hash_not_set: { GST_DEBUG ("The child properties haven't been set on %p", object); return; } } /** * ges_track_object_set_child_property_valist: * @object: The #GESTrackObject parent object * @first_property_name: The name of the first property to set * @var_args: value for the first property, followed optionally by more * name/return location pairs, followed by NULL * * Sets a property of a child of @object. If there are various child elements * that have the same property name, you can distinguish them using the following * syntax: 'ClasseName::property_name' as property name. If you don't, the * corresponding property of the first element found will be set. * * Since: 0.10.2 */ void ges_track_object_set_child_property_valist (GESTrackObject * object, const gchar * first_property_name, va_list var_args) { const gchar *name; GParamSpec *pspec; GstElement *element; gchar *error = NULL; GValue value = { 0, }; g_return_if_fail (GES_IS_TRACK_OBJECT (object)); 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_track_object_lookup_child (object, name, &element, &pspec)) goto not_found; #if GLIB_CHECK_VERSION(2,23,3) G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args, G_VALUE_NOCOPY_CONTENTS, &error); #else g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS, &error); #endif if (error) goto cant_copy; g_object_set_property (G_OBJECT (element), pspec->name, &value); g_object_unref (element); g_value_unset (&value); name = va_arg (var_args, gchar *); } return; not_found: { GST_WARNING ("No property %s in OBJECT\n", name); return; } cant_copy: { GST_WARNING ("error copying value %s in object %p: %s", pspec->name, object, error); g_value_unset (&value); return; } } /** * ges_track_object_set_child_property: * @object: The #GESTrackObject parent object * @first_property_name: The name of the first property to set * @...: value for the first property, followed optionally by more * name/return location pairs, followed by NULL * * Sets a property of a child of @object. If there are various child elements * that have the same property name, you can distinguish them using the following * syntax: 'ClasseName::property_name' as property name. If you don't, the * corresponding property of the first element found will be set. * * Since: 0.10.2 */ void ges_track_object_set_child_property (GESTrackObject * object, const gchar * first_property_name, ...) { va_list var_args; g_return_if_fail (GES_IS_TRACK_OBJECT (object)); va_start (var_args, first_property_name); ges_track_object_set_child_property_valist (object, first_property_name, var_args); va_end (var_args); } /** * ges_track_object_get_child_property_valist: * @object: The #GESTrackObject parent object * @first_property_name: The name of the first property to get * @var_args: value for the first property, followed optionally by more * name/return location pairs, followed by NULL * * Gets a property of a child of @object. If there are various child elements * that have the same property name, you can distinguish them using the following * syntax: 'ClasseName::property_name' as property name. If you don't, the * corresponding property of the first element found will be set. * * Since: 0.10.2 */ void ges_track_object_get_child_property_valist (GESTrackObject * object, const gchar * first_property_name, va_list var_args) { const gchar *name; gchar *error = NULL; GValue value = { 0, }; GParamSpec *pspec; GstElement *element; g_return_if_fail (G_IS_OBJECT (object)); name = first_property_name; /* This part is in big part copied from the gst_child_object_get_valist method */ while (name) { if (!ges_track_object_lookup_child (object, name, &element, &pspec)) goto not_found; g_value_init (&value, pspec->value_type); g_object_get_property (G_OBJECT (element), pspec->name, &value); g_object_unref (element); 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 ("no property %s in object", name); return; } cant_copy: { GST_WARNING ("error copying value %s in object %p: %s", pspec->name, object, error); g_value_unset (&value); return; } } /** * ges_track_object_list_children_properties: * @object: The #GESTrackObject to get the list of children properties from * @n_properties: return location for the length of the returned array * * Gets an array of #GParamSpec* for all configurable properties of the * children of @object. * * Returns: (transfer full) (array): an array of #GParamSpec* which should be freed after use or * %NULL if something went wrong * * Since: 0.10.2 */ GParamSpec ** ges_track_object_list_children_properties (GESTrackObject * object, guint * n_properties) { GESTrackObjectClass *class; g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), NULL); class = GES_TRACK_OBJECT_GET_CLASS (object); return class->list_children_properties (object, n_properties); } /** * ges_track_object_get_child_property: * @object: The origin #GESTrackObject * @first_property_name: The name of the first property to get * @...: return location for the first property, followed optionally by more * name/return location pairs, followed by NULL * * Gets properties of a child of @object. * * Since: 0.10.2 */ void ges_track_object_get_child_property (GESTrackObject * object, const gchar * first_property_name, ...) { va_list var_args; g_return_if_fail (GES_IS_TRACK_OBJECT (object)); va_start (var_args, first_property_name); ges_track_object_get_child_property_valist (object, first_property_name, var_args); va_end (var_args); } /** * ges_track_object_get_child_property_by_pspec: * @object: a #GESTrackObject * @pspec: The #GParamSpec that specifies the property you want to get * @value: return location for the value * * Gets a property of a child of @object. * * Since: 0.10.2 */ void ges_track_object_get_child_property_by_pspec (GESTrackObject * object, GParamSpec * pspec, GValue * value) { GstElement *element; GESTrackObjectPrivate *priv; g_return_if_fail (GES_IS_TRACK_OBJECT (object)); priv = object->priv; if (!priv->properties_hashtable) goto prop_hash_not_set; element = g_hash_table_lookup (priv->properties_hashtable, pspec); if (!element) goto not_found; g_object_get_property (G_OBJECT (element), pspec->name, value); return; not_found: { GST_ERROR ("The %s property doesn't exist", pspec->name); return; } prop_hash_not_set: { GST_ERROR ("The child properties haven't been set on %p", object); return; } } static GParamSpec ** default_list_children_properties (GESTrackObject * object, guint * n_properties) { GParamSpec **pspec, *spec; GHashTableIter iter; gpointer key, value; guint i = 0; if (!object->priv->properties_hashtable) goto prop_hash_not_set; *n_properties = g_hash_table_size (object->priv->properties_hashtable); pspec = g_new (GParamSpec *, *n_properties); g_hash_table_iter_init (&iter, object->priv->properties_hashtable); while (g_hash_table_iter_next (&iter, &key, &value)) { spec = G_PARAM_SPEC (key); pspec[i] = g_param_spec_ref (spec); i++; } return pspec; prop_hash_not_set: { *n_properties = 0; GST_ERROR ("The child properties haven't been set on %p", object); return NULL; } } /** * ges_track_object_get_max_duration: * @object: The #GESTrackObject to retrieve max duration from * * Get the max duration of @object. * * Returns: The max duration of @object * * Since: 0.10.XX */ guint64 ges_track_object_get_max_duration (GESTrackObject * object) { g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), 0); return object->priv->maxduration; } /** * ges_track_object_set_max_duration: * @object: The #GESTrackObject to retrieve max duration from * @maxduration: The maximum duration of @object * * Returns: Set the max duration of @object * * Since: 0.10.XX */ void ges_track_object_set_max_duration (GESTrackObject * object, guint64 maxduration) { g_return_if_fail (GES_IS_TRACK_OBJECT (object)); object->priv->maxduration = maxduration; } /** * ges_track_object_copy: * @object: The #GESTrackObject to copy * @deep: whether we want to create the gnlobject and copy it properties * * Copies @object * * Returns: The newly create #GESTrackObject, copied from @object * * Since: 0.10.XX */ GESTrackObject * ges_track_object_copy (GESTrackObject * object, gboolean deep) { GESTrackObject *ret = NULL; GParameter *params; GParamSpec **specs; guint n, n_specs, n_params; GValue val = { 0 }; g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), NULL); specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &n_specs); params = g_new0 (GParameter, n_specs); n_params = 0; for (n = 0; n < n_specs; ++n) { if (g_strcmp0 (specs[n]->name, "parent") && (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 (object), specs[n]->name, ¶ms[n_params].value); ++n_params; } } ret = g_object_newv (G_TYPE_FROM_INSTANCE (object), n_params, params); g_free (specs); g_free (params); specs = NULL; params = NULL; if (deep == FALSE) return ret; ensure_gnl_object (ret); specs = ges_track_object_list_children_properties (object, &n_specs); for (n = 0; n < n_specs; ++n) { g_value_init (&val, specs[n]->value_type); g_object_get_property (G_OBJECT (object), specs[n]->name, &val); ges_track_object_set_child_property_by_pspec (ret, specs[n], &val); g_value_unset (&val); } g_free (specs); g_free (params); return ret; } /** * ges_track_object_edit: * @object: the #GESTrackObject to edit * @layers: (element-type GESTimelineLayer): The layers you want the edit to * happen in, %NULL means that the edition is done in all the * #GESTimelineLayers contained in the current timeline. * FIXME: This is not implemented yet. * @mode: The #GESEditMode in which the editition will happen. * @edge: The #GESEdge the edit should happen on. * @position: The position at which to edit @object (in nanosecond) * * Edit @object in the different exisiting #GESEditMode modes. In the case of * slide, and roll, you need to specify a #GESEdge * * Returns: %TRUE if the object as been edited properly, %FALSE if an error * occured * * Since: 0.10.XX */ gboolean ges_track_object_edit (GESTrackObject * object, GList * layers, GESEditMode mode, GESEdge edge, guint64 position) { GESTrack *track = ges_track_object_get_track (object); GESTimeline *timeline; g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); if (G_UNLIKELY (!track)) { GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in" "any Track yet.", mode); return FALSE; } else if (position < 0) { GST_DEBUG_OBJECT (object, "Trying to move before 0, not moving"); return FALSE; } timeline = GES_TIMELINE (ges_track_get_timeline (track)); if (G_UNLIKELY (!timeline)) { GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in" "track %p no in any timeline yet.", mode, track); return FALSE; } switch (mode) { case GES_EDIT_MODE_NORMAL: timeline_move_object (timeline, object, layers, edge, position); break; case GES_EDIT_MODE_TRIM: timeline_trim_object (timeline, object, layers, edge, position); break; case GES_EDIT_MODE_RIPPLE: timeline_ripple_object (timeline, object, layers, edge, position); break; case GES_EDIT_MODE_ROLL: timeline_roll_object (timeline, object, layers, edge, position); break; case GES_EDIT_MODE_SLIDE: timeline_slide_object (timeline, object, layers, edge, position); break; default: GST_ERROR ("Unkown edit mode: %d", mode); return FALSE; } return TRUE; }