gstreamer/ges/ges-timeline-element.c

1883 lines
52 KiB
C
Raw Normal View History

/* gst-editing-services
* Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com>
* <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 <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:gestimelineelement
* @title: GESTimelineElement
* @short_description: Base Class for all elements that will be in a way or
* another inside a GESTimeline.
*
* The GESTimelineElement base class implements the notion of timing as well
* as priority. A GESTimelineElement can have a parent object which will be
* responsible for controlling its timing properties.
*/
#include "ges-utils.h"
#include "ges-timeline-element.h"
#include "ges-extractable.h"
#include "ges-meta-container.h"
#include "ges-internal.h"
Handle changing playback rate Before this patch, NLE and GES did not support NleOperations (respectively GESEffects) that changed the speed/tempo/rate at which the source plays. For example, the 'pitch' element can make audio play faster or slower. In GES 1.5.90 and before, an NleOperation containing the pitch element to change the rate (or tempo) would cause a pipeline state change to PAUSED after that stack; that has been fixed in 1.5.91 (see #755012 [0]). But even then, in 1.5.91 and later, NleComposition would send segment events to its NleSources assuming that one source second is equal to one pipeline second. The resulting early EOS event (in the case of a source rate higher than 1.0) would cause it to switch stacks too early, causing confusion in the timeline and spectacularly messed up output. This patch fixes that by searching for rate-changing elements in GESTrackElements such as GESEffects. If such rate-changing elements are found, their final effect on the playing rate is stored in the corresponding NleObject as the 'media duration factor', named like this because the 'media duration', or source duration, of an NleObject can be computed by multiplying the duration with the media duration factor of that object and its parents (this is called the 'recursive media duration factor'). For example, a 4-second NleSource with an NleOperation with a media duration factor of 2.0 will have an 8-second media duration, which means that for playing 4 seconds in the pipeline, the seek event sent to it must span 8 seconds of media. (So, the 'duration' of an NleObject or GES object always refers to its duration in the timeline, not the media duration.) To summarize: * Rate-changing elements are registered in the GESEffectClass (pitch::tempo and pitch::rate are registered by default); * GESTimelineElement is responsible for detecting rate-changing elements and computing the media_duration_factor; * GESTrackElement is responsible for storing the media_duration_factor in NleObject; * NleComposition is responsible for the recursive_media_duration_factor; * The latter property finally fixes media time computations in NleObject. NLE and GES tests are included. [0] https://bugzilla.gnome.org/show_bug.cgi?id=755012 Differential Revision: https://phabricator.freedesktop.org/D276
2015-12-20 13:03:57 +00:00
#include "ges-effect.h"
#include <string.h>
#include <gobject/gvaluecollector.h>
/* 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;
}
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTimelineElement, ges_timeline_element,
G_TYPE_INITIALLY_UNOWNED,
G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init)
G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
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;
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;
};
typedef struct
{
GObject *child;
GParamSpec *arg;
GESTimelineElement *self;
} EmitDeepNotifyInIdleData;
static void
_set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child,
GParamSpec * pspec, GValue * value)
{
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);
}
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 = G_TYPE_INSTANCE_GET_PRIVATE (self,
GES_TYPE_TIMELINE_ELEMENT, GESTimelineElementPrivate);
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);
g_type_class_add_private (klass, sizeof (GESTimelineElementPrivate));
object_class->get_property = _get_property;
object_class->set_property = _set_property;
/**
* GESTimelineElement:parent:
*
* The parent container of the object
*/
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 in which @element is
*/
properties[PROP_TIMELINE] =
g_param_spec_object ("timeline", "Timeline",
"The timeline the object is in", GES_TYPE_TIMELINE, G_PARAM_READWRITE);
/**
* GESTimelineElement:start:
*
* The position of the object in its container (in nanoseconds).
*/
properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
"The position in the container", 0, G_MAXUINT64, 0, G_PARAM_READWRITE);
/**
* GESTimelineElement:in-point:
*
* The in-point at which this #GESTimelineElement 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);
/**
* GESTimelineElement:duration:
*
* The duration (in nanoseconds) which will be used in the container
*/
properties[PROP_DURATION] =
g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE);
/**
* GESTimelineElement:max-duration:
*
* The maximum duration (in nanoseconds) of the #GESTimelineElement.
*/
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 object.
*
* Setting GESTimelineElement priorities is deprecated
* as all priority management is done by GES itself now.
*/
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 object
*/
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 property that changed
*
* The deep notify signal is used to be notified of property changes of all
* the childs of @timeline_element
*/
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, g_cclosure_marshal_generic,
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<N> */
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 */
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) {
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;
}
/*********************************************
* API implementation *
*********************************************/
/**
* ges_timeline_element_set_parent:
* @self: a #GESTimelineElement
* @parent: new parent of self
*
* Sets the parent of @self to @parent. The parents needs to already
* own a hard reference on @self.
*
* Returns: %TRUE if @parent could be set or %FALSE when @self
* already had a parent or @self and @parent are the same.
*/
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);
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");
gst_object_ref_sink (self);
gst_object_unref (self);
return FALSE;
}
}
/**
* ges_timeline_element_get_parent:
* @self: a #GESTimelineElement
*
* Returns the parent of @self. This function increases the refcount
* of the parent object so you should gst_object_unref() it after usage.
*
* Returns: (transfer full) (nullable): parent of @self, this can be %NULL if
* @self has no parent. unref after usage.
*/
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: The #GESTimeline @self is in
*
* Sets the timeline of @self to @timeline.
*
* Returns: %TRUE if @timeline could be set or %FALSE when @timeline
* already had a timeline.
*/
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
*
* Returns the timeline of @self. This function increases the refcount
* of the timeline so you should gst_object_unref() it after usage.
*
* Returns: (transfer full) (nullable): timeline of @self, this can be %NULL if
* @self has no timeline. unref after usage.
*/
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 position in #GstClockTime
*
* Set the position of the object in its containing layer.
*
* Note that if the snapping-distance property of the timeline containing
* @self is set, @self will properly snap to the edges around @start.
*/
void
ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
{
GESTimelineElementClass *klass;
GESTimelineElement *toplevel_container, *parent;
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
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);
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;
}
gst_object_unref (toplevel_container);
if (klass->set_start) {
if (klass->set_start (self, start)) {
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;
}
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));
}
/**
* ges_timeline_element_set_inpoint:
* @self: a #GESTimelineElement
* @inpoint: the in-point in #GstClockTime
*
* Set the in-point, that is the moment at which the @self will start
* outputting data from its contents.
*/
void
ges_timeline_element_set_inpoint (GESTimelineElement * self,
GstClockTime inpoint)
{
GESTimelineElementClass *klass;
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
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) {
if (klass->set_inpoint (self, inpoint)) {
self->inpoint = inpoint;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPOINT]);
}
return;
}
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));
}
/**
* ges_timeline_element_set_max_duration:
* @self: a #GESTimelineElement
* @maxduration: the maximum duration in #GstClockTime
*
* Set the maximun duration of the object
*/
void
ges_timeline_element_set_max_duration (GESTimelineElement * self,
GstClockTime maxduration)
{
GESTimelineElementClass *klass;
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
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;
}
self->maxduration = maxduration;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_DURATION]);
}
/**
* ges_timeline_element_set_duration:
* @self: a #GESTimelineElement
* @duration: the duration in #GstClockTime
*
* Set the duration of the object
*
* Note that if the timeline snap-distance property of the timeline containing
* @self is set, @self will properly snap to its neighboors.
*/
void
ges_timeline_element_set_duration (GESTimelineElement * self,
GstClockTime duration)
{
GESTimelineElementClass *klass;
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
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_DURATION (self)),
GST_TIME_ARGS (duration));
if (klass->set_duration) {
if (klass->set_duration (self, duration)) {
self->duration = duration;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
}
return;
}
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));
}
/**
* ges_timeline_element_get_start:
* @self: a #GESTimelineElement
*
* Returns: The @start of @self
*/
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
*
* Returns: The @inpoint of @self
*/
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
*
* Returns: The @duration of @self
*/
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
*
* Returns: The @maxduration of @self
*/
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
*
* 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 object within the containing layer
*
* Deprecated: All priority management is done by GES itself now.
* To set #GESEffect priorities #ges_clip_set_top_effect_index should
* be used.
*/
void
ges_timeline_element_set_priority (GESTimelineElement * self, guint32 priority)
{
GESTimelineElementClass *klass;
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
GST_DEBUG_OBJECT (self, "current priority: %d new priority: %d",
self->priority, priority);
if (klass->set_priority) {
if (klass->set_priority (self, priority)) {
self->priority = priority;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIORITY]);
}
return;
}
GST_WARNING_OBJECT (self, "No set_priority virtual method implementation"
" on class %s. Can not set priority %d", G_OBJECT_CLASS_NAME (klass),
priority);
}
/**
* ges_timeline_element_ripple:
* @self: The #GESTimelineElement to ripple.
* @start: The new start of @self in ripple mode.
*
* Edits @self in ripple mode. It allows you to modify the
* start of @self and move the following neighbours accordingly.
* This will change the overall timeline duration.
*
* Returns: %TRUE if the self as been rippled properly, %FALSE if an error
* occured
*/
gboolean
ges_timeline_element_ripple (GESTimelineElement * self, GstClockTime start)
{
GESTimelineElementClass *klass;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
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 (start + duration) of @self in ripple mode. It will
* basically only change the duration of @self.
*
* Edits @self in ripple mode. It allows you to modify the
* duration of a @self and move the following neighbours accordingly.
* This will change the overall timeline duration.
*
* Returns: %TRUE if the self as been rippled properly, %FALSE if an error
* occured
*/
gboolean
ges_timeline_element_ripple_end (GESTimelineElement * self, GstClockTime end)
{
GESTimelineElementClass *klass;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
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 of @self in roll mode, it will also adapat
* the in-point of @self according
*
* Edits @self in roll mode. It allows you to modify the
* start and inpoint of a @self and "resize" (basicly change the duration
* in this case) of the previous neighbours accordingly.
* This will not change the overall timeline duration.
*
* Returns: %TRUE if the self as been roll properly, %FALSE if an error
* occured
*/
gboolean
ges_timeline_element_roll_start (GESTimelineElement * self, GstClockTime start)
{
GESTimelineElementClass *klass;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
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 (start + duration) of @self in roll mode
*
* Edits @self in roll mode. It allows you to modify the
* duration of a @self and trim (basicly change the start + inpoint
* in this case) the following neighbours accordingly.
* This will not change the overall timeline duration.
*
* Returns: %TRUE if the self as been rolled properly, %FALSE if an error
* occured
*/
gboolean
ges_timeline_element_roll_end (GESTimelineElement * self, GstClockTime end)
{
GESTimelineElementClass *klass;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
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 of @self in trim mode, will adapt the inpoint
* of @self accordingly
*
* Edits @self in trim mode. It allows you to modify the
* inpoint and start of @self.
* This will not change the overall timeline duration.
*
* Note that to trim the end of an self you can just set its duration. The same way
* as this method, it will take into account the snapping-distance property of the
* timeline in which @self is.
*
* Returns: %TRUE if the self as been trimmed properly, %FALSE if an error
* occured
*/
gboolean
ges_timeline_element_trim (GESTimelineElement * self, GstClockTime start)
{
GESTimelineElementClass *klass;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
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 we want to create the elements @self contains or not
*
* Copies @self
*
* Returns: (transfer floating): The newly create #GESTimelineElement, copied from @self
*/
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 (&params[n_params].value, specs[n]->value_type);
g_object_get_property (G_OBJECT (self), specs[n]->name,
&params[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 (&params[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;
}
/**
* ges_timeline_element_get_toplevel_parent:
* @self: The #GESTimelineElement to get the toplevel parent from
*
* Gets the toplevel #GESTimelineElement controlling @self
*
* Returns: (transfer full): The toplevel controlling 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
*
* Returns a copy of the name of @self.
* Caller should g_free() the return value after usage.
*
* 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 (if avalaible<)
*
* Sets the name of object, or gives @self a guaranteed unique name (if name is NULL).
* This function makes a copy of the provided name, so the caller retains ownership
* of the name it sent.
*/
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);
if (tmp) {
gst_object_unref (tmp);
goto had_timeline;
}
timeline_remove_element (self->timeline, self);
readd_to_timeline = TRUE;
}
_set_name (self, name);
if (readd_to_timeline)
timeline_add_element (self->timeline, self);
return result;
/* error */
had_timeline:
{
2015-07-16 08:53:17 +00:00
GST_WARNING ("Object %s already in a timeline can't be renamed to %s",
self->name, name);
return FALSE;
}
}
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);
g_idle_add ((GSourceFunc) emit_deep_notify_in_idle, data);
}
gboolean
ges_timeline_element_add_child_property (GESTimelineElement * self,
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->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;
}
/**
2015-12-26 08:43:11 +00:00
* ges_timeline_element_get_child_property_by_pspec:
* @self: a #GESTrackElement
* @pspec: The #GParamSpec that specifies the property you want to get
* @value: (out): return location for the value
*
* Gets a property of a child of @self.
*/
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 #GParamSpec that specifies the property you want to set
* @value: the value
*
* Sets a property of a child of @self.
*/
void
ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
GParamSpec * pspec, GValue * value)
{
ChildPropHandler *handler;
GESTimelineElementClass *klass;
g_return_if_fail (GES_IS_TRACK_ELEMENT (self));
handler = g_hash_table_lookup (self->priv->children_props, pspec);
if (!handler)
goto not_found;
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
g_assert (klass->set_child_property);
klass->set_child_property (self, handler->child, pspec, value);
return;
not_found:
{
GST_ERROR ("The %s property doesn't exist", pspec->name);
return;
}
}
/**
* ges_timeline_element_set_child_property:
* @self: The origin #GESTimelineElement
* @property_name: The name of the property
* @value: the value
*
* Sets a property of a child of @self
*
* Note that #ges_timeline_element_set_child_property is really
* intended for language bindings, #ges_timeline_element_set_child_properties
* is much more convenient for C programming.
*
* Returns: %TRUE if the property was set, %FALSE otherwize
*/
gboolean
ges_timeline_element_set_child_property (GESTimelineElement * self,
const gchar * property_name, GValue * value)
{
GParamSpec *pspec;
GESTimelineElementClass *klass;
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;
klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
g_assert (klass->set_child_property);
klass->set_child_property (self, child, pspec, 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_get_child_property:
2015-12-26 08:43:11 +00:00
* @self: The origin #GESTimelineElement
* @property_name: The name of the property
* @value: (out): return location for the property value, it will
* be initialized if it is initialized with 0
*
* In general, a copy is made of the property contents and
* the caller is responsible for freeing the memory by calling
* g_value_unset().
*
* Gets a property of a GstElement contained in @object.
*
* Note that #ges_timeline_element_get_child_property is really
* intended for language bindings, #ges_timeline_element_get_child_properties
* is much more convenient for C programming.
*
* Returns: %TRUE if the property was found, %FALSE otherwize
*/
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;
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: 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.
2015-12-26 08:43:11 +00:00
* @child: (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.
*/
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: The #GESTimelineElement 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 @self. 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.
*/
void
ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
const gchar * first_property_name, va_list var_args)
{
const gchar *name;
GParamSpec *pspec;
GObject *child;
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, &child, &pspec))
goto not_found;
G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args,
G_VALUE_NOCOPY_CONTENTS, &error);
if (error)
goto cant_copy;
g_object_set_property (child, pspec->name, &value);
gst_object_unref (child);
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);
gst_object_unref (child);
g_param_spec_unref (pspec);
g_value_unset (&value);
return;
}
}
/**
* ges_timeline_element_set_child_properties:
* @self: The #GESTimelineElement 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 @self. 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.
*/
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: The #GESTimelineElement 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 @self. 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.
*/
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: The #GESTimelineElement to get the list of children properties from
* @n_properties: (out): return location for the length of the returned array
*
* Gets an array of #GParamSpec* for all configurable properties of the
* children of @self.
*
* Returns: (transfer full) (array length=n_properties): an array of #GParamSpec* which should be freed after use 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: The origin #GESTimelineElement
* @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 @self.
*/
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);
}
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 all the TrackTypes @self will interact with
*
* 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 the element should
* be copied to, meaning it will become the start of @self
*
* Paste @self inside the timeline. @self must have been created
* using ges_timeline_element_copy with recurse=TRUE set,
* otherwise it will fail.
*
* Returns: (transfer none): Paste @self copying the element
*
* 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 g_object_ref (res);
}
Handle changing playback rate Before this patch, NLE and GES did not support NleOperations (respectively GESEffects) that changed the speed/tempo/rate at which the source plays. For example, the 'pitch' element can make audio play faster or slower. In GES 1.5.90 and before, an NleOperation containing the pitch element to change the rate (or tempo) would cause a pipeline state change to PAUSED after that stack; that has been fixed in 1.5.91 (see #755012 [0]). But even then, in 1.5.91 and later, NleComposition would send segment events to its NleSources assuming that one source second is equal to one pipeline second. The resulting early EOS event (in the case of a source rate higher than 1.0) would cause it to switch stacks too early, causing confusion in the timeline and spectacularly messed up output. This patch fixes that by searching for rate-changing elements in GESTrackElements such as GESEffects. If such rate-changing elements are found, their final effect on the playing rate is stored in the corresponding NleObject as the 'media duration factor', named like this because the 'media duration', or source duration, of an NleObject can be computed by multiplying the duration with the media duration factor of that object and its parents (this is called the 'recursive media duration factor'). For example, a 4-second NleSource with an NleOperation with a media duration factor of 2.0 will have an 8-second media duration, which means that for playing 4 seconds in the pipeline, the seek event sent to it must span 8 seconds of media. (So, the 'duration' of an NleObject or GES object always refers to its duration in the timeline, not the media duration.) To summarize: * Rate-changing elements are registered in the GESEffectClass (pitch::tempo and pitch::rate are registered by default); * GESTimelineElement is responsible for detecting rate-changing elements and computing the media_duration_factor; * GESTrackElement is responsible for storing the media_duration_factor in NleObject; * NleComposition is responsible for the recursive_media_duration_factor; * The latter property finally fixes media time computations in NleObject. NLE and GES tests are included. [0] https://bugzilla.gnome.org/show_bug.cgi?id=755012 Differential Revision: https://phabricator.freedesktop.org/D276
2015-12-20 13:03:57 +00:00
/* Internal */
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;
}
/* Internal */
GESTimelineElement *
ges_timeline_element_get_copied_from (GESTimelineElement * self)
{
GESTimelineElement *copied_from = self->priv->copied_from;
self->priv->copied_from = NULL;
return copied_from;
}