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_video_test_pattern_get_type
GES_TYPE_PIPELINE_FLAGS GES_TYPE_PIPELINE_FLAGS
ges_pipeline_flags_get_type ges_pipeline_flags_get_type
GES_TYPE_EDGE
ges_edge_get_type
GES_TYPE_EDIT_MODE
ges_edit_mode_get_type
</SECTION> </SECTION>
<SECTION> <SECTION>
<FILE>ges-track</FILE> <FILE>ges-track</FILE>
<TITLE>GESTrack</TITLE> <TITLE>GESTrack</TITLE>
GESTrack GESTrack
GESCreateElementForGapFunc
ges_track_audio_raw_new ges_track_audio_raw_new
ges_track_video_raw_new ges_track_video_raw_new
ges_track_new ges_track_new
@ -61,6 +66,7 @@ ges_track_get_caps
ges_track_enable_update ges_track_enable_update
ges_track_get_objects ges_track_get_objects
ges_track_is_updating ges_track_is_updating
ges_track_set_create_element_for_gap_func
<SUBSECTION Standard> <SUBSECTION Standard>
GESTrackClass GESTrackClass
GESTrackPrivate GESTrackPrivate
@ -266,6 +272,7 @@ ges_timeline_is_updating
ges_timeline_get_tracks ges_timeline_get_tracks
ges_timeline_get_layers ges_timeline_get_layers
ges_timeline_get_track_for_pad ges_timeline_get_track_for_pad
ges_timeline_get_duration
<SUBSECTION Standard> <SUBSECTION Standard>
GESTimelinePrivate GESTimelinePrivate
GESTimelineClass GESTimelineClass
@ -321,7 +328,6 @@ ges_timeline_object_set_duration
ges_timeline_object_get_layer ges_timeline_object_get_layer
ges_timeline_object_find_track_object ges_timeline_object_find_track_object
ges_timeline_object_add_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_effects
ges_timeline_object_get_top_effect_position ges_timeline_object_get_top_effect_position
ges_timeline_object_move_to_layer ges_timeline_object_move_to_layer
@ -335,6 +341,9 @@ ges_timeline_object_ripple_end
ges_timeline_object_roll_start ges_timeline_object_roll_start
ges_timeline_object_roll_end ges_timeline_object_roll_end
ges_timeline_object_trim_start 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> <SUBSECTION Standard>
GES_TIMELINE_OBJECT_DURATION GES_TIMELINE_OBJECT_DURATION
GES_TIMELINE_OBJECT_INPOINT GES_TIMELINE_OBJECT_INPOINT

View file

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

View file

@ -17,6 +17,11 @@
* Boston, MA 02111-1307, USA. * 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/xmlreader.h>
#include <libxml/tree.h> #include <libxml/tree.h>
#include <libxml/parser.h> #include <libxml/parser.h>

View file

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

View file

@ -23,6 +23,16 @@
#include "ges-screenshot.h" #include "ges-screenshot.h"
#include "ges-internal.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 * GstSample *
ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps) ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps)
{ {

View file

@ -215,17 +215,18 @@ objects_start_compare (GESTimelineObject * a, GESTimelineObject * b)
static GList * static GList *
track_get_by_layer (GESTimelineLayer * layer, GESTrack * track) 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; GList *tck_objects_list = NULL, *tmp = NULL, *return_list = NULL;
GESTimelineObject *tl_obj;
tck_objects_list = ges_track_get_objects (track); tck_objects_list = ges_track_get_objects (track);
for (tmp = tck_objects_list; tmp; tmp = tmp->next) { 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 */ /* We steal the reference from tck_objects_list */
return_list = g_list_append (return_list, tmp->data); return_list = g_list_append (return_list, tmp->data);
} else } else
g_object_unref (tmp->data); 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: * ges_timeline_object_roll_start:
* @object: The #GESTimelineObject to roll
* @start: The new start of @object in roll mode, it will also adapat * @start: The new start of @object in roll mode, it will also adapat
* the in-point of @object according * 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); 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) \ #define GES_TIMELINE_PENDINGOBJS_GET_LOCK(timeline) \
(&GES_TIMELINE(timeline)->priv->pendingobjects_lock) (&GES_TIMELINE(timeline)->priv->pendingobjects_lock)
#define GES_TIMELINE_PENDINGOBJS_LOCK(timeline) \ #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) \ #define GES_TIMELINE_PENDINGOBJS_UNLOCK(timeline) \
(g_mutex_unlock(GES_TIMELINE_PENDINGOBJS_GET_LOCK (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 * + Ripple / Roll / Slide / Move / Trim
* *
* The context aims at avoiding to recalculate values/objects on each call of the * The context aims at avoiding to recalculate values/objects on each call of the
@ -90,7 +93,7 @@ struct _MoveContext
/* Last snapping properties */ /* Last snapping properties */
GESTrackObject *last_snaped1; GESTrackObject *last_snaped1;
GESTrackObject *last_snaped2; GESTrackObject *last_snaped2;
GstClockTime last_snap_ts; GstClockTime *last_snap_ts;
}; };
struct _GESTimelinePrivate struct _GESTimelinePrivate
@ -143,6 +146,7 @@ enum
PROP_0, PROP_0,
PROP_DURATION, PROP_DURATION,
PROP_SNAPPING_DISTANCE, PROP_SNAPPING_DISTANCE,
PROP_UPDATE,
PROP_LAST PROP_LAST
}; };
@ -173,6 +177,26 @@ static void
discoverer_discovered_cb (GstDiscoverer * discoverer, discoverer_discovered_cb (GstDiscoverer * discoverer,
GstDiscovererInfo * info, GError * err, GESTimeline * timeline); 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*/ /* GObject Standard vmethods*/
static void static void
ges_timeline_get_property (GObject * object, guint property_id, 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: case PROP_SNAPPING_DISTANCE:
g_value_set_uint64 (value, timeline->priv->snapping_distance); g_value_set_uint64 (value, timeline->priv->snapping_distance);
break; 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: case PROP_SNAPPING_DISTANCE:
timeline->priv->snapping_distance = g_value_get_uint64 (value); timeline->priv->snapping_distance = g_value_get_uint64 (value);
break; break;
case PROP_UPDATE:
ges_timeline_enable_update_internal (timeline,
g_value_get_boolean (value));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 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, g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE,
properties[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 * GESTimeline::track-added
* @timeline: the #GESTimeline * @timeline: the #GESTimeline
@ -397,6 +449,9 @@ ges_timeline_init (GESTimeline * self)
{ {
GESTimelinePrivate *priv; GESTimelinePrivate *priv;
GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline",
GST_DEBUG_FG_YELLOW, "ges timeline");
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
GES_TYPE_TIMELINE, GESTimelinePrivate); GES_TYPE_TIMELINE, GESTimelinePrivate);
@ -449,6 +504,28 @@ sort_layers (gpointer a, gpointer b)
return 0; 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 static gint
objects_start_compare (GESTrackObject * a, GESTrackObject * b) objects_start_compare (GESTrackObject * a, GESTrackObject * b)
{ {
@ -553,6 +630,7 @@ sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj)
*end = obj->start + obj->duration; *end = obj->start + obj->duration;
g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL);
timeline_update_duration (timeline);
} }
static inline void static inline void
@ -567,6 +645,7 @@ sort_starts_ends_start (GESTimeline * timeline, GESTrackObject * obj)
*start = obj->start; *start = obj->start;
g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL);
timeline_update_duration (timeline);
} }
static inline void static inline void
@ -590,6 +669,7 @@ resort_all_starts_ends (GESTimeline * timeline)
} }
g_sequence_sort (priv->starts_ends, (GCompareDataFunc) compare_uint64, NULL); g_sequence_sort (priv->starts_ends, (GCompareDataFunc) compare_uint64, NULL);
timeline_update_duration (timeline);
} }
static inline void static inline void
@ -610,7 +690,7 @@ init_movecontext (MoveContext * mv_ctx)
mv_ctx->max_layer_prio = 0; mv_ctx->max_layer_prio = 0;
mv_ctx->last_snaped1 = NULL; mv_ctx->last_snaped1 = NULL;
mv_ctx->last_snaped2 = NULL; mv_ctx->last_snaped2 = NULL;
mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE; mv_ctx->last_snap_ts = NULL;
} }
static inline void static inline void
@ -645,7 +725,7 @@ stop_tracking_for_snapping (GESTimeline * timeline, GESTrackObject * tckobj)
g_sequence_remove (iter_start); g_sequence_remove (iter_start);
g_sequence_remove (iter_end); g_sequence_remove (iter_end);
g_sequence_remove (tckobj_iter); g_sequence_remove (tckobj_iter);
timeline_update_duration (timeline);
} }
static void static void
@ -672,6 +752,8 @@ start_tracking_track_obj (GESTimeline * timeline, GESTrackObject * tckobj)
g_hash_table_insert (priv->by_object, pend, tckobj); g_hash_table_insert (priv->by_object, pend, tckobj);
timeline->priv->movecontext.needs_move_ctx = TRUE; timeline->priv->movecontext.needs_move_ctx = TRUE;
timeline_update_duration (timeline);
} }
static inline void static inline void
@ -680,11 +762,18 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1,
{ {
GESTrackObject *obj2; GESTrackObject *obj2;
MoveContext *mv_ctx = &timeline->priv->movecontext; 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 (timecode == NULL) {
if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) { if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) {
g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, 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 */ /* We then need to recalculate the moving context */
timeline->priv->movecontext.needs_move_ctx = TRUE; 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); 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, 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 */ /* 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_snaped1 = obj1;
mv_ctx->last_snaped2 = obj2; 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, g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
obj1, obj2, *timecode); obj1, obj2, *timecode);
@ -724,8 +813,8 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj,
GESTrackObject *tmp_tckobj; GESTrackObject *tmp_tckobj;
GESTimelineObject *tmp_tlobj, *tlobj; GESTimelineObject *tmp_tlobj, *tlobj;
GstClockTime *last_snap_ts = priv->movecontext.last_snap_ts;
guint64 snap_distance = timeline->priv->snapping_distance; guint64 snap_distance = timeline->priv->snapping_distance;
guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 = guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 =
G_MAXUINT64; G_MAXUINT64;
@ -733,6 +822,16 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj,
if (snap_distance == 0) if (snap_distance == 0)
return NULL; 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); tlobj = ges_track_object_get_timeline_object (trackobj);
iter = g_sequence_search (priv->starts_ends, &timecode, 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); nxt_iter = g_sequence_iter_next (nxt_iter);
} }
if (ret == NULL)
off = G_MAXUINT64;
prev_iter = g_sequence_iter_prev (iter); prev_iter = g_sequence_iter_prev (iter);
while (!g_sequence_iter_is_begin (prev_iter)) { while (!g_sequence_iter_is_begin (prev_iter)) {
prev_tc = g_sequence_get (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); prev_iter = g_sequence_iter_prev (prev_iter);
} }
done:
/* We emit the snapping signal only if we snapped with a different value /* We emit the snapping signal only if we snapped with a different value
* than the current one */ * than the current one */
if (emit) { if (emit) {
@ -1272,11 +1375,12 @@ ges_timeline_move_object_simple (GESTimeline * timeline,
{ {
guint64 *snap_end, *snap_st, *cur, off1, off2, end; 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; end = position + object->duration;
cur = g_hash_table_lookup (timeline->priv->by_end, object); 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); snap_end = ges_timeline_snap_position (timeline, object, cur, end, FALSE);
if (snap_end) if (snap_end)
off1 = end > *snap_end ? end - *snap_end : *snap_end - 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) { if (snap_end && off1 <= off2) {
position = position + *snap_end - end; position = position + *snap_end - end;
ges_timeline_emit_snappig (timeline, object, snap_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) { } else if (snap_st) {
position = position + *snap_st - position; position = position + *snap_st - position;
ges_timeline_emit_snappig (timeline, object, snap_st); ges_timeline_emit_snappig (timeline, object, snap_st);
GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
} else } else
ges_timeline_emit_snappig (timeline, object, NULL); ges_timeline_emit_snappig (timeline, object, NULL);
@ -1437,6 +1537,7 @@ discoverer_discovered_cb (GstDiscoverer * discoverer,
{ {
GList *tmp; GList *tmp;
GList *stream_list; GList *stream_list;
GESTimelineObject *tlobj;
GESTrackType tfs_supportedformats; GESTrackType tfs_supportedformats;
gboolean found = FALSE; gboolean found = FALSE;
@ -1521,18 +1622,26 @@ discoverer_discovered_cb (GstDiscoverer * discoverer,
check_image: check_image:
tlobj = GES_TIMELINE_OBJECT (tfs);
if (is_image) { if (is_image) {
/* don't set max-duration on still images */ /* don't set max-duration on still images */
g_object_set (tfs, "is_image", (gboolean) TRUE, NULL); 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 */ /* Continue the processing on tfs */
add_object_to_tracks (timeline, GES_TIMELINE_OBJECT (tfs)); add_object_to_tracks (timeline, tlobj);
if (!is_image) {
g_object_set (tfs, "max-duration",
gst_discoverer_info_get_duration (info), NULL);
}
/* Remove the ref as the timeline file source is no longer needed here */ /* Remove the ref as the timeline file source is no longer needed here */
g_object_unref (tfs); 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)) { if (ges_timeline_object_is_moving_from_layer (object)) {
GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing" GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing"
" anything on it", object); " 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; return;
} }
@ -1649,7 +1757,12 @@ trackobj_start_changed_cb (GESTrackObject * child,
sort_starts_ends_start (timeline, child); sort_starts_ends_start (timeline, child);
sort_starts_ends_end (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; timeline->priv->movecontext.needs_move_ctx = TRUE;
} }
@ -1659,15 +1772,12 @@ trackobj_duration_changed_cb (GESTrackObject * child,
{ {
sort_starts_ends_end (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
timeline->priv->movecontext.needs_move_ctx = TRUE; * 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 */
static void if (timeline->priv->movecontext.ignore_needs_ctx &&
trackobj_inpoint_changed_cb (GESTrackObject * child, timeline->priv->snapping_distance == 0)
GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
{
if (!timeline->priv->movecontext.ignore_needs_ctx)
timeline->priv->movecontext.needs_move_ctx = TRUE; 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_CALLBACK (trackobj_start_changed_cb), timeline);
g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration", g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration",
G_CALLBACK (trackobj_duration_changed_cb), timeline); 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); NULL);
g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb, g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb,
NULL); NULL);
g_signal_handlers_disconnect_by_func (object, trackobj_inpoint_changed_cb,
NULL);
/* Make sure to reinitialise the moving context next time */ /* Make sure to reinitialise the moving context next time */
timeline->priv->movecontext.needs_move_ctx = TRUE; timeline->priv->movecontext.needs_move_ctx = TRUE;
@ -1708,32 +1814,72 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object,
} }
static void static void
track_duration_cb (GstElement * track, pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv)
GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
{ {
guint64 duration, max_duration = 0; gchar *padname;
gboolean no_more;
GList *tmp; GList *tmp;
for (tmp = timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) { GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
TrackPrivate *tr_priv = (TrackPrivate *) tmp->data;
g_object_get (tr_priv->track, "duration", &duration, NULL);
GST_DEBUG_OBJECT (track, "track duration : %" GST_TIME_FORMAT, if (G_UNLIKELY (tr_priv->pad)) {
GST_TIME_ARGS (duration)); GST_WARNING ("We are already controlling a pad for this track");
max_duration = MAX (duration, max_duration); return;
} }
if (timeline->priv->duration != max_duration) { /* Remember the pad */
GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %" GST_OBJECT_LOCK (track);
GST_TIME_FORMAT, GST_TIME_ARGS (max_duration), tr_priv->pad = pad;
GST_TIME_ARGS (timeline->priv->duration));
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 static GstStateChangeReturn
ges_timeline_change_state (GstElement * element, GstStateChange transition) ges_timeline_change_state (GstElement * element, GstStateChange transition)
{ {
@ -1777,6 +1923,7 @@ ges_timeline_change_state (GstElement * element, GstStateChange transition)
} }
/**** API *****/
/** /**
* ges_timeline_new: * ges_timeline_new:
* *
@ -2044,71 +2191,6 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
return TRUE; 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: * ges_timeline_add_track:
* @timeline: a #GESTimeline * @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 /* ensure that each existing timeline object has the opportunity to create a
* track object for this track*/ * 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 */ /* We connect to the object for the timeline editing mode management */
g_signal_connect (G_OBJECT (track), "track-object-added", g_signal_connect (G_OBJECT (track), "track-object-added",
G_CALLBACK (track_object_added_cb), timeline); G_CALLBACK (track_object_added_cb), timeline);
@ -2192,8 +2269,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
g_list_free (objects); g_list_free (objects);
} }
track_duration_cb (GST_ELEMENT (track), NULL, timeline);
return TRUE; return TRUE;
} }
@ -2246,8 +2321,6 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
/* Remove pad-added/-removed handlers */ /* 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_added_cb, tr_priv);
g_signal_handlers_disconnect_by_func (track, pad_removed_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_added_cb, timeline);
g_signal_handlers_disconnect_by_func (track, track_object_removed_cb, g_signal_handlers_disconnect_by_func (track, track_object_removed_cb,
timeline); timeline);
@ -2382,18 +2455,27 @@ ges_timeline_is_updating (GESTimeline * timeline)
gboolean gboolean
ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled) ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled)
{ {
GList *tmp; if (ges_timeline_enable_update_internal (timeline, enabled)) {
gboolean res = TRUE; g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_UPDATE]);
GST_DEBUG_OBJECT (timeline, "%s updates", enabled ? "Enabling" : "Disabling"); return TRUE;
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 */ return FALSE;
timeline->priv->movecontext.needs_move_ctx = TRUE; }
return res; /**
* 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_enable_update(GESTimeline * timeline, gboolean enabled);
gboolean ges_timeline_is_updating (GESTimeline * timeline); gboolean ges_timeline_is_updating (GESTimeline * timeline);
GstClockTime ges_timeline_get_duration (GESTimeline *timeline);
G_END_DECLS G_END_DECLS
#endif /* _GES_TIMELINE */ #endif /* _GES_TIMELINE */

View file

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

View file

@ -35,20 +35,35 @@
G_DEFINE_TYPE (GESTrack, ges_track, GST_TYPE_BIN); 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 struct _GESTrackPrivate
{ {
/*< private > */ /*< private > */
GESTimeline *timeline; GESTimeline *timeline;
GList *trackobjects; GSequence *tckobjs_by_start;
GList *gaps;
guint64 duration; guint64 duration;
GstCaps *caps; GstCaps *caps;
GstElement *composition; /* The composition associated with this track */ GstElement *composition; /* The composition associated with this track */
GstElement *background; /* The backgrond, handle the gaps in the track */
GstPad *srcpad; /* The source GhostPad */ GstPad *srcpad; /* The source GhostPad */
gboolean updating; gboolean updating;
/* Virtual method to create GstElement that fill gaps */
GESCreateElementForGapFunc create_element_for_gaps;
}; };
enum enum
@ -72,13 +87,329 @@ static void
pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track); pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track);
static void composition_duration_cb (GstElement * composition, GParamSpec * arg static void composition_duration_cb (GstElement * composition, GParamSpec * arg
G_GNUC_UNUSED, GESTrack * obj); 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 static void
sort_track_objects_cb (GESTrackObject * child, 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, static void
GParamSpec * arg G_GNUC_UNUSED, GESTrack * track); 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 static void
ges_track_get_property (GObject * object, guint property_id, ges_track_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec) GValue * value, GParamSpec * pspec)
@ -124,12 +455,11 @@ ges_track_dispose (GObject * object)
GESTrack *track = (GESTrack *) object; GESTrack *track = (GESTrack *) object;
GESTrackPrivate *priv = track->priv; GESTrackPrivate *priv = track->priv;
while (priv->trackobjects) { /* Remove all TrackObjects and drop our reference */
GESTrackObject *trobj = GES_TRACK_OBJECT (priv->trackobjects->data); g_sequence_foreach (track->priv->tckobjs_by_start,
ges_track_remove_object (track, trobj); (GFunc) dispose_tckobjs_foreach, track);
ges_timeline_object_release_track_object ((GESTimelineObject *) g_sequence_free (priv->tckobjs_by_start);
ges_track_object_get_timeline_object (trobj), trobj); g_list_free_full (priv->gaps, (GDestroyNotify) free_gap);
}
if (priv->composition) { if (priv->composition) {
gst_bin_remove (GST_BIN (object), 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); 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 static void
ges_track_finalize (GObject * object) 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->set_property = ges_track_set_property;
object_class->dispose = ges_track_dispose; object_class->dispose = ges_track_dispose;
object_class->finalize = ges_track_finalize; object_class->finalize = ges_track_finalize;
object_class->constructed = ges_track_constructed;
/** /**
* GESTrack:caps * GESTrack:caps
@ -286,6 +573,9 @@ ges_track_init (GESTrack * self)
self->priv->composition = gst_element_factory_make ("gnlcomposition", NULL); self->priv->composition = gst_element_factory_make ("gnlcomposition", NULL);
self->priv->updating = TRUE; 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_signal_connect (G_OBJECT (self->priv->composition), "notify::duration",
G_CALLBACK (composition_duration_cb), self); 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"); GstCaps *caps = gst_caps_new_empty_simple ("video/x-raw");
track = ges_track_new (GES_TRACK_TYPE_VIDEO, caps); 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; return track;
} }
@ -355,7 +649,11 @@ ges_track_audio_raw_new (void)
GstCaps *caps = gst_caps_new_empty_simple ("audio/x-raw"); GstCaps *caps = gst_caps_new_empty_simple ("audio/x-raw");
track = ges_track_new (GES_TRACK_TYPE_AUDIO, caps); 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; return track;
} }
@ -372,12 +670,12 @@ ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
GST_DEBUG ("track:%p, timeline:%p", track, timeline); GST_DEBUG ("track:%p, timeline:%p", track, timeline);
if (track->priv->timeline) if (track->priv->timeline)
g_signal_handlers_disconnect_by_func (track, g_signal_handlers_disconnect_by_func (track->priv->timeline,
timeline_duration_cb, track->priv->timeline); timeline_duration_changed_cb, track);
if (timeline) if (timeline)
g_signal_connect (G_OBJECT (timeline), "notify::duration", g_signal_connect (timeline, "notify::duration",
G_CALLBACK (timeline_duration_cb), track); G_CALLBACK (timeline_duration_changed_cb), track);
track->priv->timeline = timeline; track->priv->timeline = timeline;
} }
@ -409,26 +707,6 @@ ges_track_set_caps (GESTrack * track, const GstCaps * caps)
/* FIXME : update all trackobjects ? */ /* 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: * ges_track_add_object:
* @track: a #GESTrack * @track: a #GESTrack
@ -471,9 +749,8 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object)
} }
g_object_ref_sink (object); g_object_ref_sink (object);
track->priv->trackobjects = g_sequence_insert_sorted (track->priv->tckobjs_by_start, object,
g_list_insert_sorted (track->priv->trackobjects, object, (GCompareDataFunc) objects_start_compare, NULL);
(GCompareFunc) objects_start_compare);
g_signal_emit (track, ges_track_signals[TRACK_OBJECT_ADDED], 0, g_signal_emit (track, ges_track_signals[TRACK_OBJECT_ADDED], 0,
GES_TRACK_OBJECT (object)); 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_signal_connect (GES_TRACK_OBJECT (object), "notify::start",
G_CALLBACK (sort_track_objects_cb), track); 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_signal_connect (GES_TRACK_OBJECT (object), "notify::priority",
G_CALLBACK (sort_track_objects_cb), track); G_CALLBACK (sort_track_objects_cb), track);
resort_and_fill_gaps (track);
return TRUE; return TRUE;
} }
@ -500,14 +782,11 @@ GList *
ges_track_get_objects (GESTrack * track) ges_track_get_objects (GESTrack * track)
{ {
GList *ret = NULL; GList *ret = NULL;
GList *tmp;
g_return_val_if_fail (GES_IS_TRACK (track), NULL); g_return_val_if_fail (GES_IS_TRACK (track), NULL);
for (tmp = track->priv->trackobjects; tmp; tmp = tmp->next) { g_sequence_foreach (track->priv->tckobjs_by_start,
ret = g_list_prepend (ret, tmp->data); (GFunc) add_trackobj_to_list_foreach, &ret);
g_object_ref (tmp->data);
}
ret = g_list_reverse (ret); ret = g_list_reverse (ret);
return ret; return ret;
@ -529,121 +808,25 @@ ges_track_get_objects (GESTrack * track)
gboolean gboolean
ges_track_remove_object (GESTrack * track, GESTrackObject * object) ges_track_remove_object (GESTrack * track, GESTrackObject * object)
{ {
GSequenceIter *it;
GESTrackPrivate *priv; GESTrackPrivate *priv;
GstElement *gnlobject;
g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE);
GST_DEBUG ("track:%p, object:%p", track, object);
priv = track->priv; priv = track->priv;
if (G_UNLIKELY (ges_track_object_get_track (object) != track)) { if (remove_object_internal (track, object) == TRUE) {
GST_WARNING ("Object belongs to another track"); it = g_sequence_lookup (priv->tckobjs_by_start, object,
return FALSE; (GCompareDataFunc) objects_start_compare, NULL);
g_sequence_remove (it);
resort_and_fill_gaps (track);
return TRUE;
} }
if ((gnlobject = ges_track_object_get_gnlobject (object))) { return FALSE;
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));
} }
/** /**
@ -702,6 +885,9 @@ ges_track_enable_update (GESTrack * track, gboolean enabled)
track->priv->updating = update; track->priv->updating = update;
if (update == TRUE)
resort_and_fill_gaps (track);
return update == enabled; return update == enabled;
} }
@ -720,3 +906,23 @@ ges_track_is_updating (GESTrack * track)
return track->priv->updating; 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; 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: * GESTrack:
* @type: a #GESTrackType indicting the basic type of the track. * @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); GList* ges_track_get_objects (GESTrack *track);
void
ges_track_set_create_element_for_gap_func (GESTrack *track,
GESCreateElementForGapFunc func);
G_END_DECLS G_END_DECLS
#endif /* _GES_TRACK */ #endif /* _GES_TRACK */

View file

@ -206,6 +206,126 @@ GST_START_TEST (test_test_source_in_layer)
GST_END_TEST; 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 * static Suite *
ges_suite (void) 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_basic);
tcase_add_test (tc_chain, test_test_source_properties); 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_test_source_in_layer);
tcase_add_test (tc_chain, test_gap_filling_basic);
return s; return s;
} }