gstreamer/ges/ges-clip.c

1639 lines
49 KiB
C
Raw Normal View History

2009-08-04 15:13:11 +00:00
/* GStreamer Editing Services
2009-11-30 14:14:25 +00:00
* Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
* 2009 Nokia Corporation
* 2012 Collabora Ltd.
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
2009-08-04 15:13:11 +00:00
*
* 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
2012-11-04 00:25:20 +00:00
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
2009-08-04 15:13:11 +00:00
*/
/**
* SECTION:gesclip
* @title: GESClip
2013-04-23 23:04:04 +00:00
* @short_description: Base Class for objects in a GESLayer
2009-08-04 15:13:11 +00:00
*
2013-01-20 15:42:29 +00:00
* A #GESClip is a 'natural' object which controls one or more
* #GESTrackElement(s) in one or more #GESTrack(s).
2009-08-04 15:13:11 +00:00
*
* Keeps a reference to the #GESTrackElement(s) it created and
* sets/updates their properties.
2009-08-04 15:13:11 +00:00
*/
2013-01-20 15:42:29 +00:00
#include "ges-clip.h"
#include "ges.h"
#include "ges-internal.h"
#include <string.h>
static GList *ges_clip_create_track_elements_func (GESClip * clip,
GESTrackType type);
static gboolean _ripple (GESTimelineElement * element, GstClockTime start);
static gboolean _ripple_end (GESTimelineElement * element, GstClockTime end);
static gboolean _roll_start (GESTimelineElement * element, GstClockTime start);
static gboolean _roll_end (GESTimelineElement * element, GstClockTime end);
static gboolean _trim (GESTimelineElement * element, GstClockTime start);
static void _compute_height (GESContainer * container);
G_DEFINE_ABSTRACT_TYPE (GESClip, ges_clip, GES_TYPE_CONTAINER);
2013-01-20 15:42:29 +00:00
struct _GESClipPrivate
{
/*< public > */
2013-04-23 23:04:04 +00:00
GESLayer *layer;
/*< private > */
/* Set to TRUE when the clip is doing updates of track element
* properties so we don't end up in infinite property update loops
*/
gboolean is_moving;
guint nb_effects;
GList *copied_track_elements;
GESLayer *copied_layer;
2013-01-20 15:42:29 +00:00
/* The formats supported by this Clip */
GESTrackType supportedformats;
};
typedef struct _CheckTrack
{
GESTrack *track;
GESTrackElement *source;
} CheckTrack;
enum
{
PROP_0,
PROP_LAYER,
PROP_SUPPORTED_FORMATS,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
/****************************************************
* Listen to our children *
****************************************************/
/* @min_priority: The absolute minimum priority a child of @container should have
* @max_priority: The absolute maximum priority a child of @container should have
*/
static void
_get_priority_range (GESContainer * container, guint32 * min_priority,
guint32 * max_priority)
{
GESLayer *layer = GES_CLIP (container)->priv->layer;
if (layer) {
*min_priority = _PRIORITY (container) + layer->min_nle_priority;
Cleanup import of GNL and rename gnl to nle for Non Linear Engine Conflicts: ges/ges-track-element.c gnl/Makefile.am gnl/common Conflicts: ges/ges-internal.h ges/ges-track.c ges/ges-utils.c ges/nle/.gitignore ges/nle/gnlmarshal.list ges/nle/nle.h ges/nle/nlecomposition.c ges/nle/nlecomposition.h ges/nle/nleghostpad.c ges/nle/nleghostpad.h ges/nle/nleobject.c ges/nle/nleoperation.c ges/nle/nleoperation.h ges/nle/nlesource.c ges/nle/nlesource.h ges/nle/nletypes.h ges/nle/nleurisource.c ges/nle/nleurisource.h gnl/Makefile.am gnl/gnl.c gnl/gnl.h gnl/gnl/gnl.h gnl/gnl/gnlcomposition.c gnl/gnl/gnlcomposition.h gnl/gnl/gnlghostpad.c gnl/gnl/gnlghostpad.h gnl/gnl/gnlmarshal.list gnl/gnl/gnlobject.c gnl/gnl/gnloperation.c gnl/gnl/gnloperation.h gnl/gnl/gnlsource.c gnl/gnl/gnlsource.h gnl/gnl/gnltypes.h gnl/gnl/gnlurisource.c gnl/gnl/gnlurisource.h gnl/gnlcomposition.c gnl/gnlcomposition.h gnl/gnlghostpad.c gnl/gnlghostpad.h gnl/gnlmarshal.list gnl/gnlobject.c gnl/gnlobject.h gnl/gnloperation.c gnl/gnloperation.h gnl/gnlsource.c gnl/gnlsource.h gnl/gnltypes.h gnl/gnlurisource.c gnl/gnlurisource.h gnl/tests/check/gnl/common.c gnl/tests/check/gnl/common.h gnl/tests/check/gnl/complex.c gnl/tests/check/gnl/gnlcomposition.c gnl/tests/check/gnl/gnloperation.c gnl/tests/check/gnl/gnlsource.c gnl/tests/check/gnl/seek.c gnl/tests/check/gnl/simple.c tests/check/gnl/common.c tests/check/gnl/common.h tests/check/gnl/complex.c tests/check/gnl/gnlcomposition.c tests/check/gnl/gnloperation.c tests/check/gnl/gnlsource.c tests/check/gnl/seek.c tests/check/gnl/simple.c tests/check/nle/common.c tests/check/nle/common.h tests/check/nle/complex.c tests/check/nle/nlecomposition.c tests/check/nle/nleoperation.c tests/check/nle/nlesource.c tests/check/nle/seek.c tests/check/nle/simple.c
2014-08-15 13:48:14 +00:00
*max_priority = layer->max_nle_priority;
} else {
*min_priority = _PRIORITY (container) + MIN_NLE_PRIO;
*max_priority = G_MAXUINT32;
}
}
static void
_child_priority_changed_cb (GESTimelineElement * child,
GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
guint32 min_prio, max_prio;
GST_DEBUG_OBJECT (container, "TimelineElement %p priority changed to %i",
child, _PRIORITY (child));
if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
return;
/* Update mapping */
_get_priority_range (container, &min_prio, &max_prio);
_ges_container_set_priority_offset (container, child,
min_prio - _PRIORITY (child));
}
/*****************************************************
* *
* GESTimelineElement virtual methods implementation *
* *
*****************************************************/
static gboolean
_set_start (GESTimelineElement * element, GstClockTime start)
{
GList *tmp;
GESTimeline *timeline;
GESContainer *container = GES_CONTAINER (element);
GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %"
GST_PTR_FORMAT ")", container->initiated_move);
element->start = start;
g_object_notify (G_OBJECT (element), "start");
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
if (child != container->initiated_move) {
/* Make the snapping happen if in a timeline */
timeline = GES_TIMELINE_ELEMENT_TIMELINE (child);
if (timeline == NULL || ges_timeline_move_object_simple (timeline, child,
NULL, GES_EDGE_NONE, start) == FALSE)
_set_start0 (GES_TIMELINE_ELEMENT (child), start);
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;
return FALSE;
}
static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
{
GList *tmp;
GESContainer *container = GES_CONTAINER (element);
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
if (child != container->initiated_move) {
_set_inpoint0 (child, inpoint);
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
static gboolean
_set_duration (GESTimelineElement * element, GstClockTime duration)
{
GList *tmp;
GESTimeline *timeline;
GESContainer *container = GES_CONTAINER (element);
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
if (child != container->initiated_move) {
/* Make the snapping happen if in a timeline */
timeline = GES_TIMELINE_ELEMENT_TIMELINE (child);
if (timeline == NULL || ges_timeline_trim_object_simple (timeline, child,
NULL, GES_EDGE_END, _START (child) + duration, TRUE) == FALSE)
_set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
static gboolean
_set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
{
GList *tmp;
for (tmp = GES_CONTAINER (element)->children; tmp; tmp = g_list_next (tmp))
ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (tmp->data),
maxduration);
return TRUE;
}
static gboolean
_set_priority (GESTimelineElement * element, guint32 priority)
{
GList *tmp;
guint32 min_prio, max_prio;
GESContainer *container = GES_CONTAINER (element);
_get_priority_range (container, &min_prio, &max_prio);
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
guint32 track_element_prio;
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
gint off = _ges_container_get_priority_offset (container, child);
if (off >= LAYER_HEIGHT) {
GST_ERROR ("%s child %s as a priority offset %d >= LAYER_HEIGHT %d"
" ==> clamping it to 0", GES_TIMELINE_ELEMENT_NAME (element),
GES_TIMELINE_ELEMENT_NAME (child), off, LAYER_HEIGHT);
off = 0;
}
/* We need to remove our current priority from @min_prio
* as it is the absolute minimum priority @child could have
* before we set @container to @priority.
*/
track_element_prio = min_prio - _PRIORITY (container) + priority - off;
if (track_element_prio > max_prio) {
GST_WARNING ("%p priority of %i, is outside of the its containing "
"layer space. (%d/%d) setting it to the maximum it can be",
container, priority, min_prio - _PRIORITY (container) + priority,
max_prio);
track_element_prio = max_prio;
}
_set_priority0 (child, track_element_prio);
}
container->children_control_mode = GES_CHILDREN_UPDATE;
_compute_height (container);
return TRUE;
}
/****************************************************
* *
* GESContainer virtual methods implementation *
* *
****************************************************/
static void
_compute_height (GESContainer * container)
{
GList *tmp;
guint32 min_prio = G_MAXUINT32, max_prio = 0;
if (container->children == NULL) {
/* FIXME Why not 0! */
_ges_container_set_height (container, 1);
return;
}
/* Go over all childs and check if height has changed */
for (tmp = container->children; tmp; tmp = tmp->next) {
guint tck_priority = _PRIORITY (tmp->data);
if (tck_priority < min_prio)
min_prio = tck_priority;
if (tck_priority > max_prio)
max_prio = tck_priority;
}
_ges_container_set_height (container, max_prio - min_prio + 1);
}
static gboolean
_add_child (GESContainer * container, GESTimelineElement * element)
{
GList *tmp;
guint max_prio, min_prio;
GESClipPrivate *priv = GES_CLIP (container)->priv;
g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
/* First make sure we work with a sorted list of GESTimelineElement-s */
_ges_container_sort_children (container);
/* If the TrackElement is an effect:
* - We add it on top of the list of TrackEffect
* - We put all TrackObject present in the TimelineObject
* which are not BaseEffect on top of them
* FIXME: Let the full control over priorities to the user
*/
_get_priority_range (container, &min_prio, &max_prio);
if (GES_IS_BASE_EFFECT (element)) {
GESChildrenControlMode mode = container->children_control_mode;
GST_DEBUG_OBJECT (container, "Adding %ith effect: %" GST_PTR_FORMAT
" Priority %i", priv->nb_effects + 1, element,
min_prio + priv->nb_effects);
tmp = g_list_nth (GES_CONTAINER_CHILDREN (container), priv->nb_effects);
container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
for (; tmp; tmp = tmp->next) {
ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
GES_TIMELINE_ELEMENT_PRIORITY (tmp->data) + 1);
}
_set_priority0 (element, min_prio + priv->nb_effects);
container->children_control_mode = mode;
priv->nb_effects++;
} else {
/* We add the track element on top of the effect list */
_set_priority0 (element, min_prio + priv->nb_effects);
}
/* We set the timing value of the child to ours, we avoid infinite loop
* making sure the container ignore notifies from the child */
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
_set_start0 (element, GES_TIMELINE_ELEMENT_START (container));
_set_inpoint0 (element, GES_TIMELINE_ELEMENT_INPOINT (container));
_set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (container));
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
static gboolean
_remove_child (GESContainer * container, GESTimelineElement * element)
{
if (GES_IS_BASE_EFFECT (element))
GES_CLIP (container)->priv->nb_effects--;
GST_FIXME_OBJECT (container, "We should set other children prios");
return TRUE;
}
static void
_child_added (GESContainer * container, GESTimelineElement * element)
{
g_signal_connect (G_OBJECT (element), "notify::priority",
G_CALLBACK (_child_priority_changed_cb), container);
_child_priority_changed_cb (element, NULL, container);
_compute_height (container);
}
static void
_child_removed (GESContainer * container, GESTimelineElement * element)
{
g_signal_handlers_disconnect_by_func (element, _child_priority_changed_cb,
container);
_compute_height (container);
}
static void
add_clip_to_list (gpointer key, gpointer clip, GList ** list)
{
*list = g_list_prepend (*list, gst_object_ref (clip));
}
static GList *
_ungroup (GESContainer * container, gboolean recursive)
{
GESClip *tmpclip;
GESTrackType track_type;
GESTrackElement *track_element;
gboolean first_obj = TRUE;
GList *tmp, *children, *ret = NULL;
GESClip *clip = GES_CLIP (container);
GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
2013-04-23 23:04:04 +00:00
GESLayer *layer = clip->priv->layer;
GHashTable *_tracktype_clip = g_hash_table_new (g_int_hash, g_int_equal);
/* If there is no TrackElement, just return @container in a list */
if (GES_CONTAINER_CHILDREN (container) == NULL) {
GST_DEBUG ("No TrackElement, simply returning");
return g_list_prepend (ret, container);
}
/* We need a copy of the current list of tracks */
children = ges_container_get_children (container, FALSE);
for (tmp = children; tmp; tmp = tmp->next) {
track_element = GES_TRACK_ELEMENT (tmp->data);
track_type = ges_track_element_get_track_type (track_element);
tmpclip = g_hash_table_lookup (_tracktype_clip, &track_type);
if (tmpclip == NULL) {
if (G_UNLIKELY (first_obj == TRUE)) {
tmpclip = clip;
first_obj = FALSE;
} else {
tmpclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
if (layer) {
/* Add new container to the same layer as @container */
ges_clip_set_moving_from_layer (tmpclip, TRUE);
2013-04-23 23:04:04 +00:00
ges_layer_add_clip (layer, tmpclip);
ges_clip_set_moving_from_layer (tmpclip, FALSE);
}
}
g_hash_table_insert (_tracktype_clip, &track_type, tmpclip);
ges_clip_set_supported_formats (tmpclip, track_type);
}
/* Move trackelement to the container it is supposed to land into */
if (tmpclip != clip) {
/* We need to bump the refcount to avoid the object to be destroyed */
gst_object_ref (track_element);
ges_container_remove (container, GES_TIMELINE_ELEMENT (track_element));
ges_container_add (GES_CONTAINER (tmpclip),
GES_TIMELINE_ELEMENT (track_element));
gst_object_unref (track_element);
}
}
g_list_free_full (children, gst_object_unref);
g_hash_table_foreach (_tracktype_clip, (GHFunc) add_clip_to_list, &ret);
g_hash_table_unref (_tracktype_clip);
return ret;
}
static GESContainer *
_group (GList * containers)
{
CheckTrack *tracks = NULL;
GESTimeline *timeline = NULL;
GESTrackType supported_formats;
2013-04-23 23:04:04 +00:00
GESLayer *layer = NULL;
GList *tmp, *tmpclip, *tmpelement;
GstClockTime start, inpoint, duration;
GESAsset *asset = NULL;
GESContainer *ret = NULL;
guint nb_tracks = 0, i = 0;
start = inpoint = duration = GST_CLOCK_TIME_NONE;
if (!containers)
return NULL;
/* First check if all the containers are clips, if they
* all have the same start/inpoint/duration and are in the same
* layer.
*
* We also need to make sure that all source have been created by the
* same asset, keep the information */
for (tmp = containers; tmp; tmp = tmp->next) {
GESClip *clip;
GESTimeline *tmptimeline;
GESContainer *tmpcontainer;
GESTimelineElement *element;
tmpcontainer = GES_CONTAINER (tmp->data);
element = GES_TIMELINE_ELEMENT (tmp->data);
if (GES_IS_CLIP (element) == FALSE) {
GST_DEBUG ("Can only work with clips");
goto done;
}
clip = GES_CLIP (tmp->data);
tmptimeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
if (!timeline) {
GList *tmptrack;
start = _START (tmpcontainer);
inpoint = _INPOINT (tmpcontainer);
duration = _DURATION (tmpcontainer);
timeline = tmptimeline;
layer = clip->priv->layer;
nb_tracks = g_list_length (GES_TIMELINE_GET_TRACKS (timeline));
tracks = g_new0 (CheckTrack, nb_tracks);
for (tmptrack = GES_TIMELINE_GET_TRACKS (timeline); tmptrack;
tmptrack = tmptrack->next) {
tracks[i].track = tmptrack->data;
i++;
}
} else {
if (start != _START (tmpcontainer) ||
inpoint != _INPOINT (tmpcontainer) ||
duration != _DURATION (tmpcontainer) || clip->priv->layer != layer) {
GST_INFO ("All children must have the same start, inpoint, duration "
" and be in the same layer");
goto done;
} else {
GList *tmp2;
for (tmp2 = GES_CONTAINER_CHILDREN (tmp->data); tmp2; tmp2 = tmp2->next) {
GESTrackElement *track_element = GES_TRACK_ELEMENT (tmp2->data);
if (GES_IS_SOURCE (track_element)) {
guint i;
for (i = 0; i < nb_tracks; i++) {
if (tracks[i].track ==
ges_track_element_get_track (track_element)) {
if (tracks[i].source) {
GST_INFO ("Can not link clips with various source for a "
"same track");
goto done;
}
tracks[i].source = track_element;
break;
}
}
}
}
}
}
}
/* Then check that all sources have been created by the same asset,
* otherwise we can not group */
for (i = 0; i < nb_tracks; i++) {
if (tracks[i].source == NULL) {
GST_FIXME ("Check what to do here as we might end up having a mess");
continue;
}
/* FIXME Check what to do if we have source that have no assets */
if (!asset) {
asset =
ges_extractable_get_asset (GES_EXTRACTABLE
(ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (tracks
[i].source))));
continue;
}
if (asset !=
ges_extractable_get_asset (GES_EXTRACTABLE
(ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (tracks
[i].source))))) {
GST_INFO ("Can not link clips with source coming from different assets");
goto done;
}
}
/* And now pass all TrackElements to the first clip,
* and remove others from the layer (updating the supported formats) */
ret = containers->data;
supported_formats = GES_CLIP (ret)->priv->supportedformats;
for (tmpclip = containers->next; tmpclip; tmpclip = tmpclip->next) {
GESClip *cclip = tmpclip->data;
GList *children = ges_container_get_children (GES_CONTAINER (cclip), FALSE);
for (tmpelement = children; tmpelement; tmpelement = tmpelement->next) {
GESTimelineElement *celement = GES_TIMELINE_ELEMENT (tmpelement->data);
ges_container_remove (GES_CONTAINER (cclip), celement);
ges_container_add (ret, celement);
supported_formats = supported_formats |
ges_track_element_get_track_type (GES_TRACK_ELEMENT (celement));
}
g_list_free_full (children, gst_object_unref);
2013-04-23 23:04:04 +00:00
ges_layer_remove_clip (layer, tmpclip->data);
}
ges_clip_set_supported_formats (GES_CLIP (ret), supported_formats);
done:
if (tracks)
g_free (tracks);
return ret;
}
static gboolean
_edit (GESContainer * container, GList * layers,
gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
{
GList *tmp;
gboolean ret = TRUE;
GESLayer *layer;
if (!G_UNLIKELY (GES_CONTAINER_CHILDREN (container))) {
GST_WARNING_OBJECT (container, "Trying to edit, but not containing"
"any TrackElement yet.");
return FALSE;
}
for (tmp = GES_CONTAINER_CHILDREN (container); tmp; tmp = g_list_next (tmp)) {
if (GES_IS_SOURCE (tmp->data) || GES_IS_TRANSITION (tmp->data)) {
ret &= ges_track_element_edit (tmp->data, layers, mode, edge, position);
break;
}
}
/* Moving to layer */
if (new_layer_priority == -1) {
GST_DEBUG_OBJECT (container, "Not moving new prio %d", new_layer_priority);
} else {
gint priority_offset;
layer = GES_CLIP (container)->priv->layer;
if (layer == NULL) {
GST_WARNING_OBJECT (container, "Not in any layer yet, not moving");
return FALSE;
}
priority_offset = new_layer_priority - ges_layer_get_priority (layer);
ret &= timeline_context_to_layer (layer->timeline, priority_offset);
}
return ret;
}
static void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
GList *tmp;
GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
ccopy->priv->copied_track_elements =
g_list_append (ccopy->priv->copied_track_elements,
ges_timeline_element_copy (tmp->data, TRUE));
}
if (self->priv->copied_layer)
ccopy->priv->copied_layer = g_object_ref (self->priv->copied_layer);
else if (self->priv->layer)
ccopy->priv->copied_layer = g_object_ref (self->priv->layer);
}
static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref,
GstClockTime paste_position)
{
GList *tmp;
GESClip *self = GES_CLIP (element);
GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
if (self->priv->copied_layer)
nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer);
ges_clip_set_moving_from_layer (nclip, TRUE);
if (self->priv->copied_layer)
ges_layer_add_clip (self->priv->copied_layer, nclip);
ges_clip_set_moving_from_layer (nclip, FALSE);
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) {
GESTrackElement *new_trackelement, *trackelement =
GES_TRACK_ELEMENT (tmp->data);
new_trackelement =
GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
(trackelement), FALSE));
if (new_trackelement == NULL) {
GST_WARNING_OBJECT (trackelement, "Could not create a copy");
continue;
}
ges_container_add (GES_CONTAINER (nclip),
GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_bindings (trackelement, new_trackelement,
GST_CLOCK_TIME_NONE);
}
return GES_TIMELINE_ELEMENT (nclip);
}
static gboolean
_lookup_child (GESTimelineElement * self, const gchar * prop_name,
GObject ** child, GParamSpec ** pspec)
{
GList *tmp;
if (GES_TIMELINE_ELEMENT_CLASS (ges_clip_parent_class)->lookup_child (self,
prop_name, child, pspec))
return TRUE;
for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec))
return TRUE;
}
return FALSE;
}
/****************************************************
* *
* GObject virtual methods implementation *
* *
****************************************************/
static void
2013-01-20 15:42:29 +00:00
ges_clip_get_property (GObject * object, guint property_id,
2009-08-04 15:16:31 +00:00
GValue * value, GParamSpec * pspec)
2009-08-04 15:13:11 +00:00
{
GESClip *clip = GES_CLIP (object);
2009-08-04 15:13:11 +00:00
switch (property_id) {
case PROP_LAYER:
g_value_set_object (value, clip->priv->layer);
break;
case PROP_SUPPORTED_FORMATS:
g_value_set_flags (value, clip->priv->supportedformats);
break;
2009-08-04 15:16:31 +00:00
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2009-08-04 15:13:11 +00:00
}
}
static void
2013-01-20 15:42:29 +00:00
ges_clip_set_property (GObject * object, guint property_id,
2009-08-04 15:16:31 +00:00
const GValue * value, GParamSpec * pspec)
2009-08-04 15:13:11 +00:00
{
GESClip *clip = GES_CLIP (object);
2009-08-04 15:13:11 +00:00
switch (property_id) {
case PROP_SUPPORTED_FORMATS:
ges_clip_set_supported_formats (clip, g_value_get_flags (value));
break;
2009-08-04 15:16:31 +00:00
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2009-08-04 15:13:11 +00:00
}
}
static void
ges_clip_finalize (GObject * object)
{
GESClip *self = GES_CLIP (object);
g_list_free_full (self->priv->copied_track_elements, g_object_unref);
G_OBJECT_CLASS (ges_clip_parent_class)->finalize (object);
}
2009-08-04 15:13:11 +00:00
static void
2013-01-20 15:42:29 +00:00
ges_clip_class_init (GESClipClass * klass)
2009-08-04 15:13:11 +00:00
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
2009-08-04 15:13:11 +00:00
2013-01-20 15:42:29 +00:00
g_type_class_add_private (klass, sizeof (GESClipPrivate));
2013-01-20 15:42:29 +00:00
object_class->get_property = ges_clip_get_property;
object_class->set_property = ges_clip_set_property;
object_class->finalize = ges_clip_finalize;
klass->create_track_elements = ges_clip_create_track_elements_func;
klass->create_track_element = NULL;
2010-07-09 09:51:21 +00:00
/**
2013-01-20 15:42:29 +00:00
* GESClip:supported-formats:
*
* The formats supported by the clip.
*/
properties[PROP_SUPPORTED_FORMATS] = g_param_spec_flags ("supported-formats",
"Supported formats", "Formats supported by the file",
GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class, PROP_SUPPORTED_FORMATS,
properties[PROP_SUPPORTED_FORMATS]);
/**
2013-01-20 15:42:29 +00:00
* GESClip:layer:
*
* The GESLayer where this clip is being used. If you want to connect to its
* notify signal you should connect to it with g_signal_connect_after as the
* signal emission can be stop in the first fase.
*/
properties[PROP_LAYER] = g_param_spec_object ("layer", "Layer",
2013-04-23 23:04:04 +00:00
"The GESLayer where this clip is being used.",
GES_TYPE_LAYER, G_PARAM_READABLE);
g_object_class_install_property (object_class, PROP_LAYER,
properties[PROP_LAYER]);
element_class->ripple = _ripple;
element_class->ripple_end = _ripple_end;
element_class->roll_start = _roll_start;
element_class->roll_end = _roll_end;
element_class->trim = _trim;
element_class->set_start = _set_start;
element_class->set_duration = _set_duration;
element_class->set_inpoint = _set_inpoint;
element_class->set_priority = _set_priority;
element_class->set_max_duration = _set_max_duration;
element_class->paste = _paste;
element_class->deep_copy = _deep_copy;
element_class->lookup_child = _lookup_child;
container_class->add_child = _add_child;
container_class->remove_child = _remove_child;
container_class->child_removed = _child_removed;
container_class->child_added = _child_added;
container_class->ungroup = _ungroup;
container_class->group = _group;
container_class->grouping_priority = G_MAXUINT;
container_class->edit = _edit;
2009-08-04 15:13:11 +00:00
}
static void
2013-01-20 15:42:29 +00:00
ges_clip_init (GESClip * self)
2009-08-04 15:13:11 +00:00
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2013-01-20 15:42:29 +00:00
GES_TYPE_CLIP, GESClipPrivate);
/* FIXME, check why it was done this way _DURATION (self) = GST_SECOND; */
self->priv->layer = NULL;
self->priv->nb_effects = 0;
self->priv->is_moving = FALSE;
}
2009-08-06 09:23:01 +00:00
/**
* ges_clip_create_track_element:
* @clip: The origin #GESClip
* @type: The #GESTrackType to create a #GESTrackElement for.
2009-08-06 09:23:01 +00:00
*
* Creates a #GESTrackElement for the provided @type. The clip
* keep a reference to the newly created trackelement, you therefore need to
* call @ges_container_remove when you are done with it.
2009-08-06 09:23:01 +00:00
*
* Returns: (transfer none) (nullable): A #GESTrackElement. Returns NULL if
* the #GESTrackElement could not be created.
2009-08-06 09:23:01 +00:00
*/
GESTrackElement *
ges_clip_create_track_element (GESClip * clip, GESTrackType type)
2009-08-06 09:23:01 +00:00
{
2013-01-20 15:42:29 +00:00
GESClipClass *class;
GESTrackElement *res;
2009-08-06 09:23:01 +00:00
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
GST_DEBUG_OBJECT (clip, "Creating track element for %s",
ges_track_type_name (type));
if (!(type & clip->priv->supportedformats)) {
GST_DEBUG_OBJECT (clip, "We don't support this track type %i", type);
return NULL;
}
class = GES_CLIP_GET_CLASS (clip);
2009-08-06 09:23:01 +00:00
if (G_UNLIKELY (class->create_track_element == NULL)) {
GST_ERROR ("No 'create_track_element' implementation available fo type %s",
G_OBJECT_TYPE_NAME (clip));
return NULL;
}
res = class->create_track_element (clip, type);
return res;
}
2010-07-07 14:51:39 +00:00
/**
* ges_clip_create_track_elements:
* @clip: The origin #GESClip
* @type: The #GESTrackType to create each #GESTrackElement for.
2010-07-07 14:51:39 +00:00
*
* Creates all #GESTrackElements supported by this clip for the track type.
2010-07-07 14:51:39 +00:00
*
* Returns: (element-type GESTrackElement) (transfer full): A #GList of
* newly created #GESTrackElement-s
2010-07-07 14:51:39 +00:00
*/
GList *
ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
2010-07-07 14:51:39 +00:00
{
GList *result = NULL, *tmp, *children;
2013-01-20 15:42:29 +00:00
GESClipClass *klass;
guint max_prio, min_prio;
2010-07-07 14:51:39 +00:00
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
klass = GES_CLIP_GET_CLASS (clip);
2010-07-07 14:51:39 +00:00
if (!(klass->create_track_elements)) {
GST_WARNING ("no GESClip::create_track_elements implentation");
return NULL;
2010-07-07 14:51:39 +00:00
}
2012-04-20 23:02:19 +00:00
GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s",
ges_track_type_name (type));
children = ges_container_get_children (GES_CONTAINER (clip), TRUE);
for (tmp = children; tmp; tmp = tmp->next) {
GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data);
if (!GES_IS_BASE_EFFECT (child) && !ges_track_element_get_track (child) &&
ges_track_element_get_track_type (child) & type) {
GST_DEBUG_OBJECT (clip, "Removing for reusage: %" GST_PTR_FORMAT, child);
result = g_list_prepend (result, g_object_ref (child));
ges_container_remove (GES_CONTAINER (clip), tmp->data);
}
}
g_list_free_full (children, gst_object_unref);
if (!result) {
result = klass->create_track_elements (clip, type);
}
2010-07-07 14:51:39 +00:00
_get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
for (tmp = result; tmp; tmp = tmp->next) {
GESTimelineElement *elem = tmp->data;
_set_start0 (elem, GES_TIMELINE_ELEMENT_START (clip));
_set_inpoint0 (elem, GES_TIMELINE_ELEMENT_INPOINT (clip));
_set_duration0 (elem, GES_TIMELINE_ELEMENT_DURATION (clip));
if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_MAX_DURATION (clip)))
ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (elem),
GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
_set_priority0 (elem, min_prio + clip->priv->nb_effects);
ges_container_add (GES_CONTAINER (clip), elem);
}
return result;
}
/*
* default implementation of GESClipClass::create_track_elements
*/
GList *
ges_clip_create_track_elements_func (GESClip * clip, GESTrackType type)
2010-07-07 14:51:39 +00:00
{
GESTrackElement *result;
2010-07-07 14:51:39 +00:00
GST_DEBUG_OBJECT (clip, "Creating trackelement for track: %s",
ges_track_type_name (type));
result = ges_clip_create_track_element (clip, type);
2010-07-07 14:51:39 +00:00
if (!result) {
GST_DEBUG ("Did not create track element");
return NULL;
2010-07-07 14:51:39 +00:00
}
return g_list_append (NULL, result);
2010-07-07 14:51:39 +00:00
}
void
2013-04-23 23:04:04 +00:00
ges_clip_set_layer (GESClip * clip, GESLayer * layer)
{
if (layer == clip->priv->layer)
return;
clip->priv->layer = layer;
GST_DEBUG ("clip:%p, layer:%p", clip, layer);
/* We do not want to notify the setting of layer = NULL when
* it is actually the result of a move between layer (as we know
* that it will be added to another layer right after, and this
* is what imports here.) */
if (!clip->priv->is_moving)
g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
}
guint32
ges_clip_get_layer_priority (GESClip * clip)
{
if (clip->priv->layer == NULL)
return -1;
return ges_layer_get_priority (clip->priv->layer);
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_set_moving_from_layer:
* @clip: a #GESClip
* @is_moving: %TRUE if you want to start moving @clip to another layer
* %FALSE when you finished moving it.
*
* Sets the clip in a moving to layer state. You might rather use the
2013-01-20 15:42:29 +00:00
* ges_clip_move_to_layer function to move #GESClip-s
* from a layer to another.
**/
void
ges_clip_set_moving_from_layer (GESClip * clip, gboolean is_moving)
{
g_return_if_fail (GES_IS_CLIP (clip));
clip->priv->is_moving = is_moving;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_is_moving_from_layer:
* @clip: a #GESClip
*
* Tells you if the clip is currently moving from a layer to another.
2013-01-20 15:42:29 +00:00
* You might rather use the ges_clip_move_to_layer function to
* move #GESClip-s from a layer to another.
*
* Returns: %TRUE if @clip is currently moving from its current layer
* %FALSE otherwize
**/
gboolean
ges_clip_is_moving_from_layer (GESClip * clip)
{
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
2012-04-20 23:02:19 +00:00
return clip->priv->is_moving;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_move_to_layer:
* @clip: a #GESClip
2013-04-23 23:04:04 +00:00
* @layer: the new #GESLayer
*
* Moves @clip to @layer. If @clip is not in any layer, it adds it to
* @layer, else, it removes it from its current layer, and adds it to @layer.
*
* Returns: %TRUE if @clip could be moved %FALSE otherwize
*/
gboolean
2013-04-23 23:04:04 +00:00
ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
{
gboolean ret;
2013-04-23 23:04:04 +00:00
GESLayer *current_layer;
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
2013-04-23 23:04:04 +00:00
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
current_layer = clip->priv->layer;
if (current_layer == NULL) {
GST_DEBUG ("Not moving %p, only adding it to %p", clip, layer);
2013-04-23 23:04:04 +00:00
return ges_layer_add_clip (layer, clip);
}
GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer,
2013-04-23 23:04:04 +00:00
ges_layer_get_priority (layer));
2012-01-20 20:03:58 +00:00
clip->priv->is_moving = TRUE;
gst_object_ref (clip);
2013-04-23 23:04:04 +00:00
ret = ges_layer_remove_clip (current_layer, clip);
if (!ret) {
gst_object_unref (clip);
return FALSE;
}
2013-04-23 23:04:04 +00:00
ret = ges_layer_add_clip (layer, clip);
clip->priv->is_moving = FALSE;
gst_object_unref (clip);
g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
return ret && (clip->priv->layer == layer);
}
/**
* ges_clip_find_track_element:
* @clip: a #GESClip
* @track: (allow-none): a #GESTrack or NULL
* @type: a #GType indicating the type of track element you are looking
* for or %G_TYPE_NONE if you do not care about the track type.
*
* Finds the #GESTrackElement controlled by @clip that is used in @track. You
* may optionally specify a GType to further narrow search criteria.
*
2011-05-06 17:38:26 +00:00
* Note: If many objects match, then the one with the highest priority will be
* returned.
*
* Returns: (transfer full) (nullable): The #GESTrackElement used by @track,
* else %NULL. Unref after usage
*/
GESTrackElement *
ges_clip_find_track_element (GESClip * clip, GESTrack * track, GType type)
{
GList *tmp;
GESTrackElement *otmp;
GESTrackElement *ret = NULL;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE), NULL);
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) {
otmp = (GESTrackElement *) tmp->data;
if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type))
continue;
if ((track == NULL) || (ges_track_element_get_track (otmp) == track)) {
ret = GES_TRACK_ELEMENT (tmp->data);
gst_object_ref (ret);
break;
}
}
return ret;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_get_layer:
* @clip: a #GESClip
*
2013-04-23 23:04:04 +00:00
* Get the #GESLayer to which this clip belongs.
*
* Returns: (transfer full) (nullable): The #GESLayer where this @clip is being
* used, or %NULL if it is not used on any layer. The caller should unref it
2011-05-06 17:38:26 +00:00
* usage.
*/
2013-04-23 23:04:04 +00:00
GESLayer *
ges_clip_get_layer (GESClip * clip)
{
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
if (clip->priv->layer != NULL)
gst_object_ref (G_OBJECT (clip->priv->layer));
return clip->priv->layer;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_get_top_effects:
* @clip: The origin #GESClip
*
* Get effects applied on @clip
*
* Returns: (transfer full) (element-type GESTrackElement): a #GList of the
* #GESBaseEffect that are applied on @clip order by ascendant priorities.
* The refcount of the objects will be increased. The user will have to
2013-01-26 15:35:19 +00:00
* unref each #GESBaseEffect and free the #GList.
*/
GList *
ges_clip_get_top_effects (GESClip * clip)
{
GList *tmp, *ret;
guint i;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
GST_DEBUG_OBJECT (clip, "Getting the %i top effects", clip->priv->nb_effects);
ret = NULL;
for (tmp = GES_CONTAINER_CHILDREN (clip), i = 0;
i < clip->priv->nb_effects; tmp = tmp->next, i++) {
ret = g_list_append (ret, gst_object_ref (tmp->data));
}
return g_list_sort (ret, (GCompareFunc) element_start_compare);
}
/**
* ges_clip_get_top_effect_index:
* @clip: The origin #GESClip
* @effect: The #GESBaseEffect we want to get the top index from
*
* Gets the index position of an effect.
*
* Returns: The top index of the effect, -1 if something went wrong.
*/
gint
ges_clip_get_top_effect_index (GESClip * clip, GESBaseEffect * effect)
{
guint max_prio, min_prio;
g_return_val_if_fail (GES_IS_CLIP (clip), -1);
g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), -1);
_get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
return GES_TIMELINE_ELEMENT_PRIORITY (effect) - min_prio;
}
/* TODO 2.0 remove as it is Deprecated */
gint
ges_clip_get_top_effect_position (GESClip * clip, GESBaseEffect * effect)
{
return ges_clip_get_top_effect_index (clip, effect);
}
/* TODO 2.0 remove as it is Deprecated */
gboolean
ges_clip_set_top_effect_priority (GESClip * clip,
GESBaseEffect * effect, guint newpriority)
{
return ges_clip_set_top_effect_index (clip, effect, newpriority);
}
/**
* ges_clip_set_top_effect_index:
* @clip: The origin #GESClip
2013-01-26 15:35:19 +00:00
* @effect: The #GESBaseEffect to move
* @newindex: the new index at which to move the @effect inside this
2013-01-20 15:42:29 +00:00
* #GESClip
*
* This is a convenience method that lets you set the index of a top effect.
*
* Returns: %TRUE if @effect was successfuly moved, %FALSE otherwise.
*/
gboolean
ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
guint newindex)
{
gint inc;
GList *tmp;
guint current_prio, min_prio, max_prio;
GESTrackElement *track_element;
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
track_element = GES_TRACK_ELEMENT (effect);
current_prio = _PRIORITY (track_element);
_get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
newindex = newindex + min_prio;
/* We don't change the priority */
if (current_prio == newindex ||
(G_UNLIKELY (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (track_element)) !=
clip)))
return FALSE;
if (newindex > (clip->priv->nb_effects - 1 + min_prio)) {
GST_DEBUG ("You are trying to make %p not a top effect", effect);
return FALSE;
}
if (current_prio > clip->priv->nb_effects + min_prio) {
GST_ERROR ("%p is not a top effect", effect);
return FALSE;
}
_ges_container_sort_children (GES_CONTAINER (clip));
if (_PRIORITY (track_element) < newindex)
inc = -1;
else
inc = +1;
GST_DEBUG_OBJECT (clip, "Setting top effect %" GST_PTR_FORMAT "priority: %i",
effect, newindex);
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *tmpo = GES_TRACK_ELEMENT (tmp->data);
guint tck_priority = _PRIORITY (tmpo);
if (tmpo == track_element)
continue;
if ((inc == +1 && tck_priority >= newindex) ||
(inc == -1 && tck_priority <= newindex)) {
_set_priority0 (GES_TIMELINE_ELEMENT (tmpo), tck_priority + inc);
}
}
_set_priority0 (GES_TIMELINE_ELEMENT (track_element), newindex);
return TRUE;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_split:
* @clip: the #GESClip to split
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
* @position: a #GstClockTime representing the timeline position at which to split
*
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
* The function modifies @clip, and creates another #GESClip so we have two
* clips at the end, splitted at the time specified by @position, as a position
* in the timeline (not in the clip to be split). For example, if
* ges_clip_split is called on a 4-second clip playing from 0:01.00 until
* 0:05.00, with a split position of 0:02.00, this will result in one clip of 1
* second and one clip of 3 seconds, not in two clips of 2 seconds.
*
* The newly created clip will be added to the same layer as @clip is in. This
* implies that @clip must be in a #GESLayer for the operation to be possible.
*
* This method supports clips playing at a different tempo than one second per
* second. For example, splitting a clip with a #GESEffect 'pitch tempo=1.5'
* four seconds after it starts, will set the inpoint of the new clip to six
* seconds after that of the clip to split. For this, the rate-changing
* property must be registered using @ges_effect_class_register_rate_property;
* for the 'pitch' plugin, this is already done.
*
* Returns: (transfer none) (nullable): The newly created #GESClip resulting
* from the splitting or %NULL if the clip can't be split.
*/
2013-01-20 15:42:29 +00:00
GESClip *
ges_clip_split (GESClip * clip, guint64 position)
{
GList *tmp;
2013-01-20 15:42:29 +00:00
GESClip *new_object;
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
GstClockTime start, inpoint, duration, old_duration, new_duration;
gdouble media_duration_factor;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (clip->priv->layer, NULL);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL);
duration = _DURATION (clip);
start = _START (clip);
inpoint = _INPOINT (clip);
if (position >= start + duration || position <= start) {
GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT
" out of boundaries", GST_TIME_ARGS (position));
return NULL;
}
GST_DEBUG_OBJECT (clip, "Spliting at %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
2013-01-20 15:42:29 +00:00
/* Create the new Clip */
new_object = GES_CLIP (ges_timeline_element_copy (GES_TIMELINE_ELEMENT (clip),
FALSE));
GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
2013-01-20 15:42:29 +00:00
/* Set new timing properties on the Clip */
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
media_duration_factor =
ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
(clip));
new_duration = duration + start - position;
old_duration = position - start;
_set_start0 (GES_TIMELINE_ELEMENT (new_object), position);
_set_inpoint0 (GES_TIMELINE_ELEMENT (new_object),
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
inpoint + old_duration * media_duration_factor);
_set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
/* We do not want the timeline to create again TrackElement-s */
ges_clip_set_moving_from_layer (new_object, TRUE);
2013-04-23 23:04:04 +00:00
ges_layer_add_clip (clip->priv->layer, new_object);
ges_clip_set_moving_from_layer (new_object, FALSE);
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
GESTrackElement *new_trackelement, *trackelement =
GES_TRACK_ELEMENT (tmp->data);
new_trackelement =
GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
(trackelement), FALSE));
if (new_trackelement == NULL) {
GST_WARNING_OBJECT (trackelement, "Could not create a copy");
continue;
}
/* Set 'new' track element timing propeties */
_set_start0 (GES_TIMELINE_ELEMENT (new_trackelement), position);
_set_inpoint0 (GES_TIMELINE_ELEMENT (new_trackelement),
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
inpoint + old_duration * media_duration_factor);
_set_duration0 (GES_TIMELINE_ELEMENT (new_trackelement), new_duration);
ges_container_add (GES_CONTAINER (new_object),
GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
GES_TIMELINE_ELEMENT (new_trackelement));
ges_track_element_copy_bindings (trackelement, new_trackelement,
position - start + inpoint);
}
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
_set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
return new_object;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_set_supported_formats:
* @clip: the #GESClip to set supported formats on
* @supportedformats: the #GESTrackType defining formats supported by @clip
*
* Sets the formats supported by the file.
*/
void
ges_clip_set_supported_formats (GESClip * clip, GESTrackType supportedformats)
{
g_return_if_fail (GES_IS_CLIP (clip));
clip->priv->supportedformats = supportedformats;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_get_supported_formats:
* @clip: the #GESClip
*
* Get the formats supported by @clip.
*
* Returns: The formats supported by @clip.
*/
GESTrackType
ges_clip_get_supported_formats (GESClip * clip)
{
g_return_val_if_fail (GES_IS_CLIP (clip), GES_TRACK_TYPE_UNKNOWN);
return clip->priv->supportedformats;
}
gboolean
_ripple (GESTimelineElement * element, GstClockTime start)
{
gboolean ret = TRUE;
GESTimeline *timeline;
GESClip *clip = GES_CLIP (element);
2013-04-23 23:04:04 +00:00
timeline = ges_layer_get_timeline (clip->priv->layer);
if (timeline == NULL) {
GST_DEBUG ("Not in a timeline yet");
return FALSE;
}
if (start > _END (element))
start = _END (element);
if (GES_CONTAINER_CHILDREN (element)) {
GESTrackElement *track_element =
GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
ret = timeline_ripple_object (timeline, track_element, NULL, GES_EDGE_NONE,
start);
}
return ret;
}
static gboolean
_ripple_end (GESTimelineElement * element, GstClockTime end)
{
gboolean ret = TRUE;
GESTimeline *timeline;
GESClip *clip = GES_CLIP (element);
2013-04-23 23:04:04 +00:00
timeline = ges_layer_get_timeline (clip->priv->layer);
if (timeline == NULL) {
GST_DEBUG ("Not in a timeline yet");
return FALSE;
}
if (GES_CONTAINER_CHILDREN (element)) {
GESTrackElement *track_element =
GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
ret = timeline_ripple_object (timeline, track_element, NULL, GES_EDGE_END,
end);
}
return ret;
}
gboolean
_roll_start (GESTimelineElement * element, GstClockTime start)
{
gboolean ret = TRUE;
GESTimeline *timeline;
GESClip *clip = GES_CLIP (element);
2013-04-23 23:04:04 +00:00
timeline = ges_layer_get_timeline (clip->priv->layer);
if (timeline == NULL) {
GST_DEBUG ("Not in a timeline yet");
return FALSE;
}
if (GES_CONTAINER_CHILDREN (element)) {
GESTrackElement *track_element =
GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
ret = timeline_roll_object (timeline, track_element, NULL, GES_EDGE_START,
start);
}
return ret;
}
gboolean
_roll_end (GESTimelineElement * element, GstClockTime end)
{
gboolean ret = TRUE;
GESTimeline *timeline;
GESClip *clip = GES_CLIP (element);
2013-04-23 23:04:04 +00:00
timeline = ges_layer_get_timeline (clip->priv->layer);
if (timeline == NULL) {
GST_DEBUG ("Not in a timeline yet");
return FALSE;
}
if (GES_CONTAINER_CHILDREN (element)) {
GESTrackElement *track_element =
GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
ret = timeline_roll_object (timeline, track_element,
NULL, GES_EDGE_END, end);
}
return ret;
}
gboolean
_trim (GESTimelineElement * element, GstClockTime start)
{
gboolean ret = TRUE;
GESTimeline *timeline;
GESClip *clip = GES_CLIP (element);
2013-04-23 23:04:04 +00:00
timeline = ges_layer_get_timeline (clip->priv->layer);
if (timeline == NULL) {
GST_DEBUG ("Not in a timeline yet");
return FALSE;
}
if (GES_CONTAINER_CHILDREN (element)) {
GESTrackElement *track_element =
GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
GST_DEBUG_OBJECT (element, "Trimming child: %" GST_PTR_FORMAT,
track_element);
ret = timeline_trim_object (timeline, track_element, NULL, GES_EDGE_START,
start);
}
return ret;
}
/**
2013-01-20 15:42:29 +00:00
* ges_clip_add_asset:
* @clip: a #GESClip
* @asset: a #GESAsset with #GES_TYPE_TRACK_ELEMENT as extractable_type
*
* Extracts a #GESTrackElement from @asset and adds it to the @clip.
2013-01-20 15:42:29 +00:00
* Should only be called in order to add operations to a #GESClip,
* ni other cases TrackElement are added automatically when adding the
2013-01-20 15:42:29 +00:00
* #GESClip/#GESAsset to a layer.
*
* Takes a reference on @track_element.
*
* Returns: (transfer none)(allow-none): Created #GESTrackElement or NULL
* if an error happened
*/
GESTrackElement *
ges_clip_add_asset (GESClip * clip, GESAsset * asset)
{
GESTrackElement *element;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type
(asset), GES_TYPE_TRACK_ELEMENT), NULL);
element = GES_TRACK_ELEMENT (ges_asset_extract (asset, NULL));
if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (element)))
return NULL;
return element;
}
/**
* ges_clip_find_track_elements:
* @clip: a #GESClip
* @track: (allow-none): a #GESTrack or NULL
* @track_type: a #GESTrackType indicating the type of tracks in which elements
* should be searched.
* @type: a #GType indicating the type of track element you are looking
* for or %G_TYPE_NONE if you do not care about the track type.
*
* Finds all the #GESTrackElement controlled by @clip that is used in @track. You
* may optionally specify a GType to further narrow search criteria.
*
* Returns: (transfer full) (element-type GESTrackElement): a #GList of the
* #GESTrackElement contained in @clip.
* The refcount of the objects will be increased. The user will have to
* unref each #GESTrackElement and free the #GList.
*/
GList *
ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
GESTrackType track_type, GType type)
{
GList *tmp;
GESTrack *tmptrack;
GESTrackElement *otmp;
GESTrackElement *foundElement;
GList *ret = NULL;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE &&
track_type == GES_TRACK_TYPE_UNKNOWN), NULL);
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) {
otmp = (GESTrackElement *) tmp->data;
if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type))
continue;
tmptrack = ges_track_element_get_track (otmp);
if (((track != NULL && tmptrack == track)) ||
(track_type != GES_TRACK_TYPE_UNKNOWN
&& ges_track_element_get_track_type (otmp) == track_type)) {
foundElement = GES_TRACK_ELEMENT (tmp->data);
ret = g_list_append (ret, gst_object_ref (foundElement));
}
}
return ret;
}