gstreamer/ges/ges-clip.c
Thibault Saunier 6b7c658b6a ges: Make setting start/duration move or trim generic
We were implementing the logic for moving/trimming elements specific
to SourceClip but this was not correct ass the new timeline tree allows
us to handle that for all element types in a generic and nice way.

This make us need to have groups trimming properly implemented in the
timeline tree, leading to some fixes in the group tests.

This adds tests for the various cases known to not be handled properly
by the previous code.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/92
2020-03-09 11:48:37 -03:00

1722 lines
56 KiB
C

/* GStreamer Editing Services
* 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>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gesclip
* @title: GESClip
* @short_description: Base class for elements that occupy a single
* #GESLayer and maintain equal timings of their children
*
* #GESClip-s are the core objects of a #GESLayer. Each clip may exist in
* a single layer but may control several #GESTrackElement-s that span
* several #GESTrack-s. A clip will ensure that all its children share the
* same #GESTimelineElement:start and #GESTimelineElement:duration in
* their tracks, which will match the #GESTimelineElement:start and
* #GESTimelineElement:duration of the clip itself. Therefore, changing
* the timing of the clip will change the timing of the children, and a
* change in the timing of a child will change the timing of the clip and
* subsequently all its siblings. As such, a clip can be treated as a
* singular object in its layer.
*
* For most uses of a #GESTimeline, it is often sufficient to only
* interact with #GESClip-s directly, which will take care of creating and
* organising the elements of the timeline's tracks.
*
* Most subclassed clips will be associated with some *core*
* #GESTrackElement-s. When such a clip is added to a layer in a timeline,
* it will create these children and they will be added to the timeline's
* tracks. You can connect to the #GESContainer::child-added signal to be
* notified of their creation. If a clip will produce several core
* elements of the same #GESTrackElement:track-type but they are destined
* for separate tracks, you should connect to the timeline's
* #GESTimeline::select-tracks-for-object signal to coordinate which
* tracks each element should land in.
*
* The #GESTimelineElement:in-point of the clip will control the
* #GESTimelineElement:in-point of these core elements to be the same
* value.
*
* ## Effects
*
* For #GESSourceClip-s, you may wish to add additional track elements to
* the clip in the form of #GESEffect-s. These can be added to the source
* clip using ges_container_add(), which will take care of adding the
* effect to the timeline's tracks. The new effect will be placed between
* the clip's core track elements and its other effects. As such, the
* newly added effect will be applied to any source data **before** the
* other existing effects. You can change the ordering of effects using
* ges_clip_set_top_effect_index().
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#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);
struct _GESClipPrivate
{
/*< public > */
GESLayer *layer;
/*< private > */
guint nb_effects;
GList *copied_track_elements;
GESLayer *copied_layer;
/* 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];
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESClip, ges_clip, GES_TYPE_CONTAINER);
/****************************************************
* 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;
*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;
GESContainer *container = GES_CONTAINER (element);
GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %"
GST_PTR_FORMAT ")", container->initiated_move);
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_start0 (GES_TIMELINE_ELEMENT (child), start);
}
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
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;
/* FIXME: we should allow the inpoint to be different for children
* that are *not* the core track elements of the clip. E.g. if we
* add a GESEffect to a clip, we should not be editing its inpoint */
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;
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) {
ELEMENT_SET_FLAG (child, GES_TIMELINE_ELEMENT_SET_SIMPLE);
_set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
ELEMENT_UNSET_FLAG (child, GES_TIMELINE_ELEMENT_SET_SIMPLE);
}
}
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;
}
static guint32
_get_layer_priority (GESTimelineElement * element)
{
GESClip *clip = GES_CLIP (element);
if (clip->priv->layer == NULL)
return GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY;
return ges_layer_get_priority (clip->priv->layer);
}
/****************************************************
* *
* 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 TrackElements present in the Clip
* 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));
/* FIXME: we should allow the inpoint to be different for children
* that are *not* the core track elements of the clip. E.g. if we
* add a GESEffect to a clip, we should not be editing its inpoint */
_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);
if (GES_IS_BASE_EFFECT (element)) {
GList *tmp;
guint32 priority;
GESChildrenControlMode mode = container->children_control_mode;
GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
tmp = GES_CONTAINER_CHILDREN (container);
priority =
ges_timeline_element_get_priority (GES_TIMELINE_ELEMENT (element));
for (; tmp; tmp = tmp->next) {
if (ges_timeline_element_get_priority (GES_TIMELINE_ELEMENT (tmp->data)) >
priority) {
ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
GES_TIMELINE_ELEMENT_PRIORITY (tmp->data) - 1);
}
}
container->children_control_mode = mode;
}
_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);
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);
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;
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 {
/* FIXME: we should allow the inpoint to be different if not a
* core track element of the clip.
* E.g. a GESEffect on a GESUriClip */
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);
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 void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
GList *tmp;
GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
if (!self->priv->layer)
return;
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));
}
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_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
if (self->priv->copied_layer) {
if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) {
GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
GES_ARGS (element), GST_TIME_ARGS (paste_position));
return NULL;
}
}
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
ges_clip_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
GESClip *clip = GES_CLIP (object);
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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
ges_clip_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
GESClip *clip = GES_CLIP (object);
switch (property_id) {
case PROP_SUPPORTED_FORMATS:
ges_clip_set_supported_formats (clip, g_value_get_flags (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
ges_clip_dispose (GObject * object)
{
GESClip *self = GES_CLIP (object);
g_list_free_full (self->priv->copied_track_elements, g_object_unref);
self->priv->copied_track_elements = NULL;
g_clear_object (&self->priv->copied_layer);
G_OBJECT_CLASS (ges_clip_parent_class)->dispose (object);
}
static void
ges_clip_class_init (GESClipClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
object_class->get_property = ges_clip_get_property;
object_class->set_property = ges_clip_set_property;
object_class->dispose = ges_clip_dispose;
klass->create_track_elements = ges_clip_create_track_elements_func;
klass->create_track_element = NULL;
/**
* GESClip:supported-formats:
*
* The #GESTrackType-s that the clip supports, which it can create
* #GESTrackElement-s for. Note that this can be a combination of
* #GESTrackType flags to indicate support for several
* #GESTrackElement:track-type elements.
*/
properties[PROP_SUPPORTED_FORMATS] = g_param_spec_flags ("supported-formats",
"Supported formats", "Formats supported by the clip",
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]);
/**
* GESClip:layer:
*
* The layer this clip lies in.
*
* If you want to connect to this property's #GObject::notify signal,
* you should connect to it with g_signal_connect_after() since the
* signal emission may be stopped internally.
*/
properties[PROP_LAYER] = g_param_spec_object ("layer", "Layer",
"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;
element_class->get_layer_priority = _get_layer_priority;
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;
}
static void
ges_clip_init (GESClip * self)
{
self->priv = ges_clip_get_instance_private (self);
self->priv->layer = NULL;
self->priv->nb_effects = 0;
}
/**
* ges_clip_create_track_element:
* @clip: A #GESClip
* @type: The track to create an element for
*
* Creates the core #GESTrackElement of the clip, of the given track type.
*
* Note, unlike ges_clip_create_track_elements(), this does not add the
* created track element to the clip or set their timings.
*
* Returns: (transfer floating) (nullable): The element created
* by @clip, or %NULL if @clip can not provide a track element for the
* given @type or an error occurred.
*/
GESTrackElement *
ges_clip_create_track_element (GESClip * clip, GESTrackType type)
{
GESClipClass *class;
GESTrackElement *res;
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);
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;
}
/**
* ges_clip_create_track_elements:
* @clip: A #GESClip
* @type: The track-type to create elements for
*
* Creates the core #GESTrackElement-s of the clip, of the given track
* type, and adds them to the clip.
*
* Returns: (transfer container) (element-type GESTrackElement): A list of
* the #GESTrackElement-s created by @clip for the given @type, or %NULL
* if no track elements are created or an error occurred.
*/
GList *
ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
{
GList *result = NULL, *tmp, *children;
GESClipClass *klass;
guint max_prio, min_prio;
gboolean readding_effects_only = TRUE;
g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
klass = GES_CLIP_GET_CLASS (clip);
if (!(klass->create_track_elements)) {
GST_WARNING ("no GESClip::create_track_elements implentation");
return NULL;
}
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_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_append (result, g_object_ref (child));
ges_container_remove (GES_CONTAINER (clip), tmp->data);
if (!GES_IS_BASE_EFFECT (child))
readding_effects_only = FALSE;
}
}
g_list_free_full (children, gst_object_unref);
/* FIXME: we need something smarter to determine whether we should
* create the track elements.
* Currently, if the clip contains at least one element with a matching
* track-type, not in a track and not a GESBaseEffect, we will not
* recreate the track elements! But this is not a reliable indicator.
*
* For example, consider a uri clip that creates two audio track
* elements: El_A and El_B. First, we add the clip to a timeline that
* only has a single track: Track_A, and we connect to the timeline's
* ::select-tracks-for-object signal to only allow El_A to end up in
* Track_A. As such, whilst both El_A and El_B are initially created,
* El_B will eventually be removed from the clip since it has no track
* (see clip_track_element_added_cb in ges-timeline.c). As such, we now
* have a clip that only contains El_A.
* Next, we remove Track_A from the timeline. El_A will remain a child
* of the clip, but now has its track unset.
* Next, we add Track_B to the timeline, and we connect to the
* timeline's ::select-tracks-for-object signal to only allow El_B to
* end up in Track_B.
*
* However, since the clip contains an audio track element, that is not
* an effect and has no track set: El_A. Therefore, the
* create_track_elements method below will not be called, so we will not
* have an El_B created for Track_B!
*
* Moreover, even if we did recreate the track elements, we would be
* creating El_A again! We could destroy and recreate El_A instead, or
* we would need a way to determine exactly which elements need to be
* recreated.
*/
if (readding_effects_only) {
result = g_list_concat (result, klass->create_track_elements (clip, type));
}
_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));
/* FIXME: only set the in-point and max-duration for non-core
* elements */
_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)
{
GESTrackElement *result;
GST_DEBUG_OBJECT (clip, "Creating trackelement for track: %s",
ges_track_type_name (type));
result = ges_clip_create_track_element (clip, type);
if (!result) {
GST_DEBUG ("Did not create track element");
return NULL;
}
return g_list_append (NULL, result);
}
void
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 (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING))
g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
}
/**
* 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
* 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));
if (is_moving)
ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
else
ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
}
/**
* ges_clip_is_moving_from_layer:
* @clip: A #GESClip
*
* Tells you if the clip is currently moving from a layer to another.
* 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);
return ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING);
}
/**
* ges_clip_move_to_layer:
* @clip: A #GESClip
* @layer: The new layer
*
* Moves a clip to a new layer. If the clip already exists in a layer, it
* is first removed from its current layer before being added to the new
* layer.
*
* Returns: %TRUE if @clip was successfully moved to @layer.
*/
gboolean
ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
{
gboolean ret;
GESLayer *current_layer;
g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
if (layer->timeline
&& !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
GES_TIMELINE_ELEMENT (clip),
ges_layer_get_priority (layer),
GES_TIMELINE_ELEMENT_START (clip),
GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT " can't move to layer %d",
GES_ARGS (clip), ges_layer_get_priority (layer));
ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
return FALSE;
}
current_layer = clip->priv->layer;
if (current_layer == NULL) {
GST_DEBUG ("Not moving %p, only adding it to %p", clip, layer);
return ges_layer_add_clip (layer, clip);
}
GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer,
ges_layer_get_priority (layer));
gst_object_ref (clip);
ret = ges_layer_remove_clip (current_layer, clip);
if (!ret) {
ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
gst_object_unref (clip);
return FALSE;
}
ret = ges_layer_add_clip (layer, clip);
ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
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): The track to search in, or %NULL to search in
* all tracks
* @type: The type of track element to search for, or `G_TYPE_NONE` to
* match any type
*
* Finds an element controlled by the clip. If @track is given,
* then only the track elements in @track are searched for. If @type is
* given, then this function searches for a track element of the given
* @type.
*
* Note, if multiple track elements in the clip match the given criteria,
* this will return the element amongst them with the highest
* #GESTimelineElement:priority (numerically, the smallest). See
* ges_clip_find_track_elements() if you wish to find all such elements.
*
* Returns: (transfer full) (nullable): The element controlled by
* @clip, in @track, and of the given @type, or %NULL if no such element
* could be found.
*/
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;
}
/**
* ges_clip_get_layer:
* @clip: A #GESClip
*
* Gets the #GESClip:layer of the clip.
*
* Returns: (transfer full) (nullable): The layer @clip is in, or %NULL if
* @clip is not in any layer.
*/
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;
}
/**
* ges_clip_get_top_effects:
* @clip: A #GESClip
*
* Gets the #GESBaseEffect-s that have been added to the clip. The
* returned list is ordered by their internal index in the clip. See
* ges_clip_get_top_effect_index().
*
* Returns: (transfer full) (element-type GESTrackElement): A list of all
* #GESBaseEffect-s that have been added to @clip.
*/
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: A #GESClip
* @effect: The effect we want to get the index of
*
* Gets the internal index of an effect in the clip. The index of effects
* in a clip will run from 0 to n-1, where n is the total number of
* effects. If two effects share the same #GESTrackElement:track, the
* effect with the numerically lower index will be applied to the source
* data **after** the other effect, i.e. output data will always flow from
* a higher index effect to a lower index effect.
*
* Returns: The index of @effect in @clip, or -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: A #GESClip
* @effect: An effect within @clip to move
* @newindex: The index for @effect in @clip
*
* Set the index of an effect within the clip. See
* ges_clip_get_top_effect_index(). The new index must be an existing
* index of the clip. The effect is moved to the new index, and the other
* effects may be shifted in index accordingly to otherwise maintain the
* ordering.
*
* Returns: %TRUE if @effect was successfully moved to @newindex.
*/
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);
if (G_UNLIKELY (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (track_element)) !=
clip))
return FALSE;
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)
return TRUE;
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;
}
/**
* ges_clip_split:
* @clip: The #GESClip to split
* @position: The timeline position at which to perform the split
*
* Splits a clip at the given timeline position into two clips. The clip
* must already have a #GESClip:layer.
*
* The original clip's #GESTimelineElement:duration is reduced such that
* its end point matches the split position. Then a new clip is created in
* the same layer, whose #GESTimelineElement:start matches the split
* position and #GESTimelineElement:duration will be set such that its end
* point matches the old end point of the original clip. Thus, the two
* clips together will occupy the same positions in the timeline as the
* original clip did.
*
* The children of the new clip will be new copies of the original clip's
* children, so it will share the same sources and use the same
* operations.
*
* The new clip will also have its #GESTimelineElement:in-point set so
* that any internal data will appear in the timeline at the same time.
* Thus, when the timeline is played, the playback of data should
* appear the same. This may be complicated by any additional
* #GESEffect-s that have been placed on the original clip that depend on
* the playback time or change the data consumption rate of sources. This
* method will attempt to translate these effects such that the playback
* appears the same. In such complex situations, you may get a better
* result if you place the clip in a separate sub #GESProject, which only
* contains this clip (and its effects), and in the original layer
* create two neighbouring #GESUriClip-s that reference this sub-project,
* but at a different #GESTimelineElement:in-point.
*
* Returns: (transfer none) (nullable): The newly created clip resulting
* from the splitting @clip, or %NULL if @clip can't be split.
*/
GESClip *
ges_clip_split (GESClip * clip, guint64 position)
{
GList *tmp;
GESClip *new_object;
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;
}
old_duration = position - start;
if (!timeline_tree_can_move_element (timeline_get_tree
(GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip),
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip),
start, old_duration, NULL)) {
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would be in an illegal" " state.", GES_ARGS (clip),
GST_TIME_ARGS (position));
return NULL;
}
new_duration = duration + start - position;
if (!timeline_tree_can_move_element (timeline_get_tree
(GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip),
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), position, new_duration,
NULL)) {
GST_WARNING_OBJECT (clip,
"Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
" as timeline would end up in an illegal" " state.", GES_ARGS (clip),
GST_TIME_ARGS (position));
return NULL;
}
GST_DEBUG_OBJECT (clip, "Spliting at %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
/* 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");
/* Set new timing properties on the Clip */
media_duration_factor =
ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
(clip));
_set_start0 (GES_TIMELINE_ELEMENT (new_object), position);
_set_inpoint0 (GES_TIMELINE_ELEMENT (new_object),
inpoint + old_duration * media_duration_factor);
_set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
/* FIXME: this does not check that the new duration of the clip does
* not break the allowed configurations of a timeline!
* E.g., consider a layer with two clips:
*
* [ clip0 ]
* [ clip1 ]
* 0 1 2 3 4 5 6 7 - timeline time
*
* If we split clip1 at time 4 then we would have
*
* [ clip0 ]
* [ clip1 ]
* [ new-clip]
* 0 1 2 3 4 5 6 7 - timeline time
*
* This means that clip1 is entirely covered by clip0! This is allowed
* to occur using ges_clip_split()!
*
* Note this early setting of the duration does not perform such a
* check. Moreover, the later setting of the duration at the end of this
* function also does not perform this check because we set the
* GES_TIMELINE_ELEMENT_SET_SIMPLE flag!
*/
_DURATION (clip) = old_duration;
g_object_notify (G_OBJECT (clip), "duration");
/* We do not want the timeline to create again TrackElement-s */
ges_clip_set_moving_from_layer (new_object, TRUE);
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);
/* FIXME: not all track elements should have their inpoint set,
* e.g. transitions or most effects. But how can we determine
* whether a transition should *always* have a 0 inpoint, or
* whether a transition actually uses the inpoint, but it just so
* happens to be set to 0? */
_set_inpoint0 (GES_TIMELINE_ELEMENT (new_trackelement),
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));
/* FIXME: is position - start + inpoint always the correct splitting
* point for control bindings? What coordinate system are control
* bindings given in? */
/* NOTE: control bindings that are not registered in GES are not
* handled */
ges_track_element_copy_bindings (trackelement, new_trackelement,
position - start + inpoint);
}
/* FIXME: The below leads to a *second* notify signal for duration */
ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
_DURATION (clip) = duration;
_set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
return new_object;
}
/**
* ges_clip_set_supported_formats:
* @clip: A #GESClip
* @supportedformats: The #GESTrackType-s supported by @clip
*
* Sets the #GESClip:supported-formats of the clip. This should normally
* only be called by subclasses, which should be responsible for updating
* its value, rather than the user.
*/
void
ges_clip_set_supported_formats (GESClip * clip, GESTrackType supportedformats)
{
g_return_if_fail (GES_IS_CLIP (clip));
clip->priv->supportedformats = supportedformats;
}
/**
* ges_clip_get_supported_formats:
* @clip: A #GESClip
*
* Gets the #GESClip:supported-formats of the clip.
*
* Returns: The #GESTrackType-s 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)
{
return ges_container_edit (GES_CONTAINER (element), NULL,
ges_timeline_element_get_layer_priority (element),
GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, start);
}
static gboolean
_ripple_end (GESTimelineElement * element, GstClockTime end)
{
return ges_container_edit (GES_CONTAINER (element), NULL,
ges_timeline_element_get_layer_priority (element),
GES_EDIT_MODE_RIPPLE, GES_EDGE_END, end);
}
gboolean
_roll_start (GESTimelineElement * element, GstClockTime start)
{
return ges_container_edit (GES_CONTAINER (element), NULL,
ges_timeline_element_get_layer_priority (element),
GES_EDIT_MODE_ROLL, GES_EDGE_START, start);
}
gboolean
_roll_end (GESTimelineElement * element, GstClockTime end)
{
return ges_container_edit (GES_CONTAINER (element), NULL,
ges_timeline_element_get_layer_priority (element),
GES_EDIT_MODE_ROLL, GES_EDGE_END, end);
}
gboolean
_trim (GESTimelineElement * element, GstClockTime start)
{
return ges_container_edit (GES_CONTAINER (element), NULL, -1,
GES_EDIT_MODE_TRIM, GES_EDGE_START, start);
}
/**
* ges_clip_add_asset:
* @clip: A #GESClip
* @asset: An asset with #GES_TYPE_TRACK_ELEMENT as its
* #GESAsset:extractable-type
*
* Extracts a #GESTrackElement from an asset and adds it to the clip.
* This can be used to add effects that derive from the asset to the
* clip, but this method is not intended to be used to create the core
* elements of the clip.
*
* Returns: (transfer none)(allow-none): The newly created element, or
* %NULL if an error occurred.
*/
/* FIXME: this is not used elsewhere in the GES library */
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): The track to search in, or %NULL to search in
* all tracks
* @track_type: The track-type of the track element to search for, or
* #GES_TRACK_TYPE_UNKNOWN to match any track type
* @type: The type of track element to search for, or %G_TYPE_NONE to
* match any type
*
* Finds the #GESTrackElement-s controlled by the clip that match the
* given criteria. If @track is given as %NULL and @track_type is given as
* #GES_TRACK_TYPE_UNKNOWN, then the search will match all elements in any
* track, including those with no track, and of any
* #GESTrackElement:track-type. Otherwise, if @track is not %NULL, but
* @track_type is #GES_TRACK_TYPE_UNKNOWN, then only the track elements in
* @track are searched for. Otherwise, if @track_type is not
* #GES_TRACK_TYPE_UNKNOWN, but @track is %NULL, then only the track
* elements whose #GESTrackElement:track-type matches @track_type are
* searched for. Otherwise, when both are given, the track elements that
* match **either** criteria are searched for. Therefore, if you wish to
* only find elements in a specific track, you should give the track as
* @track, but you should not give the track's #GESTrack:track-type as
* @track_type because this would also select elements from other tracks
* of the same type.
*
* You may also give @type to _further_ restrict the search to track
* elements of the given @type.
*
* Returns: (transfer full) (element-type GESTrackElement): A list of all
* the #GESTrackElement-s controlled by @clip, in @track or of the given
* @track_type, and of the given @type.
*/
GList *
ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
GESTrackType track_type, GType type)
{
GList *tmp;
GESTrackElement *otmp;
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;
/* TODO 2.0: an AND condition, using a condition like the above type
* check would have made more sense here. Especially when both
* track != NULL and track_type != GES_TRACK_TYPE_UNKNOWN are given */
if ((track == NULL && track_type == GES_TRACK_TYPE_UNKNOWN) ||
(track != NULL && ges_track_element_get_track (otmp) == track) ||
(track_type != GES_TRACK_TYPE_UNKNOWN
&& ges_track_element_get_track_type (otmp) == track_type))
ret = g_list_append (ret, gst_object_ref (otmp));
}
return ret;
}