Merge remote-tracking branch 'origin/0.10'

Conflicts:
	bindings/python/ges.defs
	ges/ges-screenshot.c
	ges/ges-track-video-transition.c
This commit is contained in:
Thibault Saunier 2012-05-26 17:00:50 -04:00
commit f32fdf13b7
13 changed files with 804 additions and 344 deletions

View file

@ -45,12 +45,17 @@ GES_VIDEO_TEST_PATTERN_TYPE
ges_video_test_pattern_get_type
GES_TYPE_PIPELINE_FLAGS
ges_pipeline_flags_get_type
GES_TYPE_EDGE
ges_edge_get_type
GES_TYPE_EDIT_MODE
ges_edit_mode_get_type
</SECTION>
<SECTION>
<FILE>ges-track</FILE>
<TITLE>GESTrack</TITLE>
GESTrack
GESCreateElementForGapFunc
ges_track_audio_raw_new
ges_track_video_raw_new
ges_track_new
@ -61,6 +66,7 @@ ges_track_get_caps
ges_track_enable_update
ges_track_get_objects
ges_track_is_updating
ges_track_set_create_element_for_gap_func
<SUBSECTION Standard>
GESTrackClass
GESTrackPrivate
@ -266,6 +272,7 @@ ges_timeline_is_updating
ges_timeline_get_tracks
ges_timeline_get_layers
ges_timeline_get_track_for_pad
ges_timeline_get_duration
<SUBSECTION Standard>
GESTimelinePrivate
GESTimelineClass
@ -321,7 +328,6 @@ ges_timeline_object_set_duration
ges_timeline_object_get_layer
ges_timeline_object_find_track_object
ges_timeline_object_add_track_object
ges_timeline_object_release_track_object
ges_timeline_object_get_top_effects
ges_timeline_object_get_top_effect_position
ges_timeline_object_move_to_layer
@ -335,6 +341,9 @@ ges_timeline_object_ripple_end
ges_timeline_object_roll_start
ges_timeline_object_roll_end
ges_timeline_object_trim_start
ges_timeline_object_get_max_duration
ges_timeline_object_objects_set_locked
ges_timeline_object_set_max_duration
<SUBSECTION Standard>
GES_TIMELINE_OBJECT_DURATION
GES_TIMELINE_OBJECT_INPOINT

View file

@ -400,6 +400,7 @@ gboolean
ges_formatter_load_from_uri (GESFormatter * formatter, GESTimeline * timeline,
const gchar * uri)
{
gboolean ret = FALSE;
GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter);
g_return_val_if_fail (GES_IS_FORMATTER (formatter), FALSE);
@ -407,10 +408,13 @@ ges_formatter_load_from_uri (GESFormatter * formatter, GESTimeline * timeline,
g_signal_connect (timeline, "discovery-error",
G_CALLBACK (discovery_error_cb), formatter);
if (klass->load_from_uri)
return klass->load_from_uri (formatter, timeline, uri);
if (klass->load_from_uri) {
ges_timeline_enable_update (timeline, FALSE);
ret = klass->load_from_uri (formatter, timeline, uri);
ges_timeline_enable_update (timeline, TRUE);
}
return FALSE;
return ret;
}
static gboolean
@ -599,6 +603,7 @@ discovery_error_cb (GESTimeline * timeline,
static gboolean
project_loaded (GESFormatter * formatter, GESTimeline * timeline)
{
GST_INFO_OBJECT (formatter, "Emit project loaded");
g_signal_emit (formatter, ges_formatter_signals[LOADED_SIGNAL], 0, timeline);
return TRUE;

View file

@ -17,6 +17,11 @@
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION: ges-pitivi-formatter
* @short_description: A formatter for the PiTiVi project file format
*/
#include <libxml/xmlreader.h>
#include <libxml/tree.h>
#include <libxml/parser.h>

View file

@ -39,10 +39,11 @@
typedef struct _GESPitiviFormatterPrivate GESPitiviFormatterPrivate;
/**
* GESPitiviFormatter:
*
* Serializes a #GESTimeline to a file using
* Serializes a #GESTimeline to a file using the Xml PiTiVi file format
*/
struct _GESPitiviFormatter {
@ -55,7 +56,8 @@ struct _GESPitiviFormatter {
gpointer _ges_reserved[GES_PADDING];
};
struct _GESPitiviFormatterClass {
struct _GESPitiviFormatterClass
{
/*< private >*/
GESFormatterClass parent_class;

View file

@ -23,6 +23,16 @@
#include "ges-screenshot.h"
#include "ges-internal.h"
/**
* ges_play_sink_convert_frame:
* @playsink: The olaysink to get last frame from
* @caps: The caps defining the format the return value will have
*
* Get the last buffer @playsink showed
*
* Returns: (transfer full): A #GstSample containing the last frame from
* @playsink in the format defined by the @caps
*/
GstSample *
ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps)
{

View file

@ -215,17 +215,18 @@ objects_start_compare (GESTimelineObject * a, GESTimelineObject * b)
static GList *
track_get_by_layer (GESTimelineLayer * layer, GESTrack * track)
{
GESTrackObject *tckobj;
guint32 layer_prio = layer->priv->priority;
GList *tck_objects_list = NULL, *tmp = NULL, *return_list = NULL;
GESTimelineObject *tl_obj;
tck_objects_list = ges_track_get_objects (track);
for (tmp = tck_objects_list; tmp; tmp = tmp->next) {
tl_obj = ges_track_object_get_timeline_object (tmp->data);
tckobj = GES_TRACK_OBJECT (tmp->data);
if (ges_timeline_object_get_layer (tl_obj) == layer) {
if (tckobj->priority / LAYER_HEIGHT == layer_prio) {
/* We steal the reference from tck_objects_list */
return_list = g_list_append (return_list, tmp->data);
} else
g_object_unref (tmp->data);
}

View file

@ -1749,6 +1749,7 @@ ges_timeline_object_ripple_end (GESTimelineObject * object, guint64 end)
/**
* ges_timeline_object_roll_start:
* @object: The #GESTimelineObject to roll
* @start: The new start of @object in roll mode, it will also adapat
* the in-point of @object according
*

View file

@ -48,6 +48,10 @@ static inline void init_movecontext (MoveContext * mv_ctx);
G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN);
GST_DEBUG_CATEGORY_STATIC (ges_timeline_debug);
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT ges_timeline_debug
#define GES_TIMELINE_PENDINGOBJS_GET_LOCK(timeline) \
(&GES_TIMELINE(timeline)->priv->pendingobjects_lock)
#define GES_TIMELINE_PENDINGOBJS_LOCK(timeline) \
@ -55,8 +59,7 @@ G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN);
#define GES_TIMELINE_PENDINGOBJS_UNLOCK(timeline) \
(g_mutex_unlock(GES_TIMELINE_PENDINGOBJS_GET_LOCK (timeline)))
/**
* The move context is used for the timeline editing modes functions in order to
/* The move context is used for the timeline editing modes functions in order to
* + Ripple / Roll / Slide / Move / Trim
*
* The context aims at avoiding to recalculate values/objects on each call of the
@ -90,7 +93,7 @@ struct _MoveContext
/* Last snapping properties */
GESTrackObject *last_snaped1;
GESTrackObject *last_snaped2;
GstClockTime last_snap_ts;
GstClockTime *last_snap_ts;
};
struct _GESTimelinePrivate
@ -143,6 +146,7 @@ enum
PROP_0,
PROP_DURATION,
PROP_SNAPPING_DISTANCE,
PROP_UPDATE,
PROP_LAST
};
@ -173,6 +177,26 @@ static void
discoverer_discovered_cb (GstDiscoverer * discoverer,
GstDiscovererInfo * info, GError * err, GESTimeline * timeline);
/* Internal methods */
static gboolean
ges_timeline_enable_update_internal (GESTimeline * timeline, gboolean enabled)
{
GList *tmp;
gboolean res = TRUE;
GST_DEBUG_OBJECT (timeline, "%s updates", enabled ? "Enabling" : "Disabling");
for (tmp = timeline->priv->tracks; tmp; tmp = tmp->next) {
if (!ges_track_enable_update (((TrackPrivate *) tmp->data)->track, enabled))
res = FALSE;
}
/* Make sure we reset the context */
timeline->priv->movecontext.needs_move_ctx = TRUE;
return res;
}
/* GObject Standard vmethods*/
static void
ges_timeline_get_property (GObject * object, guint property_id,
@ -189,6 +213,9 @@ ges_timeline_get_property (GObject * object, guint property_id,
case PROP_SNAPPING_DISTANCE:
g_value_set_uint64 (value, timeline->priv->snapping_distance);
break;
case PROP_UPDATE:
g_value_set_boolean (value, ges_timeline_is_updating (timeline));
break;
}
}
@ -202,6 +229,10 @@ ges_timeline_set_property (GObject * object, guint property_id,
case PROP_SNAPPING_DISTANCE:
timeline->priv->snapping_distance = g_value_get_uint64 (value);
break;
case PROP_UPDATE:
ges_timeline_enable_update_internal (timeline,
g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
@ -294,6 +325,27 @@ ges_timeline_class_init (GESTimelineClass * klass)
g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE,
properties[PROP_SNAPPING_DISTANCE]);
/**
* GESTimeline:update:
*
* If %TRUE, then all modifications to objects within the timeline will
* cause a internal pipeline update (if required).
* If %FALSE, then only the timeline start/duration/stop properties
* will be updated, and the internal pipeline will only be updated when the
* property is set back to %TRUE.
*
* It is recommended to temporarily set this property to %FALSE before doing
* more than one modification in the timeline (like adding or moving
* several objects at once) in order to speed up the process, and then setting
* back the property to %TRUE when done.
*/
properties[PROP_UPDATE] = g_param_spec_boolean ("update", "Update",
"Update the internal pipeline on every modification", TRUE,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_UPDATE,
properties[PROP_UPDATE]);
/**
* GESTimeline::track-added
* @timeline: the #GESTimeline
@ -397,6 +449,9 @@ ges_timeline_init (GESTimeline * self)
{
GESTimelinePrivate *priv;
GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline",
GST_DEBUG_FG_YELLOW, "ges timeline");
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
GES_TYPE_TIMELINE, GESTimelinePrivate);
@ -449,6 +504,28 @@ sort_layers (gpointer a, gpointer b)
return 0;
}
static void
timeline_update_duration (GESTimeline * timeline)
{
GstClockTime *cduration;
GSequenceIter *it = g_sequence_get_end_iter (timeline->priv->starts_ends);
it = g_sequence_iter_prev (it);
if (g_sequence_iter_is_end (it))
return;
cduration = g_sequence_get (it);
if (cduration && timeline->priv->duration != *cduration) {
GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %"
GST_TIME_FORMAT, GST_TIME_ARGS (*cduration),
GST_TIME_ARGS (timeline->priv->duration));
timeline->priv->duration = *cduration;
g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]);
}
}
static gint
objects_start_compare (GESTrackObject * a, GESTrackObject * b)
{
@ -553,6 +630,7 @@ sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj)
*end = obj->start + obj->duration;
g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL);
timeline_update_duration (timeline);
}
static inline void
@ -567,6 +645,7 @@ sort_starts_ends_start (GESTimeline * timeline, GESTrackObject * obj)
*start = obj->start;
g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL);
timeline_update_duration (timeline);
}
static inline void
@ -590,6 +669,7 @@ resort_all_starts_ends (GESTimeline * timeline)
}
g_sequence_sort (priv->starts_ends, (GCompareDataFunc) compare_uint64, NULL);
timeline_update_duration (timeline);
}
static inline void
@ -610,7 +690,7 @@ init_movecontext (MoveContext * mv_ctx)
mv_ctx->max_layer_prio = 0;
mv_ctx->last_snaped1 = NULL;
mv_ctx->last_snaped2 = NULL;
mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE;
mv_ctx->last_snap_ts = NULL;
}
static inline void
@ -645,7 +725,7 @@ stop_tracking_for_snapping (GESTimeline * timeline, GESTrackObject * tckobj)
g_sequence_remove (iter_start);
g_sequence_remove (iter_end);
g_sequence_remove (tckobj_iter);
timeline_update_duration (timeline);
}
static void
@ -672,6 +752,8 @@ start_tracking_track_obj (GESTimeline * timeline, GESTrackObject * tckobj)
g_hash_table_insert (priv->by_object, pend, tckobj);
timeline->priv->movecontext.needs_move_ctx = TRUE;
timeline_update_duration (timeline);
}
static inline void
@ -680,11 +762,18 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1,
{
GESTrackObject *obj2;
MoveContext *mv_ctx = &timeline->priv->movecontext;
GstClockTime snap_time = timecode ? *timecode : 0;
GstClockTime last_snap_ts = mv_ctx->last_snap_ts ?
*mv_ctx->last_snap_ts : GST_CLOCK_TIME_NONE;
GST_DEBUG_OBJECT (timeline, "Distance: %" GST_TIME_FORMAT " snapping at %"
GST_TIME_FORMAT, GST_TIME_ARGS (timeline->priv->snapping_distance),
GST_TIME_ARGS (snap_time));
if (timecode == NULL) {
if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) {
g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
mv_ctx->last_snaped1, mv_ctx->last_snaped2, mv_ctx->last_snap_ts);
mv_ctx->last_snaped1, mv_ctx->last_snaped2, last_snap_ts);
/* We then need to recalculate the moving context */
timeline->priv->movecontext.needs_move_ctx = TRUE;
@ -695,19 +784,19 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1,
obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode);
if (mv_ctx->last_snap_ts != *timecode) {
if (last_snap_ts != *timecode) {
g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
mv_ctx->last_snaped1, mv_ctx->last_snaped2, mv_ctx->last_snap_ts);
mv_ctx->last_snaped1, mv_ctx->last_snaped2, (last_snap_ts));
/* We want the snap start signal to be emited anyway */
mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE;
mv_ctx->last_snap_ts = NULL;
}
if (GST_CLOCK_TIME_IS_VALID (mv_ctx->last_snap_ts) == FALSE) {
if (mv_ctx->last_snap_ts == NULL) {
mv_ctx->last_snaped1 = obj1;
mv_ctx->last_snaped2 = obj2;
mv_ctx->last_snap_ts = *timecode;
mv_ctx->last_snap_ts = timecode;
g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
obj1, obj2, *timecode);
@ -724,8 +813,8 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj,
GESTrackObject *tmp_tckobj;
GESTimelineObject *tmp_tlobj, *tlobj;
GstClockTime *last_snap_ts = priv->movecontext.last_snap_ts;
guint64 snap_distance = timeline->priv->snapping_distance;
guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 =
G_MAXUINT64;
@ -733,6 +822,16 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj,
if (snap_distance == 0)
return NULL;
/* If we can just resnap as last snap... do it */
if (last_snap_ts) {
off = timecode > *last_snap_ts ?
timecode - *last_snap_ts : *last_snap_ts - timecode;
if (off <= snap_distance) {
ret = last_snap_ts;
goto done;
}
}
tlobj = ges_track_object_get_timeline_object (trackobj);
iter = g_sequence_search (priv->starts_ends, &timecode,
@ -756,6 +855,9 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj,
nxt_iter = g_sequence_iter_next (nxt_iter);
}
if (ret == NULL)
off = G_MAXUINT64;
prev_iter = g_sequence_iter_prev (iter);
while (!g_sequence_iter_is_begin (prev_iter)) {
prev_tc = g_sequence_get (prev_iter);
@ -773,6 +875,7 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj,
prev_iter = g_sequence_iter_prev (prev_iter);
}
done:
/* We emit the snapping signal only if we snapped with a different value
* than the current one */
if (emit) {
@ -1272,11 +1375,12 @@ ges_timeline_move_object_simple (GESTimeline * timeline,
{
guint64 *snap_end, *snap_st, *cur, off1, off2, end;
GST_DEBUG_OBJECT (timeline, "Moving to %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
end = position + object->duration;
cur = g_hash_table_lookup (timeline->priv->by_end, object);
GST_DEBUG_OBJECT (timeline, "Moving to %" GST_TIME_FORMAT " (end %"
GST_TIME_FORMAT ")", GST_TIME_ARGS (position), GST_TIME_ARGS (end));
snap_end = ges_timeline_snap_position (timeline, object, cur, end, FALSE);
if (snap_end)
off1 = end > *snap_end ? end - *snap_end : *snap_end - end;
@ -1294,13 +1398,9 @@ ges_timeline_move_object_simple (GESTimeline * timeline,
if (snap_end && off1 <= off2) {
position = position + *snap_end - end;
ges_timeline_emit_snappig (timeline, object, snap_end);
GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
} else if (snap_st) {
position = position + *snap_st - position;
ges_timeline_emit_snappig (timeline, object, snap_st);
GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
} else
ges_timeline_emit_snappig (timeline, object, NULL);
@ -1437,6 +1537,7 @@ discoverer_discovered_cb (GstDiscoverer * discoverer,
{
GList *tmp;
GList *stream_list;
GESTimelineObject *tlobj;
GESTrackType tfs_supportedformats;
gboolean found = FALSE;
@ -1521,18 +1622,26 @@ discoverer_discovered_cb (GstDiscoverer * discoverer,
check_image:
tlobj = GES_TIMELINE_OBJECT (tfs);
if (is_image) {
/* don't set max-duration on still images */
g_object_set (tfs, "is_image", (gboolean) TRUE, NULL);
} else {
GstClockTime file_duration, tlobj_max_duration;
/* Properly set duration informations from the discovery */
file_duration = gst_discoverer_info_get_duration (info);
tlobj_max_duration = ges_timeline_object_get_max_duration (tlobj);
if (tlobj_max_duration == G_MAXUINT64)
ges_timeline_object_set_max_duration (tlobj, file_duration);
if (GST_CLOCK_TIME_IS_VALID (tlobj->duration) == FALSE)
ges_timeline_object_set_duration (tlobj, file_duration);
}
/* Continue the processing on tfs */
add_object_to_tracks (timeline, GES_TIMELINE_OBJECT (tfs));
if (!is_image) {
g_object_set (tfs, "max-duration",
gst_discoverer_info_get_duration (info), NULL);
}
add_object_to_tracks (timeline, tlobj);
/* Remove the ref as the timeline file source is no longer needed here */
g_object_unref (tfs);
@ -1545,8 +1654,7 @@ layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object,
if (ges_timeline_object_is_moving_from_layer (object)) {
GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing"
" anything on it", object);
if (!timeline->priv->movecontext.ignore_needs_ctx)
timeline->priv->movecontext.needs_move_ctx = TRUE;
timeline->priv->movecontext.needs_move_ctx = TRUE;
return;
}
@ -1649,7 +1757,12 @@ trackobj_start_changed_cb (GESTrackObject * child,
sort_starts_ends_start (timeline, child);
sort_starts_ends_end (timeline, child);
if (!timeline->priv->movecontext.ignore_needs_ctx)
/* If the timeline is set to snap objects together, we
* are sure that all movement of TrackObject-s are done within
* the moving context, so we do not need to recalculate the
* move context as often */
if (timeline->priv->movecontext.ignore_needs_ctx &&
timeline->priv->snapping_distance == 0)
timeline->priv->movecontext.needs_move_ctx = TRUE;
}
@ -1659,15 +1772,12 @@ trackobj_duration_changed_cb (GESTrackObject * child,
{
sort_starts_ends_end (timeline, child);
if (!timeline->priv->movecontext.ignore_needs_ctx)
timeline->priv->movecontext.needs_move_ctx = TRUE;
}
static void
trackobj_inpoint_changed_cb (GESTrackObject * child,
GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
{
if (!timeline->priv->movecontext.ignore_needs_ctx)
/* If the timeline is set to snap objects together, we
* are sure that all movement of TrackObject-s are done within
* the moving context, so we do not need to recalculate the
* move context as often */
if (timeline->priv->movecontext.ignore_needs_ctx &&
timeline->priv->snapping_distance == 0)
timeline->priv->movecontext.needs_move_ctx = TRUE;
}
@ -1683,8 +1793,6 @@ track_object_added_cb (GESTrack * track, GESTrackObject * object,
G_CALLBACK (trackobj_start_changed_cb), timeline);
g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration",
G_CALLBACK (trackobj_duration_changed_cb), timeline);
g_signal_connect (GES_TRACK_OBJECT (object), "notify::in-point",
G_CALLBACK (trackobj_inpoint_changed_cb), timeline);
}
}
@ -1698,8 +1806,6 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object,
NULL);
g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb,
NULL);
g_signal_handlers_disconnect_by_func (object, trackobj_inpoint_changed_cb,
NULL);
/* Make sure to reinitialise the moving context next time */
timeline->priv->movecontext.needs_move_ctx = TRUE;
@ -1708,32 +1814,72 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object,
}
static void
track_duration_cb (GstElement * track,
GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv)
{
guint64 duration, max_duration = 0;
gchar *padname;
gboolean no_more;
GList *tmp;
for (tmp = timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) {
TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
g_object_get (tr_priv->track, "duration", &duration, NULL);
GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
GST_DEBUG_OBJECT (track, "track duration : %" GST_TIME_FORMAT,
GST_TIME_ARGS (duration));
max_duration = MAX (duration, max_duration);
if (G_UNLIKELY (tr_priv->pad)) {
GST_WARNING ("We are already controlling a pad for this track");
return;
}
if (timeline->priv->duration != max_duration) {
GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %"
GST_TIME_FORMAT, GST_TIME_ARGS (max_duration),
GST_TIME_ARGS (timeline->priv->duration));
/* Remember the pad */
GST_OBJECT_LOCK (track);
tr_priv->pad = pad;
timeline->priv->duration = max_duration;
no_more = TRUE;
for (tmp = tr_priv->timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) {
TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]);
if (!tr_priv->pad) {
GST_LOG ("Found track without pad %p", tr_priv->track);
no_more = FALSE;
}
}
GST_OBJECT_UNLOCK (track);
/* ghost it ! */
GST_DEBUG ("Ghosting pad and adding it to ourself");
padname = g_strdup_printf ("track_%p_src", track);
tr_priv->ghostpad = gst_ghost_pad_new (padname, pad);
g_free (padname);
gst_pad_set_active (tr_priv->ghostpad, TRUE);
gst_element_add_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad);
if (no_more) {
GST_DEBUG ("Signaling no-more-pads");
gst_element_no_more_pads (GST_ELEMENT (tr_priv->timeline));
}
}
static void
pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv)
{
GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
if (G_UNLIKELY (tr_priv->pad != pad)) {
GST_WARNING ("Not the pad we're controlling");
return;
}
if (G_UNLIKELY (tr_priv->ghostpad == NULL)) {
GST_WARNING ("We don't have a ghostpad for this pad !");
return;
}
GST_DEBUG ("Removing ghostpad");
gst_pad_set_active (tr_priv->ghostpad, FALSE);
gst_element_remove_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad);
tr_priv->ghostpad = NULL;
tr_priv->pad = NULL;
}
/* GstElement Virtual methods */
static GstStateChangeReturn
ges_timeline_change_state (GstElement * element, GstStateChange transition)
{
@ -1777,6 +1923,7 @@ ges_timeline_change_state (GstElement * element, GstStateChange transition)
}
/**** API *****/
/**
* ges_timeline_new:
*
@ -2044,71 +2191,6 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
return TRUE;
}
static void
pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv)
{
gchar *padname;
gboolean no_more;
GList *tmp;
GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
if (G_UNLIKELY (tr_priv->pad)) {
GST_WARNING ("We are already controlling a pad for this track");
return;
}
/* Remember the pad */
GST_OBJECT_LOCK (track);
tr_priv->pad = pad;
no_more = TRUE;
for (tmp = tr_priv->timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) {
TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
if (!tr_priv->pad) {
GST_LOG ("Found track without pad %p", tr_priv->track);
no_more = FALSE;
}
}
GST_OBJECT_UNLOCK (track);
/* ghost it ! */
GST_DEBUG ("Ghosting pad and adding it to ourself");
padname = g_strdup_printf ("track_%p_src", track);
tr_priv->ghostpad = gst_ghost_pad_new (padname, pad);
g_free (padname);
gst_pad_set_active (tr_priv->ghostpad, TRUE);
gst_element_add_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad);
if (no_more) {
GST_DEBUG ("Signaling no-more-pads");
gst_element_no_more_pads (GST_ELEMENT (tr_priv->timeline));
}
}
static void
pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv)
{
GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
if (G_UNLIKELY (tr_priv->pad != pad)) {
GST_WARNING ("Not the pad we're controlling");
return;
}
if (G_UNLIKELY (tr_priv->ghostpad == NULL)) {
GST_WARNING ("We don't have a ghostpad for this pad !");
return;
}
GST_DEBUG ("Removing ghostpad");
gst_pad_set_active (tr_priv->ghostpad, FALSE);
gst_element_remove_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad);
tr_priv->ghostpad = NULL;
tr_priv->pad = NULL;
}
/**
* ges_timeline_add_track:
* @timeline: a #GESTimeline
@ -2169,11 +2251,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
/* ensure that each existing timeline object has the opportunity to create a
* track object for this track*/
/* We connect to the duration change notify, so we can update
* our duration accordingly */
g_signal_connect (G_OBJECT (track), "notify::duration",
G_CALLBACK (track_duration_cb), timeline);
/* We connect to the object for the timeline editing mode management */
g_signal_connect (G_OBJECT (track), "track-object-added",
G_CALLBACK (track_object_added_cb), timeline);
@ -2192,8 +2269,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
g_list_free (objects);
}
track_duration_cb (GST_ELEMENT (track), NULL, timeline);
return TRUE;
}
@ -2246,8 +2321,6 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
/* Remove pad-added/-removed handlers */
g_signal_handlers_disconnect_by_func (track, pad_added_cb, tr_priv);
g_signal_handlers_disconnect_by_func (track, pad_removed_cb, tr_priv);
g_signal_handlers_disconnect_by_func (track, track_duration_cb,
tr_priv->track);
g_signal_handlers_disconnect_by_func (track, track_object_added_cb, timeline);
g_signal_handlers_disconnect_by_func (track, track_object_removed_cb,
timeline);
@ -2382,18 +2455,27 @@ ges_timeline_is_updating (GESTimeline * timeline)
gboolean
ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled)
{
GList *tmp;
gboolean res = TRUE;
if (ges_timeline_enable_update_internal (timeline, enabled)) {
g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_UPDATE]);
GST_DEBUG_OBJECT (timeline, "%s updates", enabled ? "Enabling" : "Disabling");
for (tmp = timeline->priv->tracks; tmp; tmp = tmp->next) {
if (!ges_track_enable_update (((TrackPrivate *) tmp->data)->track, enabled))
res = FALSE;
return TRUE;
}
/* Make sure we reset the context */
timeline->priv->movecontext.needs_move_ctx = TRUE;
return res;
return FALSE;
}
/**
* ges_timeline_get_duration
* @timeline: a #GESTimeline
*
* Get the current duration of @timeline
*
* Returns: The current duration of @timeline
*/
GstClockTime
ges_timeline_get_duration (GESTimeline * timeline)
{
g_return_val_if_fail (GES_IS_TIMELINE (timeline), GST_CLOCK_TIME_NONE);
return timeline->priv->duration;
}

View file

@ -102,6 +102,8 @@ GList *ges_timeline_get_tracks (GESTimeline *timeline);
gboolean ges_timeline_enable_update(GESTimeline * timeline, gboolean enabled);
gboolean ges_timeline_is_updating (GESTimeline * timeline);
GstClockTime ges_timeline_get_duration (GESTimeline *timeline);
G_END_DECLS
#endif /* _GES_TIMELINE */

View file

@ -1554,7 +1554,7 @@ ges_track_object_set_max_duration (GESTrackObject * object, guint64 maxduration)
*
* Copies @object
*
* Returns: The newly create #GESTrackObject, copied from @object
* Returns: (transfer full): The newly create #GESTrackObject, copied from @object
*
* Since: 0.10.XX
*/

View file

@ -35,20 +35,35 @@
G_DEFINE_TYPE (GESTrack, ges_track, GST_TYPE_BIN);
/* Structure that represents gaps and keep knowledge
* of the gaps filled in the track */
typedef struct
{
GstElement *gnlobj;
GstClockTime start;
GstClockTime duration;
GESTrack *track;
} Gap;
struct _GESTrackPrivate
{
/*< private > */
GESTimeline *timeline;
GList *trackobjects;
GSequence *tckobjs_by_start;
GList *gaps;
guint64 duration;
GstCaps *caps;
GstElement *composition; /* The composition associated with this track */
GstElement *background; /* The backgrond, handle the gaps in the track */
GstPad *srcpad; /* The source GhostPad */
gboolean updating;
/* Virtual method to create GstElement that fill gaps */
GESCreateElementForGapFunc create_element_for_gaps;
};
enum
@ -72,13 +87,329 @@ static void
pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track);
static void composition_duration_cb (GstElement * composition, GParamSpec * arg
G_GNUC_UNUSED, GESTrack * obj);
/* Private methods/functions/callbacks */
/* Utilities */
static gint
objects_start_compare (GESTrackObject * a, GESTrackObject * b,
gpointer user_data)
{
if (a->start == b->start) {
if (a->priority < b->priority)
return -1;
if (a->priority > b->priority)
return 1;
return 0;
}
if (a->start < b->start)
return -1;
if (a->start > b->start)
return 1;
return 0;
}
static void
add_trackobj_to_list_foreach (GESTrackObject * trackobj, GList ** list)
{
g_object_ref (trackobj);
*list = g_list_prepend (*list, trackobj);
}
static Gap *
gap_new (GESTrack * track, GstClockTime start, GstClockTime duration)
{
GstElement *gnlsrc, *elem;
Gap *new_gap;
gnlsrc = gst_element_factory_make ("gnlsource", NULL);
elem = track->priv->create_element_for_gaps (track);
if (G_UNLIKELY (gst_bin_add (GST_BIN (gnlsrc), elem) == FALSE)) {
GST_WARNING_OBJECT (track, "Could not create gap filler");
if (gnlsrc)
gst_object_unref (gnlsrc);
if (elem)
gst_object_unref (elem);
return NULL;
}
if (G_UNLIKELY (gst_bin_add (GST_BIN (track->priv->composition),
gnlsrc) == FALSE)) {
GST_WARNING_OBJECT (track, "Could not add gap to the composition");
if (gnlsrc)
gst_object_unref (gnlsrc);
if (elem)
gst_object_unref (elem);
return NULL;
}
new_gap = g_slice_new (Gap);
new_gap->start = start;
new_gap->duration = duration;
new_gap->track = track;
new_gap->gnlobj = gst_object_ref (gnlsrc);
g_object_set (gnlsrc, "start", new_gap->start, "duration", new_gap->duration,
"priority", 0, NULL);
GST_DEBUG_OBJECT (track,
"Created gap with start %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT,
GST_TIME_ARGS (new_gap->start), GST_TIME_ARGS (new_gap->duration));
return new_gap;
}
static void
free_gap (Gap * gap)
{
GESTrack *track = gap->track;
GST_DEBUG_OBJECT (track, "Removed gap with start %" GST_TIME_FORMAT
" duration %" GST_TIME_FORMAT, GST_TIME_ARGS (gap->start),
GST_TIME_ARGS (gap->duration));
gst_bin_remove (GST_BIN (track->priv->composition), gap->gnlobj);
gst_element_set_state (gap->gnlobj, GST_STATE_NULL);
gst_object_unref (gap->gnlobj);
g_slice_free (Gap, gap);
}
static inline void
update_gaps (GESTrack * track)
{
Gap *gap;
GSequenceIter *it;
GESTrackObject *tckobj;
GstClockTime start, end, duration = 0, timeline_duration;
GESTrackPrivate *priv = track->priv;
if (priv->create_element_for_gaps == NULL) {
GST_INFO ("Not filling the gaps as no create_element_for_gaps vmethod"
" provided");
return;
}
/* 1- Remove all gaps */
g_list_free_full (priv->gaps, (GDestroyNotify) free_gap);
priv->gaps = NULL;
/* 2- And recalculate gaps */
for (it = g_sequence_get_begin_iter (priv->tckobjs_by_start);
g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) {
tckobj = g_sequence_get (it);
start = GES_TRACK_OBJECT_START (tckobj);
end = start + GES_TRACK_OBJECT_DURATION (tckobj);
if (start > duration) {
/* 3- Fill gap */
gap = gap_new (track, duration, start - duration);
if (G_LIKELY (gap != NULL))
priv->gaps = g_list_prepend (priv->gaps, gap);
}
duration = MAX (duration, end);
}
/* 4- Add a gap at the end of the timeline if needed */
if (priv->timeline) {
g_object_get (priv->timeline, "duration", &timeline_duration, NULL);
if (duration < timeline_duration) {
gap = gap_new (track, duration, timeline_duration - duration);
if (G_LIKELY (gap != NULL)) {
priv->gaps = g_list_prepend (priv->gaps, gap);
}
priv->duration = timeline_duration;
}
}
}
static inline void
resort_and_fill_gaps (GESTrack * track)
{
g_sequence_sort (track->priv->tckobjs_by_start,
(GCompareDataFunc) objects_start_compare, NULL);
if (track->priv->updating == TRUE) {
update_gaps (track);
}
}
/* callbacks */
static void
timeline_duration_changed_cb (GESTimeline * timeline,
GParamSpec * arg, GESTrack * track)
{
GESTrackPrivate *priv = track->priv;
/* Remove the last gap on the timeline if not needed anymore */
if (priv->updating == TRUE && priv->gaps) {
Gap *gap = (Gap *) priv->gaps->data;
GstClockTime tl_duration = ges_timeline_get_duration (timeline);
if (gap->start + gap->duration > tl_duration) {
free_gap (gap);
priv->gaps = g_list_remove (priv->gaps, gap);
}
}
}
static void
sort_track_objects_cb (GESTrackObject * child,
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track);
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track)
{
resort_and_fill_gaps (track);
}
static void timeline_duration_cb (GESTimeline * timeline,
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track);
static void
pad_added_cb (GstElement * element, GstPad * pad, GESTrack * track)
{
GESTrackPrivate *priv = track->priv;
GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
/* ghost the pad */
priv->srcpad = gst_ghost_pad_new ("src", pad);
gst_pad_set_active (priv->srcpad, TRUE);
gst_element_add_pad (GST_ELEMENT (track), priv->srcpad);
GST_DEBUG ("done");
}
static void
pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track)
{
GESTrackPrivate *priv = track->priv;
GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
if (G_LIKELY (priv->srcpad)) {
gst_pad_set_active (priv->srcpad, FALSE);
gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad);
priv->srcpad = NULL;
}
GST_DEBUG ("done");
}
static void
composition_duration_cb (GstElement * composition,
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track)
{
guint64 duration;
g_object_get (composition, "duration", &duration, NULL);
if (track->priv->duration != duration) {
GST_DEBUG_OBJECT (track,
"composition duration : %" GST_TIME_FORMAT " current : %"
GST_TIME_FORMAT, GST_TIME_ARGS (duration),
GST_TIME_ARGS (track->priv->duration));
track->priv->duration = duration;
g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_DURATION]);
}
}
/* GESCreateElementForGapFunc Gaps filler for raw tracks */
static GstElement *
create_element_for_raw_audio_gap (GESTrack * track)
{
GstElement *elem;
elem = gst_element_factory_make ("audiotestsrc", NULL);
g_object_set (elem, "wave", 4, NULL);
return elem;
}
static GstElement *
create_element_for_raw_video_gap (GESTrack * track)
{
GstElement *elem;
elem = gst_element_factory_make ("videotestsrc", NULL);
g_object_set (elem, "pattern", 2, NULL);
return elem;
}
/* Remove @object from @track, but keeps it in the sequence this is needed
* when finalizing as we can not change a GSequence at the same time we are
* accessing it
*/
static gboolean
remove_object_internal (GESTrack * track, GESTrackObject * object)
{
GESTrackPrivate *priv;
GstElement *gnlobject;
GST_DEBUG_OBJECT (track, "object:%p", object);
priv = track->priv;
if (G_UNLIKELY (ges_track_object_get_track (object) != track)) {
GST_WARNING ("Object belongs to another track");
return FALSE;
}
if ((gnlobject = ges_track_object_get_gnlobject (object))) {
GST_DEBUG ("Removing GnlObject '%s' from composition '%s'",
GST_ELEMENT_NAME (gnlobject), GST_ELEMENT_NAME (priv->composition));
if (!gst_bin_remove (GST_BIN (priv->composition), gnlobject)) {
GST_WARNING ("Failed to remove gnlobject from composition");
return FALSE;
}
gst_element_set_state (gnlobject, GST_STATE_NULL);
}
g_signal_handlers_disconnect_by_func (object, sort_track_objects_cb, NULL);
ges_track_object_set_track (object, NULL);
g_signal_emit (track, ges_track_signals[TRACK_OBJECT_REMOVED], 0,
GES_TRACK_OBJECT (object));
g_object_unref (object);
return TRUE;
}
static void
dispose_tckobjs_foreach (GESTrackObject * tckobj, GESTrack * track)
{
GESTimelineObject *tlobj;
tlobj = ges_track_object_get_timeline_object (tckobj);
remove_object_internal (track, tckobj);
ges_timeline_object_release_track_object (tlobj, tckobj);
}
/* GObject virtual methods */
static void
ges_track_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
@ -124,12 +455,11 @@ ges_track_dispose (GObject * object)
GESTrack *track = (GESTrack *) object;
GESTrackPrivate *priv = track->priv;
while (priv->trackobjects) {
GESTrackObject *trobj = GES_TRACK_OBJECT (priv->trackobjects->data);
ges_track_remove_object (track, trobj);
ges_timeline_object_release_track_object ((GESTimelineObject *)
ges_track_object_get_timeline_object (trobj), trobj);
}
/* Remove all TrackObjects and drop our reference */
g_sequence_foreach (track->priv->tckobjs_by_start,
(GFunc) dispose_tckobjs_foreach, track);
g_sequence_free (priv->tckobjs_by_start);
g_list_free_full (priv->gaps, (GDestroyNotify) free_gap);
if (priv->composition) {
gst_bin_remove (GST_BIN (object), priv->composition);
@ -144,48 +474,6 @@ ges_track_dispose (GObject * object)
G_OBJECT_CLASS (ges_track_parent_class)->dispose (object);
}
static void
ges_track_constructed (GObject * object)
{
GObjectClass *parent_class;
GstElement *background = NULL;
GESTrack *self = GES_TRACK (object);
GESTrackPrivate *priv = self->priv;
if ((priv->background = gst_element_factory_make ("gnlsource", "background"))) {
g_object_set (priv->background, "priority", G_MAXUINT, NULL);
switch (self->type) {
case GES_TRACK_TYPE_VIDEO:
background = gst_element_factory_make ("videotestsrc", "background");
g_object_set (background, "pattern", 2, NULL);
break;
case GES_TRACK_TYPE_AUDIO:
background = gst_element_factory_make ("audiotestsrc", "background");
g_object_set (background, "wave", 4, NULL);
break;
default:
break;
}
if (background) {
if (!gst_bin_add (GST_BIN (priv->background), background))
GST_ERROR ("Couldn't add background");
else {
if (!gst_bin_add (GST_BIN (priv->composition), priv->background))
GST_ERROR ("Couldn't add background");
}
}
}
parent_class = ges_track_parent_class;
if (parent_class->constructed)
parent_class->constructed (object);
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
ges_track_finalize (GObject * object)
{
@ -203,7 +491,6 @@ ges_track_class_init (GESTrackClass * klass)
object_class->set_property = ges_track_set_property;
object_class->dispose = ges_track_dispose;
object_class->finalize = ges_track_finalize;
object_class->constructed = ges_track_constructed;
/**
* GESTrack:caps
@ -286,6 +573,9 @@ ges_track_init (GESTrack * self)
self->priv->composition = gst_element_factory_make ("gnlcomposition", NULL);
self->priv->updating = TRUE;
self->priv->tckobjs_by_start = g_sequence_new (NULL);
self->priv->create_element_for_gaps = NULL;
self->priv->gaps = NULL;
g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration",
G_CALLBACK (composition_duration_cb), self);
@ -336,6 +626,10 @@ ges_track_video_raw_new (void)
GstCaps *caps = gst_caps_new_empty_simple ("video/x-raw");
track = ges_track_new (GES_TRACK_TYPE_VIDEO, caps);
ges_track_set_create_element_for_gap_func (track,
create_element_for_raw_video_gap);
GST_DEBUG_OBJECT (track, "New raw video track");
return track;
}
@ -355,7 +649,11 @@ ges_track_audio_raw_new (void)
GstCaps *caps = gst_caps_new_empty_simple ("audio/x-raw");
track = ges_track_new (GES_TRACK_TYPE_AUDIO, caps);
ges_track_set_create_element_for_gap_func (track,
create_element_for_raw_audio_gap);
GST_DEBUG_OBJECT (track, "New raw audio track %p",
track->priv->create_element_for_gaps);
return track;
}
@ -372,12 +670,12 @@ ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
GST_DEBUG ("track:%p, timeline:%p", track, timeline);
if (track->priv->timeline)
g_signal_handlers_disconnect_by_func (track,
timeline_duration_cb, track->priv->timeline);
g_signal_handlers_disconnect_by_func (track->priv->timeline,
timeline_duration_changed_cb, track);
if (timeline)
g_signal_connect (G_OBJECT (timeline), "notify::duration",
G_CALLBACK (timeline_duration_cb), track);
g_signal_connect (timeline, "notify::duration",
G_CALLBACK (timeline_duration_changed_cb), track);
track->priv->timeline = timeline;
}
@ -409,26 +707,6 @@ ges_track_set_caps (GESTrack * track, const GstCaps * caps)
/* FIXME : update all trackobjects ? */
}
/* FIXME : put the compare function in the utils */
static gint
objects_start_compare (GESTrackObject * a, GESTrackObject * b)
{
if (a->start == b->start) {
if (a->priority < b->priority)
return -1;
if (a->priority > b->priority)
return 1;
return 0;
}
if (a->start < b->start)
return -1;
if (a->start > b->start)
return 1;
return 0;
}
/**
* ges_track_add_object:
* @track: a #GESTrack
@ -471,9 +749,8 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object)
}
g_object_ref_sink (object);
track->priv->trackobjects =
g_list_insert_sorted (track->priv->trackobjects, object,
(GCompareFunc) objects_start_compare);
g_sequence_insert_sorted (track->priv->tckobjs_by_start, object,
(GCompareDataFunc) objects_start_compare, NULL);
g_signal_emit (track, ges_track_signals[TRACK_OBJECT_ADDED], 0,
GES_TRACK_OBJECT (object));
@ -481,9 +758,14 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object)
g_signal_connect (GES_TRACK_OBJECT (object), "notify::start",
G_CALLBACK (sort_track_objects_cb), track);
g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration",
G_CALLBACK (sort_track_objects_cb), track);
g_signal_connect (GES_TRACK_OBJECT (object), "notify::priority",
G_CALLBACK (sort_track_objects_cb), track);
resort_and_fill_gaps (track);
return TRUE;
}
@ -500,14 +782,11 @@ GList *
ges_track_get_objects (GESTrack * track)
{
GList *ret = NULL;
GList *tmp;
g_return_val_if_fail (GES_IS_TRACK (track), NULL);
for (tmp = track->priv->trackobjects; tmp; tmp = tmp->next) {
ret = g_list_prepend (ret, tmp->data);
g_object_ref (tmp->data);
}
g_sequence_foreach (track->priv->tckobjs_by_start,
(GFunc) add_trackobj_to_list_foreach, &ret);
ret = g_list_reverse (ret);
return ret;
@ -529,121 +808,25 @@ ges_track_get_objects (GESTrack * track)
gboolean
ges_track_remove_object (GESTrack * track, GESTrackObject * object)
{
GSequenceIter *it;
GESTrackPrivate *priv;
GstElement *gnlobject;
g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE);
GST_DEBUG ("track:%p, object:%p", track, object);
priv = track->priv;
if (G_UNLIKELY (ges_track_object_get_track (object) != track)) {
GST_WARNING ("Object belongs to another track");
return FALSE;
if (remove_object_internal (track, object) == TRUE) {
it = g_sequence_lookup (priv->tckobjs_by_start, object,
(GCompareDataFunc) objects_start_compare, NULL);
g_sequence_remove (it);
resort_and_fill_gaps (track);
return TRUE;
}
if ((gnlobject = ges_track_object_get_gnlobject (object))) {
GST_DEBUG ("Removing GnlObject '%s' from composition '%s'",
GST_ELEMENT_NAME (gnlobject), GST_ELEMENT_NAME (priv->composition));
if (!gst_bin_remove (GST_BIN (priv->composition), gnlobject)) {
GST_WARNING ("Failed to remove gnlobject from composition");
return FALSE;
}
gst_element_set_state (gnlobject, GST_STATE_NULL);
}
g_signal_handlers_disconnect_by_func (object, sort_track_objects_cb, NULL);
ges_track_object_set_track (object, NULL);
g_signal_emit (track, ges_track_signals[TRACK_OBJECT_REMOVED], 0,
GES_TRACK_OBJECT (object));
priv->trackobjects = g_list_remove (priv->trackobjects, object);
g_object_unref (object);
return TRUE;
}
static void
pad_added_cb (GstElement * element, GstPad * pad, GESTrack * track)
{
GESTrackPrivate *priv = track->priv;
GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
/* ghost the pad */
priv->srcpad = gst_ghost_pad_new ("src", pad);
gst_pad_set_active (priv->srcpad, TRUE);
gst_element_add_pad (GST_ELEMENT (track), priv->srcpad);
GST_DEBUG ("done");
}
static void
pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track)
{
GESTrackPrivate *priv = track->priv;
GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
if (G_LIKELY (priv->srcpad)) {
gst_pad_set_active (priv->srcpad, FALSE);
gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad);
priv->srcpad = NULL;
}
GST_DEBUG ("done");
}
static void
composition_duration_cb (GstElement * composition,
GParamSpec * arg G_GNUC_UNUSED, GESTrack * obj)
{
guint64 duration;
g_object_get (composition, "duration", &duration, NULL);
if (obj->priv->duration != duration) {
GST_DEBUG ("composition duration : %" GST_TIME_FORMAT " current : %"
GST_TIME_FORMAT, GST_TIME_ARGS (duration),
GST_TIME_ARGS (obj->priv->duration));
obj->priv->duration = duration;
g_object_notify_by_pspec (G_OBJECT (obj), properties[ARG_DURATION]);
}
}
static void
sort_track_objects_cb (GESTrackObject * child,
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track)
{
track->priv->trackobjects =
g_list_sort (track->priv->trackobjects,
(GCompareFunc) objects_start_compare);
}
static void
timeline_duration_cb (GESTimeline * timeline,
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track)
{
guint64 duration;
g_object_get (timeline, "duration", &duration, NULL);
g_object_set (GES_TRACK (track)->priv->background, "duration", duration,
NULL);
GST_DEBUG_OBJECT (track, "Updating background duration to %" GST_TIME_FORMAT,
GST_TIME_ARGS (duration));
return FALSE;
}
/**
@ -702,6 +885,9 @@ ges_track_enable_update (GESTrack * track, gboolean enabled)
track->priv->updating = update;
if (update == TRUE)
resort_and_fill_gaps (track);
return update == enabled;
}
@ -720,3 +906,23 @@ ges_track_is_updating (GESTrack * track)
return track->priv->updating;
}
/**
* ges_track_set_create_element_for_gap_func:
* @track: a #GESTrack
* @func: (scope notified): The #GESCreateElementForGapFunc that will be used
* to create #GstElement to fill gaps
*
* Sets the function that should be used to create the GstElement used to fill gaps.
* To avoid to provide such a function we advice you to use the
* #ges_track_audio_raw_new and #ges_track_video_raw_new constructor when possible.
*/
void
ges_track_set_create_element_for_gap_func (GESTrack * track,
GESCreateElementForGapFunc func)
{
g_return_if_fail (GES_IS_TRACK (track));
track->priv->create_element_for_gaps = func;
}

View file

@ -47,6 +47,18 @@ G_BEGIN_DECLS
typedef struct _GESTrackPrivate GESTrackPrivate;
/**
* GESCreateElementForGapFunc:
* @track: the #GESTrack
*
* A function that will be called to create the #GstElement that will be used
* as a source to fill the gaps in @track.
*
* Returns: A #GstElement (must be a source) that will be used to
* fill the gaps (periods of time in @track that containes no source).
*/
typedef GstElement* (*GESCreateElementForGapFunc) (GESTrack *track);
/**
* GESTrack:
* @type: a #GESTrackType indicting the basic type of the track.
@ -107,6 +119,10 @@ gboolean ges_track_is_updating (GESTrack * track);
GList* ges_track_get_objects (GESTrack *track);
void
ges_track_set_create_element_for_gap_func (GESTrack *track,
GESCreateElementForGapFunc func);
G_END_DECLS
#endif /* _GES_TRACK */

View file

@ -206,6 +206,126 @@ GST_START_TEST (test_test_source_in_layer)
GST_END_TEST;
static gint
find_composition_func (GstElement * element)
{
GstElementFactory *fac = gst_element_get_factory (element);
const gchar *name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (fac));
if (g_strcmp0 (name, "gnlcomposition") == 0)
return 0;
return 1;
}
static GstElement *
find_composition (GESTrack * track)
{
GstIterator *it = gst_bin_iterate_recurse (GST_BIN (track));
GstElement *ret =
gst_iterator_find_custom (it, (GCompareFunc) find_composition_func, NULL);
gst_iterator_free (it);
return ret;
}
#define gap_object_check(gnlobj, start, duration, priority) \
{ \
guint64 pstart, pdur, pprio; \
g_object_get (gnlobj, "start", &pstart, "duration", &pdur, \
"priority", &pprio, NULL); \
assert_equals_uint64 (pstart, start); \
assert_equals_uint64 (pdur, duration); \
assert_equals_int (pprio, priority); \
}
GST_START_TEST (test_gap_filling_basic)
{
GESTrack *track;
GESTrackObject *trackobject, *trackobject1, *trackobject2;
/*GESTimelineLayer *layer; */
GESTimelineObject *object, *object1, *object2;
GstElement *gnlsrc, *gnlsrc1, *gap = NULL;
GstElement *composition;
GList *tmp;
ges_init ();
track = ges_track_audio_raw_new ();
fail_unless (track != NULL);
composition = find_composition (track);
fail_unless (composition != NULL);
object = GES_TIMELINE_OBJECT (ges_timeline_test_source_new ());
fail_unless (object != NULL);
/* Set some properties */
g_object_set (object, "start", (guint64) 0, "duration", (guint64) 5, NULL);
assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 0);
assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (object), 5);
trackobject = ges_timeline_object_create_track_object (object, track);
ges_timeline_object_add_track_object (object, trackobject);
fail_unless (ges_track_add_object (track, trackobject));
fail_unless (trackobject != NULL);
gnlsrc = ges_track_object_get_gnlobject (trackobject);
fail_unless (gnlsrc != NULL);
/* Check that trackobject has the same properties */
assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 0);
assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject), 5);
/* Check no gap were wrongly added */
assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 1);
object1 = GES_TIMELINE_OBJECT (ges_timeline_test_source_new ());
fail_unless (object1 != NULL);
g_object_set (object1, "start", (guint64) 15, "duration", (guint64) 5, NULL);
assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object1), 15);
assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (object1), 5);
trackobject1 = ges_timeline_object_create_track_object (object1, track);
ges_timeline_object_add_track_object (object1, trackobject1);
fail_unless (ges_track_add_object (track, trackobject1));
fail_unless (trackobject1 != NULL);
gnlsrc1 = ges_track_object_get_gnlobject (trackobject1);
fail_unless (gnlsrc1 != NULL);
/* Check that trackobject1 has the same properties */
assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject1), 15);
assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject1), 5);
/* Check the gap as properly been added */
assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 3);
for (tmp = GST_BIN_CHILDREN (composition); tmp; tmp = tmp->next) {
GstElement *tmp_gnlobj = GST_ELEMENT (tmp->data);
if (tmp_gnlobj != gnlsrc && tmp_gnlobj != gnlsrc1) {
gap = tmp_gnlobj;
}
}
fail_unless (gap != NULL);
gap_object_check (gap, 5, 10, 0)
object2 = GES_TIMELINE_OBJECT (ges_timeline_test_source_new ());
fail_unless (object2 != NULL);
g_object_set (object2, "start", (guint64) 35, "duration", (guint64) 5, NULL);
trackobject2 = ges_timeline_object_create_track_object (object2, track);
ges_timeline_object_add_track_object (object2, trackobject2);
fail_unless (ges_track_add_object (track, trackobject2));
fail_unless (trackobject2 != NULL);
assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject2), 35);
assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject2), 5);
assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 5);
gst_object_unref (track);
}
GST_END_TEST;
static Suite *
ges_suite (void)
{
@ -217,6 +337,7 @@ ges_suite (void)
tcase_add_test (tc_chain, test_test_source_basic);
tcase_add_test (tc_chain, test_test_source_properties);
tcase_add_test (tc_chain, test_test_source_in_layer);
tcase_add_test (tc_chain, test_gap_filling_basic);
return s;
}