diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 2b01ae264b..6002b96aaf 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -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
ges-track GESTrack 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 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 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 GES_TIMELINE_OBJECT_DURATION GES_TIMELINE_OBJECT_INPOINT diff --git a/ges/ges-formatter.c b/ges/ges-formatter.c index 8577346cb1..4777363497 100644 --- a/ges/ges-formatter.c +++ b/ges/ges-formatter.c @@ -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; diff --git a/ges/ges-pitivi-formatter.c b/ges/ges-pitivi-formatter.c index f828eecb70..0ad06f4f0f 100644 --- a/ges/ges-pitivi-formatter.c +++ b/ges/ges-pitivi-formatter.c @@ -17,6 +17,11 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION: ges-pitivi-formatter + * @short_description: A formatter for the PiTiVi project file format + */ + #include #include #include diff --git a/ges/ges-pitivi-formatter.h b/ges/ges-pitivi-formatter.h index 80751aa4d4..987be4b8b1 100644 --- a/ges/ges-pitivi-formatter.h +++ b/ges/ges-pitivi-formatter.h @@ -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; diff --git a/ges/ges-screenshot.c b/ges/ges-screenshot.c index 99aac759d8..420d7c57e5 100644 --- a/ges/ges-screenshot.c +++ b/ges/ges-screenshot.c @@ -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) { diff --git a/ges/ges-timeline-layer.c b/ges/ges-timeline-layer.c index 4c991fbc2f..521ca370da 100644 --- a/ges/ges-timeline-layer.c +++ b/ges/ges-timeline-layer.c @@ -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); } diff --git a/ges/ges-timeline-object.c b/ges/ges-timeline-object.c index 6d54acf81b..b25c46b303 100644 --- a/ges/ges-timeline-object.c +++ b/ges/ges-timeline-object.c @@ -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 * diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 5ee3a0417b..18d63638ed 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -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; } diff --git a/ges/ges-timeline.h b/ges/ges-timeline.h index de51bcb1d0..ad1992ca6e 100644 --- a/ges/ges-timeline.h +++ b/ges/ges-timeline.h @@ -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 */ diff --git a/ges/ges-track-object.c b/ges/ges-track-object.c index f32f2ba008..d55ff97ba8 100644 --- a/ges/ges-track-object.c +++ b/ges/ges-track-object.c @@ -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 */ diff --git a/ges/ges-track.c b/ges/ges-track.c index 7c6f439dfc..f20332c9a5 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -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; +} diff --git a/ges/ges-track.h b/ges/ges-track.h index b5bad827b8..033f777b4f 100644 --- a/ges/ges-track.h +++ b/ges/ges-track.h @@ -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 */ diff --git a/tests/check/ges/backgroundsource.c b/tests/check/ges/backgroundsource.c index 77a511dc93..ee1a58b4c2 100644 --- a/tests/check/ges/backgroundsource.c +++ b/tests/check/ges/backgroundsource.c @@ -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; }