Merge branch 'tizen' into tizen_gst_1.19.2 14/269614/2 tizen_gst_1.19.2
authorGilbok Lee <gilbok.lee@samsung.com>
Wed, 19 Jan 2022 06:54:43 +0000 (15:54 +0900)
committerGilbok Lee <gilbok.lee@samsung.com>
Wed, 19 Jan 2022 06:56:45 +0000 (15:56 +0900)
Change-Id: I3806ab340e71edb091ae34d7b2f9016d636454cb

310 files changed:
.gitignore
.gitlab-ci.yml
.gitmodules [deleted file]
ChangeLog
Makefile.am [deleted file]
NEWS
RELEASE
autogen.sh [deleted file]
bindings/Makefile.am [deleted file]
bindings/python/Makefile.am [deleted file]
bindings/python/gi/Makefile.am [deleted file]
bindings/python/gi/overrides/GES.py
bindings/python/gi/overrides/Makefile.am [deleted file]
common [deleted submodule]
configure.ac [deleted file]
docs/Makefile.am [deleted file]
docs/base-classes.md [new file with mode: 0644]
docs/deprecated.md [new file with mode: 0644]
docs/design/encoding.txt
docs/design/time_notes.md [new file with mode: 0644]
docs/gst_plugins_cache.json [new file with mode: 0644]
docs/images/layer_track_overview.png [moved from docs/libs/layer_track_overview.png with 100% similarity]
docs/index.md [new file with mode: 0644]
docs/libs/.gitignore [deleted file]
docs/libs/GESAudioTestSource-children-props.md [new file with mode: 0644]
docs/libs/GESAudioUriSource-children-props.md [new file with mode: 0644]
docs/libs/GESTimeOverlayClip-children-props.md [new file with mode: 0644]
docs/libs/GESTitleSource-children-props.md [new file with mode: 0644]
docs/libs/GESTransitionClip-children-props.md [new file with mode: 0644]
docs/libs/GESVideoTestSource-children-props.md [new file with mode: 0644]
docs/libs/GESVideoUriSource-children-props.md [new file with mode: 0644]
docs/libs/Makefile.am [deleted file]
docs/libs/architecture.xml [deleted file]
docs/libs/document-children-props.py [new file with mode: 0644]
docs/libs/ges-docs.sgml [deleted file]
docs/libs/ges-sections.txt [deleted file]
docs/libs/ges.types [deleted file]
docs/libs/meson.build [deleted file]
docs/low_level.md [new file with mode: 0644]
docs/meson.build
docs/plugins/index.md [moved from m4/Makefile.am with 100% similarity]
docs/plugins/nle.md [new file with mode: 0644]
docs/plugins/sitemap.txt [new file with mode: 0644]
docs/sitemap.txt [new file with mode: 0644]
examples/.gitignore [deleted file]
examples/Makefile.am [deleted file]
examples/c/Makefile.am [deleted file]
examples/c/concatenate.c
examples/c/ges-ui.c
examples/c/gessrc.c
examples/c/multifilesrc.c
examples/c/overlays.c
examples/c/play_timeline_with_one_clip.c
examples/c/simple1.c
examples/c/test2.c
examples/c/test3.c
examples/c/test4.c
examples/c/text_properties.c
examples/c/thumbnails.c
examples/c/transition.c
examples/python/gst-player.py
examples/python/simple.py
ges/.gitignore [deleted file]
ges/Makefile.am [deleted file]
ges/ges-asset.c
ges/ges-asset.h
ges/ges-audio-source.c
ges/ges-audio-source.h
ges/ges-audio-test-source.c
ges/ges-audio-test-source.h
ges/ges-audio-track.c
ges/ges-audio-track.h
ges/ges-audio-transition.c
ges/ges-audio-transition.h
ges/ges-audio-uri-source.c
ges/ges-audio-uri-source.h
ges/ges-auto-transition.c
ges/ges-auto-transition.h
ges/ges-base-effect-clip.c
ges/ges-base-effect-clip.h
ges/ges-base-effect.c
ges/ges-base-effect.h
ges/ges-base-transition-clip.h
ges/ges-base-xml-formatter.c
ges/ges-base-xml-formatter.h
ges/ges-clip-asset.c
ges/ges-clip-asset.h
ges/ges-clip.c
ges/ges-clip.h
ges/ges-command-line-formatter.c
ges/ges-command-line-formatter.h
ges/ges-container.c
ges/ges-container.h
ges/ges-effect-asset.c
ges/ges-effect-asset.h
ges/ges-effect-clip.c
ges/ges-effect-clip.h
ges/ges-effect.c
ges/ges-effect.h
ges/ges-enums.c
ges/ges-enums.h
ges/ges-extractable.c
ges/ges-extractable.h
ges/ges-formatter.c
ges/ges-formatter.h
ges/ges-gerror.h
ges/ges-group.c
ges/ges-group.h
ges/ges-image-source.c
ges/ges-image-source.h
ges/ges-internal.h
ges/ges-layer.c
ges/ges-layer.h
ges/ges-marker-list.c [new file with mode: 0644]
ges/ges-marker-list.h [new file with mode: 0644]
ges/ges-meta-container.c
ges/ges-meta-container.h
ges/ges-multi-file-source.c
ges/ges-multi-file-source.h
ges/ges-operation-clip.h
ges/ges-operation.c
ges/ges-operation.h
ges/ges-overlay-clip.h
ges/ges-pipeline.c
ges/ges-pipeline.h
ges/ges-pitivi-formatter.c
ges/ges-pitivi-formatter.h
ges/ges-prelude.h
ges/ges-project.c
ges/ges-project.h
ges/ges-screenshot.c
ges/ges-screenshot.h
ges/ges-smart-adder.h
ges/ges-smart-video-mixer.c
ges/ges-smart-video-mixer.h
ges/ges-source-clip-asset.c [new file with mode: 0644]
ges/ges-source-clip-asset.h [new file with mode: 0644]
ges/ges-source-clip.c
ges/ges-source-clip.h
ges/ges-source.c
ges/ges-source.h
ges/ges-structure-parser.c
ges/ges-structure-parser.h
ges/ges-structured-interface.c
ges/ges-structured-interface.h
ges/ges-test-clip.c
ges/ges-test-clip.h
ges/ges-text-overlay-clip.h
ges/ges-text-overlay.c
ges/ges-text-overlay.h
ges/ges-time-overlay-clip.c [new file with mode: 0644]
ges/ges-time-overlay-clip.h [new file with mode: 0644]
ges/ges-timeline-element.c
ges/ges-timeline-element.h
ges/ges-timeline-tree.c
ges/ges-timeline-tree.h
ges/ges-timeline.c
ges/ges-timeline.h
ges/ges-title-clip.c
ges/ges-title-clip.h
ges/ges-title-source.c
ges/ges-title-source.h
ges/ges-track-element-asset.c
ges/ges-track-element-asset.h
ges/ges-track-element-deprecated.h [new file with mode: 0644]
ges/ges-track-element.c
ges/ges-track-element.h
ges/ges-track.c
ges/ges-track.h
ges/ges-transition-clip.c
ges/ges-transition-clip.h
ges/ges-transition.c
ges/ges-transition.h
ges/ges-types.h
ges/ges-uri-asset.c
ges/ges-uri-asset.h
ges/ges-uri-clip.c
ges/ges-uri-clip.h
ges/ges-uri-source.c [new file with mode: 0644]
ges/ges-uri-source.h [new file with mode: 0644]
ges/ges-utils.c
ges/ges-utils.h
ges/ges-validate.c
ges/ges-version.h.in
ges/ges-video-source.c
ges/ges-video-source.h
ges/ges-video-test-source.c
ges/ges-video-test-source.h
ges/ges-video-track.c
ges/ges-video-track.h
ges/ges-video-transition.c
ges/ges-video-transition.h
ges/ges-video-uri-source.c
ges/ges-video-uri-source.h
ges/ges-xml-formatter.c
ges/ges-xml-formatter.h
ges/ges.c
ges/ges.h
ges/ges.resource [new file with mode: 0644]
ges/gstframepositioner.c
ges/gstframepositioner.h
ges/meson.build
ges/parse.l
ges/python/gesotioformatter.py [new file with mode: 0644]
gst-editing-services.doap
meson.build
meson_options.txt
packaging/common.tar.gz [deleted file]
packaging/gst-editing-services.manifest [moved from gst-editing-services.manifest with 100% similarity]
packaging/gst-editing-services.spec
pkgconfig/.gitignore [deleted file]
pkgconfig/Makefile.am [deleted file]
pkgconfig/gst-editing-services-uninstalled.pc.in [deleted file]
pkgconfig/gst-editing-services.pc.in [deleted file]
pkgconfig/meson.build [deleted file]
plugins/Makefile.am [deleted file]
plugins/ges/Makefile.am [deleted file]
plugins/ges/gesbasebin.c [new file with mode: 0644]
plugins/ges/gesbasebin.h [moved from plugins/ges/gessrc.h with 56% similarity]
plugins/ges/gesdemux.c
plugins/ges/gesdemux.h [deleted file]
plugins/ges/gesplugin.c
plugins/ges/gessrc.c
plugins/ges/meson.build
plugins/meson.build
plugins/nle/.gitignore [deleted file]
plugins/nle/Makefile.am [deleted file]
plugins/nle/meson.build
plugins/nle/nlecomposition.c
plugins/nle/nleghostpad.c
plugins/nle/nleobject.c
plugins/nle/nleobject.h
plugins/nle/nleoperation.c
plugins/nle/nleoperation.h
plugins/nle/nlesource.c
scripts/extract-release-date-from-doap-file.py [new file with mode: 0755]
tests/.gitignore [deleted file]
tests/Makefile.am [deleted file]
tests/benchmarks/Makefile.am [deleted file]
tests/benchmarks/timeline.c
tests/check/Makefile.am [deleted file]
tests/check/assets/audio_only.ogg [moved from tests/check/ges/audio_only.ogg with 100% similarity]
tests/check/assets/audio_video.ogg [moved from tests/check/ges/audio_video.ogg with 100% similarity]
tests/check/assets/image.png [moved from tests/check/ges/image.png with 100% similarity]
tests/check/assets/test-auto-transition.xges [moved from tests/check/ges/test-auto-transition.xges with 100% similarity]
tests/check/assets/test-project.xges [moved from tests/check/ges/test-project.xges with 100% similarity]
tests/check/assets/test-properties.xges [moved from tests/check/ges/test-properties.xges with 98% similarity]
tests/check/ges/.gitignore [deleted file]
tests/check/ges/asset.c
tests/check/ges/backgroundsource.c
tests/check/ges/basic.c
tests/check/ges/clip.c
tests/check/ges/effects.c
tests/check/ges/group.c
tests/check/ges/layer.c
tests/check/ges/markerlist.c [new file with mode: 0644]
tests/check/ges/overlays.c
tests/check/ges/project.c
tests/check/ges/tempochange.c
tests/check/ges/test-utils.c
tests/check/ges/test-utils.h
tests/check/ges/timelineedition.c
tests/check/ges/titles.c
tests/check/ges/transition.c
tests/check/ges/uriclip.c
tests/check/meson.build
tests/check/nle/complex.c
tests/check/nle/nlecomposition.c
tests/check/nle/nleoperation.c
tests/check/nle/nlesource.c
tests/check/nle/seek.c
tests/check/nle/simple.c
tests/check/nle/tempochange.c
tests/check/python/common.py
tests/check/python/test_assets.py
tests/check/python/test_clip.py
tests/check/python/test_group.py
tests/check/python/test_timeline.py
tests/check/scenarios/check-clip-positioning.validatetest [new file with mode: 0644]
tests/check/scenarios/check_edit_in_frames.scenario [new file with mode: 0644]
tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario [new file with mode: 0644]
tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest [new file with mode: 0644]
tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected [new file with mode: 0644]
tests/check/scenarios/check_layer_activness_gaps.scenario [new file with mode: 0644]
tests/check/scenarios/check_video_track_restriction_scale.scenario [new file with mode: 0644]
tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario [new file with mode: 0644]
tests/check/scenarios/complex_effect_bin_desc.validatetest [new file with mode: 0644]
tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected [new file with mode: 0644]
tests/check/scenarios/edit_while_seeked_with_stop.validatetest [new file with mode: 0644]
tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected [new file with mode: 0644]
tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest [new file with mode: 0644]
tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected [new file with mode: 0644]
tests/check/scenarios/seek_with_stop.validatetest [new file with mode: 0644]
tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected [new file with mode: 0644]
tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected [new file with mode: 0644]
tests/check/scenarios/set-layer-on-command-line.validatetest [new file with mode: 0644]
tests/validate/Makefile.am [deleted file]
tests/validate/geslaunch.py
tests/validate/scenarios/Makefile.am [deleted file]
tools/Makefile.am [deleted file]
tools/ges-launch.c
tools/ges-launcher-kb.c [new file with mode: 0644]
tools/ges-launcher-kb.h [new file with mode: 0644]
tools/ges-launcher.c
tools/ges-launcher.h
tools/ges-validate.c
tools/ges-validate.h
tools/meson.build
tools/utils.c
tools/utils.h

index 071f280..a2e9b72 100644 (file)
@@ -1,95 +1,9 @@
-*.o
-*.bak
-*.orig
-*.diff
-*.patch
-*.so
-*.a
-*.la
-*.lo
-*.pyc
-*.page
-*.swp
-build*
+/build/
+/b/
+/_build/
 *~
 core.*
 
-Makefile
-Makefile.in
 core
 log
-.deps
-.libs
-.dirstamp
 
-/INSTALL
-
-/aclocal.m4
-/autom4te.cache
-/autoregen.sh
-/compile
-/config.guess
-/config.h
-/config.h.in
-/config.log
-/config.status
-/config.sub
-/configure
-/depcomp
-/install-sh
-/libtool
-/ltmain.sh
-/missing
-/py-compile
-/stamp-h.in
-/stamp-h1
-
-/ges/gesmarshal.h
-/ges/gesmarshal.c
-/ges/ges-version.h
-
-/docs/version.entities
-
-/bindings/python/ges.c
-
-/ges/lex.priv_ges_parse_yy.c
-/ges/ges-parse-lex.h
-
-/m4
-
-/test-driver
-/tests/check/test-registry.reg
-/tests/check/*.log
-/tests/check/*.trs
-/tests/check/*/*.log
-/tests/check/*/*.trs
-
-/tests/check/ges/group
-/tests/check/ges/material
-/tests/check/ges/mixers
-/tests/check/ges/timelineedition
-/tests/check/ges/timelinegroup
-/tests/check/ges/tempochange
-/tests/check/ges/uriclip
-/tests/check/integration
-/tests/benchmarks/timeline
-/tests/examples/materials
-
-/tests/check/nle/simple
-/tests/check/nle/complex
-/tests/check/nle/nlecomposition
-/tests/check/nle/nleoperation
-/tests/check/nle/nlesource
-/tests/check/nle/tempochange
-
-/tools/ges-launch-1.0
-/tests/check/ges/project
-/tests/examples/assets
-
-/tests/check/coverage/
-*gcno
-*BACKUP*
-*REMOTE*
-*LOCAL*
-*BASE*
-*anjuta*
index bed438d..c61aa7a 100644 (file)
@@ -1 +1 @@
-include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/1.16/gitlab/ci_template.yml"
+include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml"
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644 (file)
index 0ab8387..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "common"]
-       path = common
-       url = https://gitlab.freedesktop.org/gstreamer/common.git
index 0a06c87..165f1ca 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
-=== release 1.16.2 ===
+=== release 1.19.2 ===
 
-2019-12-03 11:17:11 +0000  Tim-Philipp Müller <tim@centricular.com>
+2021-09-23 01:35:39 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * ChangeLog:
+       * NEWS:
+       * RELEASE:
+       * gst-editing-services.doap:
+       * meson.build:
+         Release 1.19.2
+
+2021-08-10 17:10:43 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/meson.build:
+       * tools/ges-launcher.c:
+       * tools/ges-validate.c:
+       * tools/utils.h:
+         launch: Make enabling validate opt-in
+         Instead of opt-out.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/264>
+
+2021-08-12 23:37:59 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * ges/ges-uri-source.c:
+         ges-uri-source: fix object debug
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/265>
+
+2021-08-10 23:54:47 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * docs/gst_plugins_cache.json:
+       * plugins/nle/nlecomposition.c:
+       * tools/ges-launcher.c:
+       * tools/utils.h:
+         ges-launcher: add option to forward tags
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/265>
+
+2021-08-10 23:25:06 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * tools/ges-launcher.c:
+       * tools/utils.h:
+         ges-launcher: allow using a clip to determine the rendering format
+         This includes both topology and profile
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/265>
+
+2021-08-10 23:23:39 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * tools/ges-launcher.c:
+         launcher: don't start the pipeline before we're done updating it
+         Since 70e3b8ae2a8d13b50f52305b71cfa4b590bb63f6 the CommandLineFormatter
+         also emit "loaded" so we ended up doing this twice, once
+         as before in `run_pipeline` and another time in the `project:loaded`
+         callback.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/265>
+
+2021-08-10 23:20:21 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * tools/ges-launcher.c:
+         ges-launcher: don't unref transfer none objects
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/265>
+
+2021-07-21 19:31:53 +0200  Piotrek Brzeziński <thewildtree@outlook.com>
+
+       * ges/ges-clip.c:
+       * tests/check/ges/clip.c:
+         clip: Copy trackelement's metadata upon splitting
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/260>
+
+2021-07-09 16:15:01 +0200  Piotrek Brzeziński <thewildtree@outlook.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Add support for metadata on sources
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/260>
+
+2021-07-09 16:14:19 +0200  Piotrek Brzeziński <thewildtree@outlook.com>
+
+       * ges/ges-marker-list.c:
+       * tests/check/ges/markerlist.c:
+         marker-list: Add flags (de)serialization
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/260>
+
+2021-08-03 11:31:07 +0200  Stéphane Cerveau <scerveau@collabora.com>
+
+       * ges/ges-pipeline.c:
+       * ges/ges-timeline.c:
+       * ges/ges-timeline.h:
+         ges: freeze commit during render
+         In render mode, do not commit the timeline
+         as the position can be invalid and lead to
+         missing frames.
+         Fixes #136
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/262>
+
+2021-08-05 22:59:07 +0200  Piotrek Brzeziński <thewildtree@outlook.com>
+
+       * ges/ges-timeline-tree.c:
+         timeline: Check if metadata value holds object on marker snapping
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/263>
+
+2021-06-20 23:51:02 +0200  Piotrek Brzeziński <thewildtree@outlook.com>
+
+       * ges/ges-enums.c:
+       * ges/ges-enums.h:
+       * ges/ges-internal.h:
+       * ges/ges-marker-list.c:
+       * ges/ges-marker-list.h:
+       * ges/ges-timeline-tree.c:
+       * tests/check/ges/markerlist.c:
+       * tests/check/ges/timelineedition.c:
+         timeline: Implement snapping to markers
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/259>
+
+2021-06-16 17:12:11 +0200  François Laignel <fengalin@free.fr>
+
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline.c:
+         Check mandatory ClockTime arguments
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/258>
+
+2021-05-22 18:41:08 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * ges/ges-pitivi-formatter.c:
+       * meson.build:
+         Use g_memdup2() where available and add fallback for older GLib versions
+         Size is constant here, so no problem in any case, but g_memdup() is
+         now deprecated and we don't want deprecation warnings.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/257>
+
+2021-06-01 15:29:10 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+         Back to development
+
+=== release 1.19.1 ===
+
+2021-06-01 00:16:05 +0100  Tim-Philipp Müller <tim@centricular.com>
 
        * ChangeLog:
        * NEWS:
        * RELEASE:
-       * configure.ac:
        * gst-editing-services.doap:
        * meson.build:
-         Release 1.16.2
+         Release 1.19.1
+
+2021-05-18 11:42:22 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-clip.c:
+         uriclip: Add an error message when creating a clip failed
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/252>
+
+2021-05-18 11:31:19 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * examples/c/simple1.c:
+         examples: c: Sensibly simplify the simple example
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/252>
+
+2021-05-18 11:16:02 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * examples/python/gst-player.py:
+       * examples/python/simple.py:
+         examples: python: Simplify the simple example
+         We shouldn't show assets usage in the simplest example we have
+         as it is useful for more advanced use cases.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/252>
+
+2021-05-21 15:26:03 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected:
+         tests: Update expectation files with sorted structure fields
+
+2021-05-20 16:47:41 +0100  Philippe Normand <philn@igalia.com>
+
+       * tests/check/ges/test-utils.c:
+       * tests/check/meson.build:
+       * tests/check/nle/complex.c:
+       * tests/check/nle/nlecomposition.c:
+       * tests/check/nle/nleoperation.c:
+       * tests/check/nle/nlesource.c:
+       * tests/check/nle/seek.c:
+       * tests/check/nle/simple.c:
+       * tests/check/nle/tempochange.c:
+       * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario:
+       * tests/check/scenarios/check_layer_activness_gaps.scenario:
+         tests/check: Use fake{audio,video}sink
+         The tests already depend on -bad, so this should be OK.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/254>
+
+2021-05-20 16:45:43 +0100  Philippe Normand <philn@igalia.com>
+
+       * tools/ges-launcher.c:
+         launcher: Switch to fake{audio,video}sink
+         Simplifies the code a bit, though introducing runtime dependency on -bad.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/254>
+
+2021-05-18 21:31:38 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-structured-interface.c:
+       * tests/check/meson.build:
+       * tests/check/scenarios/set-layer-on-command-line.validatetest:
+         structure-interface: Convert fields type as much as possible
+         Since 60922c02889cf1ebcfaca4501936be689c342e01 we force string in the
+         command line parser which broke setting layers on clips for example
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/253>
+
+2021-05-18 22:04:48 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Add support to check properties of object properties
+         And recursively
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/253>
+
+2021-04-23 16:08:48 +0900  Seungha Yang <seungha@centricular.com>
+
+       * ges/ges-smart-video-mixer.c:
+       * ges/ges-utils.c:
+         smart-mixer: Add support for d3d11compositor and glvideomixer
+         Some hardware compositor elements (d3d11compositor and glvideomixer)
+         consist of wrapper bin with internal mixer element.
+         So, we need special handling for such elements.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/242>
+
+2021-04-24 00:55:45 +0900  Seungha Yang <seungha@centricular.com>
+
+       * ges/gstframepositioner.c:
+         framepositioner: Install operator property only when compositor is used
+         Other compositor/mixer elements might not have the property. For instance,
+         d3d11compositor and glvideomixer define graphics API specific blending
+         properties, instead of simple "operator" one.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/242>
+
+2021-05-12 17:43:46 -0400  Doug Nazar <nazard@nazar.ca>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Write xml directly to file
+         Skip allocation of temp buffer (which was undersized).
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/250>
+
+2021-05-01 19:18:15 -0400  Doug Nazar <nazard@nazar.ca>
+
+       * tests/check/meson.build:
+         tests: Run ges-launch tests non-interactively
+         It's not needed for the tests and fixes an occasional issue where
+         the terminal is left in -echo mode.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/248>
+
+2021-02-24 23:49:06 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track-element.h:
+         track-element: Fix and cleanup annotations
+         Making the class subclass able by bindings
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/231>
+
+2021-02-24 23:37:28 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-source.c:
+       * ges/ges-audio-source.h:
+       * ges/ges-audio-test-source.c:
+       * ges/ges-audio-uri-source.c:
+       * ges/ges-image-source.c:
+       * ges/ges-multi-file-source.c:
+       * ges/ges-source.h:
+       * ges/ges-title-source.c:
+       * ges/ges-track-element.h:
+       * ges/ges-video-source.c:
+       * ges/ges-video-source.h:
+       * ges/ges-video-test-source.c:
+       * ges/ges-video-uri-source.c:
+         ges: Move GESVideo/AudioSource::create_source to GESSource
+         Deprecating the old variants which were not introspectable
+         and cleaning a bit the API.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/231>
+
+2021-04-21 10:47:51 +0200  François Laignel <fengalin@free.fr>
+
+       * docs/design/encoding.txt:
+       * ges/ges-effect-asset.c:
+       * ges/ges-pipeline.c:
+       * ges/ges-smart-video-mixer.c:
+       * ges/gstframepositioner.c:
+       * plugins/nle/nleoperation.c:
+         Use gst_element_request_pad_simple...
+         Instead of the deprecated gst_element_get_request_pad.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/240>
+
+2021-04-28 00:57:35 +0900  Seungha Yang <seungha@centricular.com>
+
+       * examples/c/concatenate.c:
+       * examples/c/ges-ui.c:
+       * examples/c/gessrc.c:
+       * examples/c/multifilesrc.c:
+       * examples/c/overlays.c:
+       * examples/c/play_timeline_with_one_clip.c:
+       * examples/c/simple1.c:
+       * examples/c/test2.c:
+       * examples/c/test3.c:
+       * examples/c/test4.c:
+       * examples/c/text_properties.c:
+       * examples/c/thumbnails.c:
+       * examples/c/transition.c:
+       * ges/ges-asset.c:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-uri-asset.c:
+       * ges/ges.c:
+       * tests/benchmarks/timeline.c:
+       * tests/check/ges/test-utils.c:
+       * tools/ges-launcher.c:
+       * tools/ges-validate.c:
+       * tools/utils.c:
+         ges: Port to gst_print*
+         Sync with gst-launch, as g_print* will print broken string on Windows.
+         See also
+         https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/258
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/245>
+
+2021-04-23 16:42:26 +0900  Seungha Yang <seungha@centricular.com>
+
+       * ges/gstframepositioner.c:
+         framepositioner: Allow ANY caps features
+         framepositioner will not touch raw video data and therefore should
+         be able to accept ANY caps features
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/243>
+
+2021-04-23 09:01:35 -0500  reed.lawrence <reed.lawrence@zenofchem.com>
+
+       * ges/gstframepositioner.c:
+         gstframepositioner: fix operator magic number
+         In gst_frame_positioner_init, there was the magic number 1
+         when assigning the default value of the operator. Now it
+         has the default value for the operator pulled from the
+         compositor.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/241>
+
+2021-04-21 18:12:30 -0500  reed.lawrence <reed.lawrence@zenofchem.com>
+
+       * ges/ges-smart-video-mixer.c:
+       * ges/ges-video-source.c:
+       * ges/gstframepositioner.c:
+       * ges/gstframepositioner.h:
+         gstframepositioner: added 'operator' property
+         The 'operator' property was added to gstframepositioner so that
+         blending modes in the compositor could be accessed. This was done
+         by accessing the pad of the compositor class, and referencing the
+         'operator' property in that pad. Getters and Setters were also
+         created so that the 'operator' could be accessed by software that
+         is based on GES, such as Pitivi.
+         Related to but does not close Issue
+         https://gitlab.gnome.org/GNOME/pitivi/-/issues/2313
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/236>
+
+2021-04-14 12:58:30 +0900  Seungha Yang <seungha@centricular.com>
+
+       * ges/gstframepositioner.c:
+         framepositioner: Fix runtime warning
+         GstCaps is not a GObject!
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/238>
+
+2021-04-08 15:35:30 -0500  Adam Leppky <aleppky2@huskers.unl.edu>
+
+       * ges/ges-title-source.c:
+         titleclip: Expose draw-shadow child property
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/235>
+
+2021-03-19 17:21:01 +1100  Matthew Waters <matthew@centricular.com>
+
+       * ges/ges-smart-video-mixer.c:
+       * ges/gstframepositioner.c:
+       * plugins/nle/nlecomposition.c:
+       * plugins/nle/nleobject.c:
+         gst: don't use volatile to mean atomic
+         volatile is not sufficient to provide atomic guarantees and real atomics
+         should be used instead.  GCC 11 has started warning about using volatile
+         with atomic operations.
+         https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719
+         Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/234>
+
+2021-03-08 14:50:52 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip.h:
+         ges: doc: Fix wrong vmethod links
+
+2021-03-08 09:56:49 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-group.c:
+         group: Use proper group constructor
+         Otherwise we might en up having a group which is not backed by any asset
+         leading to possible assertion as this should never happen (see
+         https://gitlab.gnome.org/GNOME/pitivi/-/issues/2526)
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/232>
+
+2021-02-17 21:34:22 +1100  Jan Schmidt <jan@centricular.com>
+
+       * tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest:
+       * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected:
+         Update check_keyframes_in_compositor_two_sources
+         Update the validate expectation for videoconvert caps changes in
+         https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1033
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/229>
+
+2021-01-19 11:00:22 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+         project: Plug a leak
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-19 10:29:09 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Properly report error parsing restriction caps
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:29:47 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/meson.build:
+       * tests/check/scenarios/check-clip-positioning.validatetest:
+         test: Check clip positioning works when specifying track size
+         Make use of the new 'timeline specification' support in .validatetest
+         files.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:28:34 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         tools: Fix some naming
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:28:17 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         tools: Reindent options
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:27:30 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launch: Add encoding profiles to the project
+         So it is serialized on `--save`
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:26:36 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/ges-validate.c:
+       * tools/ges-validate.h:
+       * tools/utils.c:
+         validate: Handle passing timeline desc in .validatetest files
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:25:12 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesbasebin.c:
+       * plugins/ges/gesdemux.c:
+         plugin: Fix `is-ges-timeline` registration
+         We need to register it for all subclasses.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:23:13 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+         command-line-formatter: Stop uselessly looping over options
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:21:06 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-command-line-formatter.h:
+       * ges/ges-internal.h:
+       * ges/ges-xml-formatter.c:
+       * plugins/ges/gessrc.c:
+       * tools/utils.c:
+         command-line-formatter: Add a way to format timelines using the format
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 15:03:20 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * plugins/ges/gessrc.c:
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/utils.c:
+       * tools/utils.h:
+         ges: Use a `ges:` uri to define timeline from description
+         This way the command line formatter actually uses an URI and not
+         an ugly hack where were passing a random string instead of an URI.
+         This also allows the `gessrc` element to handle timelines described
+         in its URI meaning that you can now use, for example:
+         gst-play-1.0 "ges:+test-clip blue d=4.0
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 09:27:31 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-structure-parser.c:
+       * ges/parse.l:
+         ges: Add keyframe support to the command line formatter
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 09:25:11 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+       * ges/ges-structured-interface.h:
+       * ges/ges-validate.c:
+         structured-interface: Move set_control_source from ges-validate
+         So it can be reused in the command line formatter.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 09:13:59 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         structured-interface: Factor out method to get element to set property
+         Used to set properties or keyframes
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 08:49:20 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+         command-line-formatter: Reindent command line options array
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-15 08:47:10 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-xml-formatter.c:
+         formatter: Use the new `GstEncodingProfile:element-properties` property
+         Cleaning up the code and making everything simpler.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-14 08:05:59 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-validate.c:
+         ges: Minor debug logging level and typo fixes
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-12 15:55:52 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-structure-parser.c:
+       * ges/ges-structured-interface.c:
+       * ges/ges-structured-interface.h:
+       * ges/parse.l:
+       * tools/ges-launcher.c:
+         command-line-formatter: Add track management to timeline description
+         Instead of having it all handled by the tool, this way we can
+         set the restriction before clips are added to the timeline,
+         leading to better behavior in term of video images placement
+         in the scene.
+         Without that we would have the clips positioned before setting the
+         restriction caps which leads to weird behavior for the end users.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-13 15:18:04 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-source.c:
+       * ges/ges-video-test-source.c:
+         test-source: Respect asset natural size
+         We had cases where the frame positioner had the default natural size for
+         video test sources instead of the user provided one.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/227>
+
+2021-01-29 20:42:26 +0100  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * tools/ges-launcher.c:
+         ges-launcher: do not set rendering details too early
+         It looks like the _set_rendering_details call is superfluous
+         in _startup(), as it will get called in run_pipeline.
+         The problem with calling it before timeline_set_user_options
+         is that we are going to fail creating a smart profile if
+         the user selected eg --track-types=video, as the get_smart_profile
+         method compares the tracks in the asset with those on the timeline.
+         Reproduce with a video-only clip:
+         ges-launch-1.0 --track-types=video +clip file://$PWD/jelly.mp4 \
+         inpoint=15.0 -o foo.mp4 --smart-rendering
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/228>
+
+2019-10-29 17:03:14 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-structure-parser.c:
+       * ges/ges-structure-parser.h:
+       * ges/ges-structured-interface.c:
+       * ges/parse.l:
+         ges-structure-parser: force string types
+         Force a string type for structure values obtained through parsing a
+         serialized timeline by inserting a (string) specifier after a '=',
+         rather than relying on gst_structure_from_string guessing the type.
+         As such, the functions that extract clocktimes and properties are
+         modified to accept string value types.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/123>
+
+2019-10-29 16:29:24 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+         command-line-formatter: fix typos
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/123>
+
+2019-10-18 23:23:10 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-marker-list.c:
+       * tests/check/ges/markerlist.c:
+         marker-list: made deserialize reverse of serialize
+         Changed deserialize method to actually reverse the serialize method by
+         removing the edge quote marks and reversing g_strescape.
+         See https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/452
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/123>
+
+2020-12-13 22:54:37 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-uri-source.c:
+       * ges/ges-source.c:
+       * ges/ges-source.h:
+       * ges/ges-uri-source.c:
+       * ges/ges-uri-source.h:
+       * ges/ges-video-uri-source.c:
+         uri-source: Respect stream-id even on streams muxed in raw
+         The issue is that we rely on `decodebin::autoplug-select` to `SKIP`
+         unwanted pads, that signal was first provided to select factories during
+         autoplugin, not totally thought to avoid exposing pads. For streams
+         muxed directly in raw, decodebin has nothing to plug after the demuxer
+         and the pad is exposed right away, meaning that we do not have any
+         chance to avoid that pad to be exposed. This patch takes that limitation
+         into account and checks the stream ID of the pads exposed by decodebin
+         before exposing them itself, so we end up using the right pad even if
+         more are uselessly exposed by decodebin.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/126
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/222>
+
+2021-01-12 15:50:27 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-track.c:
+         audio-track: Respect track restrictions in our gaps
+         Avoiding not negotiated errors in specific cases.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/225>
+
+2021-01-05 11:52:15 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launch: Ensure to add required ref to profiles from project
+         We were unreffing something we were not owning
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/224>
+
+2020-11-02 22:18:24 +1100  Jan Schmidt <jan@centricular.com>
+
+       * tests/check/meson.build:
+         tests: fix meson test env setup to make sure we use the right gst-plugin-scanner
+         This is the same fix that was applied in gst-plugins-good in
+         https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/603
+         and fixes the testsuite running in gst-build.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/219>
+
+2020-09-04 10:27:05 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher-kb.c:
+       * tools/ges-launcher-kb.h:
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/meson.build:
+         launch: Add an interactive mode where we can seek etc...
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/209>
+
+2020-11-04 18:47:28 +0530  Nirbheek Chauhan <nirbheek@centricular.com>
+
+       * meson.build:
+         meson: Enable some MSVC warnings for parity with GCC/Clang
+         This makes it easier to do development with MSVC by making it warn
+         on common issues that GCC/Clang error out for in our CI configuration.
+         Continuation from https://gitlab.freedesktop.org/gstreamer/gst-build/-/merge_requests/223
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/220>
+
+2020-10-30 00:30:52 +1100  Jan Schmidt <jan@centricular.com>
+
+       * ges/ges.c:
+       * tools/ges-launcher.c:
+         init: Fix initialisation crash
+         Fix a case where initialisation fails without setting
+         the passed-in GError and the caller assumes it will be
+         set, and add a guard to catch the condition in case it
+         happens again in the future.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/217>
+
+2018-11-04 13:04:45 -0500  Xavier Claessens <xavier.claessens@collabora.com>
+
+       * ges/meson.build:
+       * meson.build:
+       * pkgconfig/gst-editing-services-uninstalled.pc.in:
+       * pkgconfig/gst-editing-services.pc.in:
+       * pkgconfig/meson.build:
+         Meson: Use pkg-config generator
+
+2020-10-18 16:08:36 +0200  Fabrice Fontaine <fontaine.fabrice@gmail.com>
+
+       * tools/ges-launcher.c:
+       * tools/utils.c:
+       * tools/utils.h:
+         utils.c: fix static build
+         Static build fails since version 1.17.1 and
+         https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/commit/1e488d4311420b5ca193155ad8ab05509c9a4a37
+         on:
+         FAILED: tools/ges-launch-1.0
+         /srv/storage/autobuild/run/instance-2/output-1/host/bin/arm-linux-gcc  -o tools/ges-launch-1.0 tools/ges-launch-1.0.p/ges-validate.c.o tools/ges-launch-1.0.p/ges-launch.c.o tools/ges-launch-1.0.p/ges-launcher.c.o tools/ges-launch-1.0.p/utils.c.o -Wl,--as-needed -Wl,--no-undefined -Wl,-O1 -Wl,-Bsymbolic-functions -static -Wl,--start-group ges/libges-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstreamer-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgobject-2.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libglib-2.0.a -pthread /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libpcre.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libffi.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgmodule-2.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstbase-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstvideo-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstpbutils-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstaudio-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libz.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgsttag-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgstcontroller-1.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libgio-2.0.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libmount.a /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libblkid.a -lm -Wl,--end-group
+         /srv/storage/autobuild/run/instance-2/output-1/host/opt/ext-toolchain/bin/../lib/gcc/arm-buildroot-linux-uclibcgnueabi/8.3.0/../../../../arm-buildroot-linux-uclibcgnueabi/bin/ld: /srv/storage/autobuild/run/instance-2/output-1/host/arm-buildroot-linux-uclibcgnueabi/sysroot/usr/lib/libc.a(err.os): in function `warn':
+         err.c:(.text+0x1d8): multiple definition of `warn'; tools/ges-launch-1.0.p/utils.c.o:utils.c:(.text+0x9bc): first defined here
+         So rename warn function to ges_warn
+         Also prefix ok, print and printerr function by ges_ for consistancy and
+         run gst-indent on tools/ges-launcher.c
+         Fixes:
+         - http://autobuild.buildroot.org/results/2a528a1185644f5b23d26eb3f2b342e99aa1e493
+         Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/216>
+
+2020-10-18 20:11:33 +0200  Antonio Ospite <antonio.ospite@collabora.com>
+
+       * meson.build:
+         meson: actually check glib dependency version
+         Actually check the version constraint when looking for the glib
+         dependency.
+         The version check will make meson use the fallback dependency when the
+         one from the system is not recent enough, and eventually make the build
+         succeed even on some older systems like Ubuntu 16.04.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/212>
+
+2020-10-16 13:17:04 +0200  Stéphane Cerveau <scerveau@collabora.com>
+
+       * ges/ges-asset.c:
+       * meson.build:
+         meson: update glib minimum version to 2.56
+         In order to support the symbol g_enum_to_string in various
+         project using GStreamer ( gst-validate etc.), the glib minimum
+         version should be 2.56.0.
+         Remove compat code as glib requirement
+         is now > 2.56
+         Version used by Ubuntu 18.04 LTS
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/215>
+
+2020-09-03 23:32:23 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-auto-transition.c:
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline.c:
+       * ges/ges-uri-clip.c:
+       * tests/check/python/common.py:
+       * tests/check/python/test_assets.py:
+       * tests/check/python/test_timeline.py:
+         ges: Do not recreate auto-transitions when changing clip assets
+         Otherwise we loose the configuration of the auto transition, and
+         it is not required at all in any case.
+         Fixes https://gitlab.gnome.org/GNOME/pitivi/-/issues/2380
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/208>
+
+2020-09-08 11:39:10 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/meson.build:
+         ges: Fix a copy/paste mistake in meson file
+         Passed unnoticed because we built against GstValidate
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/119
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/211>
+
+2020-09-03 21:15:16 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-transition.c:
+       * ges/ges-video-transition.h:
+         video-transition: Make smpte props children properties
+         And deprecate old style accessors.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/207>
+
+2020-09-08 17:30:53 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * .gitlab-ci.yml:
+         ci: include template from gst-ci master branch again
+
+2020-09-08 16:59:02 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+         Back to development
+
+=== release 1.18.0 ===
+
+2020-09-08 00:09:25 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * .gitlab-ci.yml:
+       * ChangeLog:
+       * NEWS:
+       * RELEASE:
+       * gst-editing-services.doap:
+       * meson.build:
+         Release 1.18.0
+
+2020-09-04 10:43:05 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+       * plugins/ges/gesdemux.c:
+         demux: Fixate documentation caps
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/210>
+
+2020-08-22 00:57:06 +1000  Jan Schmidt <jan@centricular.com>
+
+       * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected:
+         complex_effect_bin_desc: Regenerate expectation for compositor change
+         Part of: https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/796
+
+2020-08-20 21:09:31 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/meson.build:
+         tests: Fix running tests fully uninstalled
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/118
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/206>
+
+=== release 1.17.90 ===
+
+2020-08-20 16:16:01 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * ChangeLog:
+       * NEWS:
+       * RELEASE:
+       * gst-editing-services.doap:
+       * meson.build:
+         Release 1.17.90
+
+2020-07-31 22:02:01 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-source.c:
+         ges:source: Handle missing elements in converters
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-22 12:02:10 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-smart-video-mixer.c:
+       * ges/ges-video-transition.c:
+       * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected:
+         smart-mixer: Move the videoconvert to after the mixer
+         So that it tries to negotiate with alpha and the alpha channel is
+         dropped as late as possible in the pipeline.
+         The compositor is able to do video conversion internally in any case
+         so having a videoconvert before it is useless.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-21 08:49:35 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-transition.c:
+         transition: Enhance name of the elements
+         Making it simpler to debug
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-20 17:32:39 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-source.c:
+         source: Handle missing elements in converter
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-14 00:09:32 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-source.c:
+         video-source: Stop giving useless name to frame positioner
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-13 18:18:22 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-smart-video-mixer.c:
+       * ges/ges-smart-video-mixer.h:
+       * ges/ges-utils.c:
+       * ges/ges-video-transition.c:
+         transition: Better document the way alpha is computed for transitions
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-12 13:51:42 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-smart-video-mixer.c:
+       * ges/ges-timeline.c:
+       * ges/ges-utils.c:
+       * tests/check/meson.build:
+       * tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest:
+       * tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected:
+         smart-mixer: Use the new 'samples-selected' signal to handle queuing in aggregator pads
+         Since aggregator introduced queueing in its sinkpads the way we set
+         properties on the pads is incorrect as it doesn't take it into account.
+         This fixes the issue by using the newly introduced `samples-selected`
+         signal in aggregator to set the properties right before the compositing
+         is done.
+         Also require the compositor we use to be an aggregator.
+         And add a validate test for it.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-12 13:49:36 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+       * ges/ges-validate.c:
+         ges:validate: Allow setting keyframes using the clips directly
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/204>
+
+2020-07-25 13:14:56 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-source.c:
+         ges-source: Ensure that we output stream with segments in time
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-09 11:10:41 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: Restrict the presence only if the user didn't explicitly provided one
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-08 15:47:55 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: Add a simplified version of track selection signal
+         Most user do not need to select several tracks for a single
+         TrackElement and this signal is not binding friendly so
+         this is adding a simpler, more user and binding friendly version
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-08 15:47:12 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-source.c:
+         uri-source: Respect user stream selection
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-08 08:02:27 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-validate.c:
+         launch: Also print the position when disabling validate
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-08 08:01:58 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * meson.build:
+       * tools/ges-launcher.c:
+       * tools/meson.build:
+       * tools/utils.c:
+       * tools/utils.h:
+         launch: Print more useful information to stdout
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-08 07:42:38 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * meson_options.txt:
+       * tools/ges-launcher.c:
+         build: Add an option to disable examples
+         And make it yield as in other modules
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 18:21:22 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launcher: Re activate smart rendering support
+         Trying to get the best encoding profile for smart rendering when
+         the user didn't specify anything.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 18:16:13 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-enums.h:
+       * ges/ges-internal.h:
+       * ges/ges-pipeline.c:
+       * ges/ges-source.c:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline-tree.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track.c:
+       * ges/ges-uri-source.c:
+         ges: Fix smart rendering
+         Smart rendering has been broken since, mostly forever, but some code
+         was there pretending it was supported... let's try to stop pretending.
+         We now keep track of the smart rendering state in the timeline, track
+         and sources to be able to:
+         * tell decodebin to stop plugging more (decoding elements) as soon as
+         downstream supports the format.
+         * avoid plugging converters after the source element when smart
+         rendering.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 18:00:39 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/validate/geslaunch.py:
+         validate: Pipe debug output to a file when discovering scenarios
+         Otherwise `gst-validate-launcher` can get veeery noisy
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 17:59:49 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlesource.c:
+         nle: Minor debug enhancement
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 17:58:16 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/ges/clip.c:
+         tests: Mark audio identity as audio
+         Otherwise GES fallbacks to video...
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-01-13 13:08:24 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-internal.h:
+       * ges/ges-pipeline.c:
+       * ges/ges-track.c:
+         pipeline: stop setting the track caps
+         Stop setting the track 'caps' property. The previous code could
+         overwrite a users own setting of the caps for video and audio caps.
+         Moreover, the 'caps' property is listed as construct only, and users
+         will likely expect it to stay the same after a track has been added to a
+         timeline.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 17:41:28 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launcher: Delay setting rendering setting to right before rendering
+         So that user settings have been applied to the timeline taking into
+         account any `validatetest` arguments
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 17:18:51 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-source.c:
+       * ges/ges-internal.h:
+       * ges/ges-source.c:
+       * ges/ges-video-source.c:
+       * ges/ges-video-test-source.c:
+         source: Refactor the way we plug converter elements
+         Paving the way to skipping converters when rendering smartly
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 17:02:45 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: Do not name urisink as `urisink` as it is useless
+         And actually harmful in case you are debugging several pipelines.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 17:01:18 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: Remove urisink from timeline instead of unrefing it
+         Doing what was suggested in the FIXME and avoiding to unref
+         something it while we do not actually own it ourself.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 16:52:06 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: Discard encoding profiles that don't match any track
+         Otherwise we get a 'not linked' error and we should just help
+         the user as we can here.
+         If the user adds a new track, he should set a new encoding profile
+         anyway.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-03 16:34:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-uri-source.c:
+       * ges/ges-audio-uri-source.h:
+       * ges/ges-uri-source.c:
+       * ges/ges-uri-source.h:
+       * ges/ges-video-uri-source.c:
+       * ges/ges-video-uri-source.h:
+       * ges/meson.build:
+         uri*source: Factor out common logic into a GESUriSource private data
+         The two classes are *very* close but have different hierarchy so this
+         introduces a new GESUriSource structure that is used as private
+         structure by both subclasses and makes most of the logic shared this
+         way.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-06-24 11:11:11 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-uri-source.c:
+       * ges/ges-video-uri-source.c:
+         *uri-source: Call free from the object ->finalize not ->dispose
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/198>
+
+2020-07-25 19:16:06 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+       * meson_options.txt:
+       * tools/meson.build:
+         meson: install bash completion helper for ges-launch-1.0
+         Fixes #77
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/203>
+
+2020-07-25 19:09:30 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+       * meson_options.txt:
+         meson: add 'tools' and 'examples' options
+         To optionally disable build of those.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/203>
+
+2020-07-24 07:43:05 +0530  AsociTon <asociton@outlook.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * tests/check/python/test_assets.py:
+         Fix retrieving asset metadata on project reload.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/202>
+
+2020-01-21 16:02:56 +0530  yatinmaan1@gmail.com <yatinmaan1@gmail.com>
+
+       * tests/check/python/test_clip.py:
+         tests: Add test for ges_clip_get_top_effect_index
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/201>
+
+2020-07-14 10:20:32 +0200  Guillaume Desmottes <guillaume.desmottes@collabora.com>
+
+       * tests/check/ges/clip.c:
+         tests: clip: fix test_rate_effects_duration_limit
+         Fix this assertion:
+         g_value_copy: assertion 'g_value_type_compatible (G_VALUE_TYPE (src_value), G_VALUE_TYPE (dest_value))' failed
+         'tempo' is a float, not a double.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/199>
+
+2020-07-10 08:16:10 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/meson.build:
+         build: Add version.h to the headers list
+         So it is properly installed and the gir contains the required information
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/75
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/197>
+
+2020-07-09 21:42:50 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pitivi-formatter.h:
+         pitivi-formatter: Also skip the class
+
+2020-07-08 17:33:07 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+       * scripts/extract-release-date-from-doap-file.py:
+         meson: set release date from .doap file for releases
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/196>
+
+2020-07-08 10:03:43 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-title-clip.h:
+         title: Make deprecated symbols visible API
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/195>
+
+2020-07-03 02:04:08 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+         Back to development
+
+=== release 1.17.2 ===
+
+2020-07-03 00:35:20 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * ChangeLog:
+       * NEWS:
+       * RELEASE:
+       * gst-editing-services.doap:
+       * meson.build:
+         Release 1.17.2
+
+2020-06-23 16:11:59 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * docs/libs/GESTimeOverlayClip-children-props.md:
+       * docs/libs/GESTitleSource-children-props.md:
+       * docs/libs/GESVideoTestSource-children-props.md:
+       * docs/libs/GESVideoUriSource-children-props.md:
+       * ges/ges-track.c:
+       * plugins/nle/nleoperation.c:
+         docs: fix links
+
+2020-06-23 00:05:13 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * docs/gst_plugins_cache.json:
+         plugins_cache: add base classes
+
+2020-06-23 00:04:52 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * docs/meson.build:
+         meson: mark plugins cache target as always stale
+
+2020-06-21 01:42:26 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * plugins/ges/gesbasebin.c:
+       * plugins/nle/nleobject.c:
+         docs: mark more types as plugin API
+
+2020-06-19 22:56:41 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+         doc: Stop documenting properties from parents
+
+2020-06-22 12:34:20 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * ges/ges-smart-video-mixer.c:
+         smart-video-mixer: Don't call gst_ghost_pad_construct() anymore
+         It's deprecated, unneeded and doesn't do anything anymore.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/192>
+
+2020-06-20 00:28:31 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+         Back to development
+
+=== release 1.17.1 ===
+
+2020-06-19 19:25:56 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * ChangeLog:
+       * NEWS:
+       * RELEASE:
+       * gst-editing-services.doap:
+       * meson.build:
+         Release 1.17.1
+
+2020-06-19 11:13:24 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip-asset.c:
+       * ges/ges-clip-asset.h:
+       * ges/ges-clip.c:
+       * ges/ges-enums.c:
+       * ges/ges-layer.c:
+       * ges/ges-marker-list.c:
+       * ges/ges-marker-list.h:
+       * ges/ges-meta-container.c:
+       * ges/ges-project.h:
+       * ges/ges-source-clip-asset.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element-asset.c:
+       * ges/ges-track-element-asset.h:
+       * ges/ges-track-element.c:
+       * ges/ges-types.h:
+       * ges/ges-uri-asset.c:
+       * ges/ges-video-source.c:
+         ges: Add all missing Since markers from 1.16 onward
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/191>
+
+2020-06-09 10:07:13 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+         asset: Do not try to update proxies when we are in a proxying loop
+         This is a regression introduced in
+         c12b84788d197c714ec32653e2b751079e377c46, this commit simply brings back
+         the previous behavior.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/113
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/185>
+
+2020-06-09 00:03:57 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-effect-asset.c:
+       * ges/ges-effect-clip.c:
+       * ges/ges-effect.c:
+       * ges/ges-gerror.h:
+       * ges/ges-internal.h:
+       * tests/check/meson.build:
+       * tests/check/scenarios/complex_effect_bin_desc.validatetest:
+       * tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected:
+         ges: Refactor the way we plug converters in effects
+         Stopping to do it at the bin description level but properly
+         plugging them where they are needed and cleanly ghosting the pads
+         where it makes most sense.
+         This introduces support for GES to request pads on the most upstream
+         element in case no static pad can be ghosted.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/187>
+
+2020-06-09 16:40:11 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-structured-interface.c:
+         structured-interface: Add support for setting effects inpoint
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/187>
+
+2020-06-09 16:35:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track-element.c:
+       * ges/ges-track-element.h:
+         track-element: Make set_has_internal_source return a boolean
+         Telling the user if it is legal to have an internal source in that
+         particular GESTrackElement.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/187>
+
+2020-06-15 13:09:39 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: doc: Add a note about trying to render before setting rendering settings
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/189>
+
+2020-06-15 12:23:26 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-uri-clip.c:
+         uri-clip: Add a warning about synchronous uri discovery
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/189>
+
+2020-06-09 15:22:30 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+         docs: Update plugins cache
+
+2020-06-08 10:58:43 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+         docs: Update plugins cache
+
+2020-06-05 15:56:00 +0200  Guillaume Desmottes <guillaume.desmottes@collabora.com>
+
+       * tests/check/scenarios/edit_while_seeked_with_stop.validatetest:
+       * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest:
+       * tests/check/scenarios/seek_with_stop.validatetest:
+         tests: enforce I420 format
+         Tests are assuming video is I420 with a specific chroma and colorimetry
+         but were not actually enforcing it.
+         Fixes needed as I420 will no longer be the first video format, see
+         gst-plugins-base!689
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/184>
+
+2020-06-04 23:14:59 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * docs/gst_plugins_cache.json:
+       * ges/ges-track.c:
+       * plugins/nle/nlecomposition.c:
+         track, composition: mark stream id properties as DOC_SHOW_DEFAULT
+         and update plugins cache
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/182>
+
+2020-06-03 18:30:39 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/meson.build:
+         doc: Require hotdoc >= 0.11.0
+
+2020-05-27 16:03:35 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * docs/gst_plugins_cache.json:
+         docs: Update gst_plugins_cache.json
+
+2020-06-03 09:57:06 +0200  Guillaume Desmottes <guillaume.desmottes@collabora.com>
+
+       * ges/ges-base-effect.c:
+       * ges/ges-base-effect.h:
+       * ges/ges-clip.c:
+       * ges/ges-enums.h:
+       * ges/ges-gerror.h:
+       * ges/ges-layer.c:
+       * ges/ges-time-overlay-clip.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element.c:
+       * ges/ges-track.c:
+         add missing Since annotations on new API
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/181>
+
+2020-05-27 19:44:29 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pitivi-formatter.c:
+         formatter: Do not dereference NULL pointer
+         CID 1461701
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/180>
+
+2020-05-27 19:39:49 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+         xml-formatter: Add an GST_ERROR when setting control sources fails
+         CID 1463853
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/180>
+
+2020-05-26 19:14:53 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Wait for state change to consider commit as done
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/178>
+
+2020-05-26 19:02:58 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/validate/geslaunch.py:
+         validate: Stop always muting
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/178>
+
+2020-05-21 17:22:18 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-xml-formatter.c:
+       * tests/check/python/common.py:
+       * tests/check/python/test_timeline.py:
+         formatter: Fix saving/loading project with clip speed rate control
+         We need to ensure that clips duration is set after time effects are
+         added and we now need to serialize effects inpoints and max duration.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-21 15:42:23 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * docs/design/time_notes.md:
+         docs: add some notes on Time in GES
+         These notes cover time coordinates in GES, time effects, time
+         translations.
+         It also goes into why keyframes will not work with non-linear time
+         effects.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-21 11:25:30 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-uri-clip.c:
+       * tests/check/ges/asset.c:
+         uri-clip: don't assume duration needs to stay the same
+         ges_uri_clip_asset_get_duration does not tell us what the duration in
+         the timeline needs to be. Especially when we have time effects, or
+         effects with finite max-durations. So we should no longer expect the
+         duration to stay the same when replacing assets. Instead, we just check
+         that the new max-duration would be compatible with the current in-point
+         (which was not checked before), and the clip would not be totally
+         overlapped if its duration-limit changes.
+         This is based on the assumption that each source is replaced one-to-one
+         in its track. If a source is replaced with nothing in the same track,
+         this check may be a little too strong (but still mostly weaker than
+         before). However, problems could occur if track selection does
+         something unexpected, such as placing the new source in a track not
+         previously occupied.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-20 21:23:03 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: provide an example of using time effects
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-20 21:20:10 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track-element.h:
+       * ges/ges-xml-formatter.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/project.c:
+         track-element: use out-point for updating control bindings
+         The out-point, which is an internal time, is used instead of the
+         duration for determining the control binding value at the end of the
+         element.
+         Also, allow the user to switch off the auto-clamping of control sources
+         if they are not desired. And allow them to clamp specific control sources
+         individually.
+         Also, fix a lot of memory leaks related to control sources. In
+         particular, releasing the extra ref gained by source in
+         g_object_get (binding, "control-source", &source, NULL);
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 18:09:50 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: test for layer in group
+         Make sure the layer exists before we try to remove the grouped clips
+         from it.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 14:58:08 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-internal.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline.c:
+         timeline-tree: make sure the layer priority refers to an existing layer
+         If a layer priority sits between the priorities of two layers in the
+         timeline, i.e. it references a gap in the timeline's layers, then
+         ges_timeline_append_layer will never fill this gap and create the
+         desired layer, so the edit in timeline-tree would loop forever. So a
+         check was added to avoid this.
+         This would be a usage error, but a user can reasonably end up with a gap
+         in their layers if they remove a layer from the timeline.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 14:53:49 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * ges/ges-internal.h:
+       * ges/ges-timeline.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/effects.c:
+         clip: add method for adding top effects
+         Unlike ges_container_add, this lets you set the index and will check
+         that track selection did not fail. This is useful for time effects whose
+         addition would create an unsupported timeline configuration.
+         Also can use the clip add error in ges_timeline_add_clip to let the user
+         know when adding a clip to a layer that its in-point is set larger than
+         the max-duration of its core children.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 14:47:15 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-enums.h:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-tree.c:
+       * tests/check/python/test_timeline.py:
+         timeline-tree: take time effects into account when trimming
+         When trimming the start of a clip, we want to set the in-point of its
+         children such that whatever data was at the timeline time T still
+         remains at the timeline time T after the trim, where
+         T = MAX (prev_start, new_start)
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 14:41:58 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * tests/check/ges/tempochange.c:
+         clip: use time translation for split
+         The new in-point should be the media position corresponding to the media
+         position. media_duration_factor is no longer needed.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-18 17:34:01 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: fix warning when getting duration-limit
+         The duration-limit case was missing a 'break;' statement.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-12 18:18:09 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * ges/ges-timeline-element.c:
+       * tests/check/ges/clip.c:
+         clip: add methods to convert between time coordinates
+         Add methods to convert between the timeline time coordinates and the
+         internal time coordinates of a track element in a clip, taking time
+         effects into account.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 14:28:09 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-base-effect-clip.c:
+       * ges/ges-base-effect.c:
+       * ges/ges-base-effect.h:
+       * ges/ges-clip.c:
+       * ges/ges-effect.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element.c:
+       * tests/check/ges/clip.c:
+         effect: Add support for time effects
+         Allow the user to register a child property of a base effect as a time
+         property. This can be used by GES to correctly calculate the
+         duration-limit of a clip when it has time effects on it. The existing
+         ges_effect_class_register_rate_property is now used to automatically
+         register such time effects for rate effects.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 14:25:01 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * ges/ges-gerror.h:
+       * ges/ges-group.c:
+       * ges/ges-internal.h:
+       * ges/ges-layer.c:
+       * ges/ges-layer.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline-tree.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track.c:
+       * ges/ges-track.h:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/test-utils.h:
+       * tests/check/python/common.py:
+       * tests/check/python/test_timeline.py:
+         errors: added edit errors
+         Added more errors to GES_ERROR for when edits fail (other than
+         programming or usage errors). Also promoted some GST messages if they
+         related to a usage error.
+         Also added explanation of timeline overlap rules in user docs.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/177>
+
+2020-05-15 12:19:16 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/scenarios/seek_with_stop.validatetest:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected:
+         tests: Stop recording gaps in seek_with_stop
+         We have little control over those as they are generated by streamsynchronizer in a not reproducible way
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-05-15 11:53:10 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/libs/GESTitleSource-children-props.md:
+       * docs/libs/GESVideoTestSource-children-props.md:
+         docs: Remove reference to deinterlacing props in title and video test source
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-05-15 18:33:46 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Fix setting ges properties
+         And fix typos.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-04-22 13:39:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/libs/GESTimeOverlayClip-children-props.md:
+       * docs/libs/document-children-props.py:
+       * docs/sitemap.txt:
+       * ges/ges-internal.h:
+       * ges/ges-source-clip.c:
+       * ges/ges-test-clip.c:
+       * ges/ges-test-clip.h:
+       * ges/ges-time-overlay-clip.c:
+       * ges/ges-time-overlay-clip.h:
+       * ges/ges-video-source.c:
+       * ges/ges-video-test-source.c:
+       * ges/ges-video-test-source.h:
+       * ges/ges.h:
+       * ges/meson.build:
+       * tests/check/ges/clip.c:
+       * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario:
+       * tests/check/scenarios/edit_while_seeked_with_stop.validatetest:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest:
+       * tests/check/scenarios/seek_with_stop.validatetest:
+         ges: Move TimeOverlayClip out of GESTestClip
+         This was complexifying the implementation for very little gain.
+         Each source type should ideally have its own API.
+         In that patch we make it so we do not have to subclass anything
+         but instead use GESAsset to pass information about how the pipeline
+         should look like.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-05-14 00:56:40 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Add stack initialization action after setting our state
+         Otherwise there is a pretty rare race where we get the
+         _initialize_stack_func executed leading to the stack set up and
+         the source pushing buffers before the composition source pad is
+         activated, and a STREAM_ERROR is reported as we end up pushing a
+         buffer to a flushing pad.
+         Thanks rr chaos mode for showing that improbable race
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-05-13 17:11:24 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-internal.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track.c:
+         timeline: No thread checking while disposing
+         While this is not correct, we can't predict from what thread a
+         GstElement will be disposed as it might still be referenced by
+         a GstMessage somewhere which is freed by, any thread.
+         In this specific case we can assume that GES user will already have
+         let go his timeline reference and we should not avoid assert in that
+         specific case as it should be safe to let the timeline be destroyed
+         at that point.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-05-01 23:05:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nleobject.c:
+         nle: Use G_PARAM_DEPRECATED for media-duration-factor
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/175>
+
+2020-05-18 08:49:53 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         ges: Ensure that assets are added to project before adding clip to timeline
+         It is the right ordering and in Pitivi we set the project size
+         when adding the first (relevant) asset, meaning that our code to
+         reposition clips would kick in (in the unit tests) if we do not respect
+         that ordering.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/176>
+
+2020-05-13 12:11:32 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track-element.h:
+       * ges/ges-uri-clip.c:
+         track-element: Add is_core method to API
+         Open up the method to the user, since they may need the information.
+         Also added more documentation on what a core track element is to a clip
+         and how they are treated.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-05-01 12:40:58 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-project.c:
+       * tests/check/ges/asset.c:
+       * tests/check/ges/backgroundsource.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/group.c:
+       * tests/check/ges/layer.c:
+         asset: unref requested assets
+         Prevent a few memory leaks in the tests.
+         Also mark ges_project_save as transfer full for the formatter asset.
+         Also make sure that ges_project_request_sync is transfer full on the
+         returned asset.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/104
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-30 12:10:22 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-track-element.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/test-utils.h:
+         clip: enforce duration-limit
+         Prevent setting of properties or that of children, if the clip would not
+         be able to set the corresponding duration if the duration-limit would
+         drop below the currently set duration.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-30 12:01:52 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * tests/check/ges/clip.c:
+         clip: make sure core child is active for non-core in same track
+         Each active non-core child must have a corresponding active core child
+         in the same track. Therefore, if we de-activate a core child, we also
+         need to de-activate all the non-core children in the same track.
+         Similarly, if we activate a non-core child, we need to activate the
+         corresponding core child as well.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-30 11:50:08 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: be more robust in handling priority
+         Make less assumptions about the priority of effects and core elements so
+         that the code would still work if the priority of an element was set
+         directly. In particular, the index of a top effect will always be its
+         position in the effect ordering.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-28 17:29:22 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-internal.h:
+         container: stop storing priority offset in child mapping
+         GESGroup no longer uses this, and GESClip can be made simpler without
+         it.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-27 19:11:16 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-auto-transition.c:
+       * ges/ges-auto-transition.h:
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline.c:
+       * tests/check/ges/clip.c:
+         clip: preserve auto-transition in split
+         When splitting a clip, keep the auto-transition at the end of the clip
+         alive and move its source to that of the corresponding split track
+         element.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-21 12:55:34 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * tests/check/ges/clip.c:
+         clip: change order of split
+         We first change the duration of the splitted clip, then we add the new
+         clip to the layer and assign the tracks for its children. Normally, when
+         a clip is added to a layer it will have its track elements created, if
+         needed, and then assigned to their tracks. This will fail if any sources
+         would fully or triple overlap existing sources in the same track.
+         However, here we were adding the clip to the layer *and* avoiding the
+         track assignment process and instead setting the tracks explicitly. In
+         particular, the order was:
+         + add new clip to layer with no tracks assigned
+         + shrink the split clip
+         + assign the tracks for the new clip
+         This has been changed to:
+         + shrink the split clip
+         + add new clip to layer with no tracks assigned
+         + assign the tracks for the new clip
+         Thus, the order of events for any users connecting to object signals
+         will be close to that of adding another clip to the layer.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-27 16:27:15 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-timeline.c:
+         timeline: create auto-transitions during track-element-added
+         Any time a track element is added to a track, we need to check whether
+         we need to create a new corresponding auto-transition. This simply moves
+         the code from ges-clip.c to ges-timeline.c, where it is more appropriate.
+         Moreover, it technically opens the possibility for creating
+         auto-transitions for track elements in the timeline that have no
+         corresponding clip.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-27 16:05:54 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline-tree.c:
+       * tests/check/python/test_timeline.py:
+         timeline-tree: also trim non-core track elements
+         Also trim the in-point of non-core children of clips to ensure that
+         their content will appear in the timeline at the same position.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-24 21:00:18 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+       * tests/check/ges/basic.c:
+         timeline: make sure appended layer has lowest priority
+         Make sure that the priority of an appended layer is the lowest (highest
+         in value) when appending a layer to the timeline. This change is
+         important when appending a layer to a timeline, which can easily have a
+         gap in priorities if a layer has been removed.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-23 17:34:52 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * tests/check/python/common.py:
+       * tests/check/python/test_timeline.py:
+         tests: add tests for new editing behaviour
+         These tests expose some of the new editing behaviour in timeline
+         tree. In particular, we test:
+         + edits for clips within groups within a group
+         + that an edit can succeed if a snap allows it to
+         + that snapping occurs at a specific point, and that we alternate
+         between one call to snapping-started and one call to snapping-ended
+         with corresponding values
+         + that an edit can fail if a snap causes it to
+         + no snapping is released when an edit fails
+         + We tests for the expected changes, and otherwise check that the
+         configuration of the timeline has remained unchanged
+         + The timeline configuration remains the same when an edit fails
+         + That each clip overlap has a corresponding auto-transition
+         + That particular auto-transitions are created when a new overlap is
+         formed
+         + That particular auto-transitions are destroyed when an overlap ends
+         + That auto-transitions are not replaced when two clips move but
+         maintain their overlap
+         + That the timeline does not contain any unaccounted for clips
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-23 17:30:17 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-layer.c:
+       * ges/ges-timeline.c:
+         layer: don't set timeline when moving clip
+         If a clip is moving we should not unset its timeline when it is removed
+         from the layer. Logic has been moved to ges_timeline_add_clip and
+         ges_timeline_remove_clip.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-22 15:06:32 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-auto-transition.c:
+       * ges/ges-auto-transition.h:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline.c:
+         timeline-tree: freeze auto-transitions whilst editing
+         Freeze the auto-tranistions so they do not destroy themselves during an
+         edit. Once complete the auto-transitions can move themselves back into
+         position, or remove themselves if their sources are no longer
+         overlapping.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-21 15:06:03 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline-tree.h:
+         clip: make auto-transitions less expensive when adding to track
+         Only check the overlaps with the actual track element that was just added
+         to the track. This reduces the tree traversal by one order.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-21 14:05:55 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-layer.c:
+       * tests/check/ges/clip.c:
+         clip: remove children if failed to add to layer
+         If adding to a layer fails during ges_timeline_add_clip, any new children
+         that were created during this process should be removed from the clip to
+         put it back into its previous state.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-21 11:36:58 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-group.c:
+         group: let timeline-tree handle layer priority
+         Since a group can only have its priority set whilst it is part of a
+         timeline, we can simply let the timeline-tree handle the move, which it
+         can already do, whilst checking that the move would be legal (not break
+         the timeline configuration). All the group has to do now if update its
+         priority value if the priority of any of its children changes. It
+         doesn't even need to keep track of the layer priority offsets.
+         Also, added a check to ensure added children belong to the same
+         timeline.
+         Also moved the sigids from the GObject data to a g_hash_table, which is
+         clearer.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-20 14:56:55 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-group.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+         timeline-element: stop using edit vmethods
+         These were all redirecting to essentially ges_timeline_element_edit
+         anyway.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-20 13:13:48 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-auto-transition.c:
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-group.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-tree.c:
+         timeline-element: simplify check for being edited
+         It should be sufficient to set the edit flag only on the toplevel, which
+         allows all of its children to know they are being edited and should not
+         move in response.
+         Also, removed some unnecessary setting/checking of this.
+         Also, supplied the ges_timeline_element_peak_toplevel, which unlike
+         ges_timeline_element_get_toplevel_parent, does not add a reference to
+         the toplevel. Some corresponding leaks in auto-transition have been
+         fixed by using this instead.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-27 14:05:38 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+       * tests/check/python/test_timeline.py:
+         timeline: emit snapping-started with new valid time
+         Only emit snapping-ended if we have a valid snap time. Moreover, we
+         should emit a new snapping-started even if we are snapping at the same
+         location. This is because a new snap will always correspond to a new edit,
+         possibly involving different snapping elements, which a user would want
+         to know about.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-27 13:58:38 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-enums.c:
+       * ges/ges-enums.h:
+       * ges/ges-group.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline-tree.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track.c:
+       * tests/check/ges/layer.c:
+       * tests/check/ges/timelineedition.c:
+       * tests/check/python/test_timeline.py:
+       * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario:
+         timeline-tree: simplify and fix editing
+         Editing has been simplified by breaking down each edit into a
+         combination of three basic single-element edits: MOVE, TRIM_START, and
+         TRIM_END.
+         Each edit follows these steps:
+         + Determine which elements are to be edited and under which basic mode
+         + Determine which track elements will move as a result
+         + Snap the edit position to one of the edges of the main edited element,
+         (or the edge of one of its descendants, in the case of MOVE), avoiding
+         moving elements.
+         NOTE: in particular, we can *not* snap to the edge of a neighbouring
+         element in a roll edit. This was previously possible, even though the
+         neighbour was moving!
+         + Determine the edit positions for clips (or track elements with no
+         parent) using the snapped value. In addition, we replace any edits of
+         a group with an edit of its descendant clips. If any value would be
+         out of bounds (e.g. negative start) we do not edit.
+         NOTE: this is now done *after* checking the snapping. This allows the
+         edit to succeed if snapping would cause it to go from being invalid to
+         valid!
+         + Determine whether the collection of edits would result in a valid
+         timeline-configuration which does not break the rules for sources
+         overlapping.
+         + If all this succeeds, we emit snapping-started on the timeline.
+         + We then perform all the edits. At this point they should all succeed.
+         The simplification/unification should make it easier to make other
+         changes.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/97
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/98
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-18 16:49:31 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-group.c:
+       * tests/check/ges/group.c:
+         group: fix priority setting
+         Stop moving the group if a child clip is being edited by timeline-tree,
+         a child group is updating its own priority, or a layer that a clip is in
+         has changed priority. A group should only move if a descendant moves
+         layers outside of a timeline-tree edit, or the priority of the group is
+         set by the user.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/89
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-18 16:34:56 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * ges/ges-group.c:
+       * ges/ges-internal.h:
+         container: keep start and duration up to date
+         Simplified keeping the start and the duration of a container/group up to
+         date with the earliest start of the children and the last end of the
+         children. The previous logic was spread between ges-group and
+         ges-container, now all the position handling is in ges-container.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-28 18:01:04 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-uri-clip.c:
+         uri-clip: use duration-limit in set_max_duration
+         Use the duration-limit rather than max-duration - in-point, since the
+         former will be able to take other factors, such as effects, into
+         account.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-04-13 17:42:22 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/test-utils.h:
+         clip: add the duration-limit property
+         The duration-limit is the maximum duration that can be set for the clip
+         given its current children and their properties. If a change in the
+         children properties causes this to drop below the current duration, it
+         is automatically capped by this limit.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/169>
+
+2020-05-04 10:35:25 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-formatter.c:
+       * ges/python/gesotioformatter.py:
+         ges: Output otio formatter loading issues in debug logs
+         Instead of spamming the terminal with a python traceback
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/107
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/173>
+
+2020-05-05 23:03:36 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/scenarios/seek_with_stop.validatetest:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected:
+         tests: Stop recording segment position in seek_with_stop
+         There are two valid timing in GstAggregator where the segment event
+         is pushed before GstAggregator sets its srcpad->segment.position in
+         gst_aggregator_pad_chain_internal. Segment.position is basically
+         a helper field for internal elements use so we should not require
+         a specific value here as we are not checking a particular element
+         behavior.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/106
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/174>
+
+2020-05-02 01:24:18 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/validate/geslaunch.py:
+         test: Add support for .validatetest in the launcher app
+
+2020-05-01 14:26:32 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+         container: return TRUE if adding doesn't cause any errors
+         If `add_child` and `set_parent` succeed we want to return TRUE, even if
+         the added element is no longer a child by the end of the method. This is
+         because some users may call ges_container_remove during `child-added`.
+         This shouldn't be considered an error.
+
+2020-04-30 17:44:33 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/scenarios/edit_while_seeked_with_stop.validatetest:
+       * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.validatetest:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected:
+         ges:tests: Fix the `ignore-fields` format in validatetests
+         They are needed as those are not 100% reproducible with GES.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/168>
+
+2020-04-30 13:23:05 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesbasebin.c:
+         plugin: Fix a race removing tracks from timeline from the wrong thread
+         The case was that the timeline state was being changed from the parent
+         composition's action thread before the timeline was committed, leading
+         to the SELECT_STREAM event to be pushed from the track to the nested
+         timeline from the wrong composition thread.
+         ```
+         **
+         GES:ERROR:../subprojects/gst-editing-services/ges/ges-track.c:1263:ges_track_remove_element: assertion failed: (track->priv->valid_thread == g_thread_self())
+         Bail out! GES:ERROR:../subprojects/gst-editing-services/ges/ges-track.c:1263:ges_track_remove_element: assertion failed: (track->priv->valid_thread == g_thread_self())
+         Thread 1 (Thread 0x7f6ec2d43700 (LWP 1228982)):
+         #0  0x00007f6ed85b2a25 in raise () at /lib64/libc.so.6
+         #1  0x00007f6ed859b895 in abort () at /lib64/libc.so.6
+         #2  0x00007f6ed899cb8c in g_assertion_message (domain=<optimized out>, file=0x7f6ed8d7fd58 "../subprojects/gst-editing-services/ges/ges-track.c", line=<optimized out>, func=<optimized out>, message=<optimized out>) at ../glib/gtestutils.c:2914
+         #3  0x00007f6ed89fa9ff in g_assertion_message_expr (domain=domain@entry=0x7f6ed8d76875 "GES", file=file@entry=0x7f6ed8d7fd58 "../subprojects/gst-editing-services/ges/ges-track.c", line=line@entry=1263, func=func@entry=0x7f6ed8d805b0 <__func__.6> "ges_track_remove_element", expr=expr@entry=0x7f6ed8d801e8 "track->priv->valid_thread == g_thread_self()") at ../glib/gtestutils.c:2940
+         #4  0x00007f6ed8d2658f in ges_track_remove_element (track=track@entry=0x7f6eb4119b20 [GESAudioTrack], object=object@entry=0x106f240 [GESAudioUriSource]) at ../subprojects/gst-editing-services/ges/ges-track.c:1263
+         #5  0x00007f6ed8d10842 in ges_clip_empty_from_track (clip=0x7f6e7803ee80 [GESUriClip], track=track@entry=0x7f6eb4119b20 [GESAudioTrack]) at ../subprojects/gst-editing-services/ges/ges-clip.c:1086
+         #6  0x00007f6ed8d01453 in ges_timeline_remove_track (timeline=timeline@entry=0x7f6e6c01ae50 [GESTimeline], track=0x7f6eb4119b20 [GESAudioTrack]) at ../subprojects/gst-editing-services/ges/ges-timeline.c:2460
+         #7  0x00007f6ed8d0286b in ges_timeline_send_event (element=<optimized out>, event=<optimized out>) at ../subprojects/gst-editing-services/ges/ges-timeline.c:484
+         #8  0x00007f6ed8bf466c in gst_element_send_event (element=0x7f6e6c01ae50 [GESTimeline], event=event@entry=0x7f6eb410f9f0) at ../subprojects/gstreamer/gst/gstelement.c:1934
+         #9  0x00007f6ed8d242cd in ges_track_handle_message (bin=0xd846f0 [GESVideoTrack], message=0x7f6eb411ac90) at ../subprojects/gst-editing-services/ges/ges-track.c:477
+         #10 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #11 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0xfdf440 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #12 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x10261d0 [NleComposition], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #13 0x00007f6ed8bccbee in gst_bin_post_message (element=0x10261d0 [NleComposition], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #14 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x10261d0 [NleComposition], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #15 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #16 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0xfdf2c0 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #17 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x1029110 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #18 0x00007f6ed8bccbee in gst_bin_post_message (element=0x1029110 [GstBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #19 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x1029110 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #20 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #21 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0xfdf500 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #22 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0xd705e0 [NleSource], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #23 0x00007f6ed8bccbee in gst_bin_post_message (element=0xd705e0 [NleSource], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #24 0x00007f6ed8bf4b66 in gst_element_post_message (element=0xd705e0 [NleSource], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #25 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #26 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x1042400 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #27 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x1029450 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #28 0x00007f6ed8bccbee in gst_bin_post_message (element=0x1029450 [GstBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #29 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x1029450 [GstBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #30 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #31 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x1042640 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #32 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6eb42fc7a0 [GstURIDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #33 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6eb42fc7a0 [GstURIDecodeBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #34 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x7f6eb42fc7a0 [GstURIDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #35 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #36 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x7f6eb80a7130 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #37 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #38 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6e6c02aa60 [GstDecodeBin], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #39 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x7f6e6c02aa60 [GstDecodeBin], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #40 0x00007f6ec8f1e00d in gst_decode_bin_handle_message (bin=0x7f6e6c02aa60 [GstDecodeBin], msg=<optimized out>) at ../subprojects/gst-plugins-base/gst/playback/gstdecodebin2.c:5667
+         #41 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #42 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x7f6eb4139110 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #43 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6e54038c70 [GESDemux], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #44 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6e54038c70 [GESDemux], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #45 0x00007f6ed8bf4b66 in gst_element_post_message (element=0x7f6e54038c70 [GESDemux], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #46 0x00007f6ed8bc9128 in bin_bus_handler (bus=<optimized out>, message=<optimized out>, bin=<optimized out>) at ../subprojects/gstreamer/gst/gstbin.c:3286
+         #47 0x00007f6ed8bdbae2 in gst_bus_post (bus=bus@entry=0x7f6eb4139350 [GstBus], message=message@entry=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstbus.c:359
+         #48 0x00007f6ed8bf1396 in gst_element_post_message_default (element=element@entry=0x7f6e6c01ae50 [GESTimeline], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2067
+         #49 0x00007f6ed8bccbee in gst_bin_post_message (element=0x7f6e6c01ae50 [GESTimeline], msg=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstmessage.h:376
+         #50 0x00007f6ed8bf4b66 in gst_element_post_message (element=element@entry=0x7f6e6c01ae50 [GESTimeline], message=0x7f6eb411ac90) at ../subprojects/gstreamer/gst/gstelement.c:2110
+         #51 0x00007f6ed8cfa221 in ges_timeline_change_state (element=0x7f6e6c01ae50 [GESTimeline], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gst-editing-services/ges/ges-timeline.c:450
+         #52 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6e6c01ae50 [GESTimeline], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #53 0x00007f6ed8bf6868 in gst_element_continue_state (element=element@entry=0x7f6e6c01ae50 [GESTimeline], ret=ret@entry=GST_STATE_CHANGE_SUCCESS) at ../subprojects/gstreamer/gst/gstelement.c:2741
+         #54 0x00007f6ed8bf5d67 in gst_element_change_state (element=element@entry=0x7f6e6c01ae50 [GESTimeline], transition=transition@entry=GST_STATE_CHANGE_NULL_TO_READY) at ../subprojects/gstreamer/gst/gstelement.c:3072
+         #55 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6e6c01ae50 [GESTimeline], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #56 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_PAUSED, start_time=0, base_time=0, element=0x7f6e6c01ae50 [GESTimeline], bin=0x7f6e54038c70 [GESDemux]) at ../subprojects/gstreamer/gst/gstbin.c:2615
+         #57 gst_bin_change_state_func (element=0x7f6e54038c70 [GESDemux], transition=GST_STATE_CHANGE_PAUSED_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957
+         #58 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6e54038c70 [GESDemux], transition=transition@entry=GST_STATE_CHANGE_PAUSED_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #59 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6e54038c70 [GESDemux], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #60 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0x7f6e54038c70 [GESDemux], bin=0x7f6e6c02aa60 [GstDecodeBin]) at ../subprojects/gstreamer/gst/gstbin.c:2615
+         #61 gst_bin_change_state_func (element=0x7f6e6c02aa60 [GstDecodeBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957
+         #62 0x00007f6ec8f1e84f in gst_decode_bin_change_state (element=0x7f6e6c02aa60 [GstDecodeBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gst-plugins-base/gst/playback/gstdecodebin2.c:5482
+         #63 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #64 0x00007f6ed8bf6868 in gst_element_continue_state (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], ret=ret@entry=GST_STATE_CHANGE_SUCCESS) at ../subprojects/gstreamer/gst/gstelement.c:2741
+         #65 0x00007f6ed8bf5d67 in gst_element_change_state (element=element@entry=0x7f6e6c02aa60 [GstDecodeBin], transition=transition@entry=GST_STATE_CHANGE_NULL_TO_READY) at ../subprojects/gstreamer/gst/gstelement.c:3072
+         #66 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6e6c02aa60 [GstDecodeBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #67 0x00007f6ed8bf5ae8 in gst_element_sync_state_with_parent (element=0x7f6e6c02aa60 [GstDecodeBin]) at ../subprojects/gstreamer/gst/gstelement.c:2413
+         #68 0x00007f6ed89f17a0 in g_slist_foreach (list=<optimized out>, func=0x7f6ed8bf5a50 <gst_element_sync_state_with_parent>, user_data=user_data@entry=0x0) at ../glib/gslist.c:880
+         #69 0x00007f6ec8f37d45 in gst_uri_decode_bin_change_state (element=<optimized out>, transition=<optimized out>) at ../subprojects/gst-plugins-base/gst/playback/gsturidecodebin.c:2869
+         #70 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x7f6eb42fc7a0 [GstURIDecodeBin], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #71 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x7f6eb42fc7a0 [GstURIDecodeBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #72 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0x7f6eb42fc7a0 [GstURIDecodeBin], bin=0x1029450 [GstBin]) at ../subprojects/gstreamer/gst/gstbin.c:2615
+         #73 gst_bin_change_state_func (element=0x1029450 [GstBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957
+         #74 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x1029450 [GstBin], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #75 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x1029450 [GstBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #76 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0x1029450 [GstBin], bin=0xd705e0 [NleSource]) at ../subprojects/gstreamer/gst/gstbin.c:2615
+         #77 gst_bin_change_state_func (element=0xd705e0 [NleSource], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957
+         #78 0x00007f6ec805533f in nle_object_change_state (element=0xd705e0 [NleSource], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gst-editing-services/plugins/nle/nleobject.c:748
+         #79 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0xd705e0 [NleSource], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #80 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0xd705e0 [NleSource], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #81 0x00007f6ed8bd2129 in gst_bin_element_set_state (next=GST_STATE_PAUSED, current=GST_STATE_READY, start_time=0, base_time=0, element=0xd705e0 [NleSource], bin=0x1029110 [GstBin]) at ../subprojects/gstreamer/gst/gstbin.c:2615
+         #82 gst_bin_change_state_func (element=0x1029110 [GstBin], transition=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstbin.c:2957
+         #83 0x00007f6ed8bf5d1e in gst_element_change_state (element=element@entry=0x1029110 [GstBin], transition=transition@entry=GST_STATE_CHANGE_READY_TO_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:3033
+         #84 0x00007f6ed8bf6368 in gst_element_set_state_func (element=0x1029110 [GstBin], state=GST_STATE_PAUSED) at ../subprojects/gstreamer/gst/gstelement.c:2987
+         #85 0x00007f6ed8bf5ae8 in gst_element_sync_state_with_parent (element=0x1029110 [GstBin]) at ../subprojects/gstreamer/gst/gstelement.c:2413
+         #86 0x00007f6ec8060356 in _activate_new_stack (toplevel_seek=<optimized out>, comp=0x10261d0 [NleComposition]) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:3117
+         #87 update_pipeline (comp=comp@entry=0x10261d0 [NleComposition], currenttime=<optimized out>, seqnum=<optimized out>, update_reason=update_reason@entry=COMP_UPDATE_STACK_INITIALIZE) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:3396
+         #88 0x00007f6ec80614f6 in _initialize_stack_func (comp=0x10261d0 [NleComposition], ucompo=0x108c800) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:732
+         #89 0x00007f6ed893788a in g_closure_invoke (closure=<optimized out>, return_value=<optimized out>, n_param_values=<optimized out>, param_values=<optimized out>, invocation_hint=<optimized out>) at ../gobject/gclosure.c:810
+         #90 0x00007f6ec805aaf6 in _execute_actions (comp=0x10261d0 [NleComposition]) at ../subprojects/gst-editing-services/plugins/nle/nlecomposition.c:412
+         #91 0x00007f6ed8c4c1cf in gst_task_func (task=0x7f6e6c01c290 [GstTask]) at ../subprojects/gstreamer/gst/gsttask.c:328
+         #92 0x00007f6ed89fc0f4 in g_thread_pool_thread_proxy (data=<optimized out>) at ../glib/gthreadpool.c:354
+         #93 0x00007f6ed89fb7f2 in g_thread_proxy (data=0x7f6eb0017800) at ../glib/gthread.c:807
+         #94 0x00007f6ed7e14432 in start_thread () at /lib64/libpthread.so.0
+         #95 0x00007f6ed86779d3 in clone () at /lib64/libc.so.6
+         ```
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/167>
+
+2020-04-14 10:22:09 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * tests/check/nle/tempochange.c:
+         check: give nle_tempochange test more time
+         These test can take longer than most under valgrind, so give them a
+         little more time until they timeout.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/160>
+
+2020-04-13 11:40:55 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+       * tests/check/ges/basic.c:
+         timeline: fix adding track when layers contains clips
+         Made sure that adding a new track only uses select-tracks-for-object for
+         core children to determine whether a track elements should be added to the
+         new track or not, and *not* any other track. In particular, there should
+         be *no* change in the existing tracks of the timeline when adding another
+         track. Moreover, a new track should not invoke the creation of track
+         elements for other tracks.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/160>
+
+2020-04-08 17:11:14 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-effect.c:
+       * ges/ges-track-element.c:
+       * plugins/nle/nleghostpad.c:
+       * plugins/nle/nleobject.c:
+       * plugins/nle/nleobject.h:
+       * plugins/nle/nleoperation.c:
+       * tests/check/ges/tempochange.c:
+       * tests/check/nle/tempochange.c:
+         nleobject: stop using media-duration-factor
+         The property had been deprecated and is unused.
+         This property is not needed. Any internal time effect that an nleoperation
+         wraps is itself responsible for converting seek/segment timestamps.
+         Previously, the ghostpads were performing a rate conversion after the
+         rate element had already done so, essentially doubling their effect on
+         seeks and segment times. This was always unnecessary, but went unnoticed
+         by the tempochange test because it was using an identity element rather
+         than an actual rate-changing element.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/160>
+
+2020-04-08 17:08:41 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+       * plugins/nle/nleoperation.c:
+       * plugins/nle/nleoperation.h:
+         nleoperation: stop setting next_base_time
+         This property was unused.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/160>
+
+2020-04-21 16:22:31 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+       * tests/check/meson.build:
+       * tests/check/scenarios/edit_while_seeked_with_stop.validatetest:
+       * tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest:
+       * tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop.validatetest:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected:
+       * tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected:
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.c:
+         nlecomposition: Fix seeking with stop
+         And add some tests
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/166>
+
+2020-04-24 17:15:16 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.c:
+         launch: Add support for testfiles
+         Making it simpler to define a test in a single files, including the
+         configuration etc..
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/166>
+
+2020-04-24 16:46:50 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track.c:
+         track: Do not commit ourselves automatically when changing state from wrong thread
+         The user is responsible to commit the timeline from the right thread
+         in that case and in the case of gesdemux, the loaded timeline is filling
+         gaps automatically when the project is set loaded.
+         Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/merge_requests/166>
+
+2020-04-18 16:22:25 +0200  Andoni Morales Alastruey <ylatuya@gmail.com>
+
+       * meson.build:
+         macos: fix python's configure checks
+
+2020-04-17 12:35:26 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-source.c:
+       * ges/ges-video-uri-source.c:
+         ges: Fix interlaced stream playback
+         Negotiation was failling as `videoflip` was not allowing not
+         progressive interlacing.
+         Also avoid adding a deinterlace element when it is useless.
+
+2020-04-16 20:27:30 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/gstframepositioner.c:
+       * ges/gstframepositioner.h:
+       * meson.build:
+       * tests/check/scenarios/check_video_track_restriction_scale.scenario:
+         framepositioner: Fix some source repositionning rounding issues
+         Avoid loosing (too much) precision when rescaling back and forth by
+         storing values in gdoubles.
+         Handle the fact that position values can be negative
+         Also fix debug category static variable
+         as it clashes with the instance variable name in a few methods.
+
+2020-04-16 12:53:00 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip-asset.c:
+       * ges/ges-timeline.c:
+         timeline: Fix wrong usage of scale_int
+         We are multiplying the framerate by GST_SECOND and thus have no
+         guarantee that it won't overflow.
+
+2020-04-11 11:40:06 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-image-source.c:
+       * ges/ges-video-uri-source.c:
+         ges: Place imagefreeze at right place
+         Negotiation fails when having the imagefreeze after videorate and
+         frame positioning won't happen after seeks if we do not put it
+         before the postioner
+
+2020-04-09 11:24:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+       * ges/ges-timeline.c:
+         asset: Avoid dereferencing NULL pointer
+         CID 1461286
+
+2020-04-09 11:20:34 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesbasebin.c:
+         basebin: Do not set stream_group if upstream didn't provide it
+         CID: 1461278
+
+2020-04-09 11:17:59 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/gstframepositioner.c:
+         framepositionner: Fix wrong old size check condition
+         CID: 1461277
+
+2020-04-09 11:16:34 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Avoid dereferencing NULL pointer
+         CID: 1461266
+
+2020-04-09 11:10:43 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: Ensure setting framerate in timeline_get_framerate
+         CID: 1461250, 1461288
+
+2020-04-09 11:07:04 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/nle/complex.c:
+         tests: Check that linking pads works
+         CID: 1456061
+
+2020-04-09 11:02:26 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         structured-interface: Properly check that setting keyframe works
+         Fixes CID: 1455490
+
+2020-04-09 10:59:40 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Check result of g_stat
+         CID: 1455489, 1455521
+
+2020-04-09 10:54:26 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/ges/tempochange.c:
+         test: tempochange: Plug leak
+         CID: 1455448
+
+2020-04-09 10:42:03 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         ges: Cast to signed int to compare agasint 0
+         The check made sense but we were not casting to be able to check
+         signess of subtraction result.
+         CID: 1444923
+
+2020-04-09 10:37:20 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: Do not compare unsigned with 0
+         Layer priorities are always positive the check was making no
+         sense in any case.
+         Fixes CID: 1444922, 1461284
+
+2020-04-09 10:31:36 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-group.c:
+       * ges/ges-pitivi-formatter.c:
+       * plugins/ges/gesdemux.c:
+       * tests/check/ges/clip.c:
+         ges: Always check return value of `ges_container_add`
+         Making coverity happy
+         CIDs: 1461460, 1461461, 1461462, 1461463, 1461464, 1461465, 1461466, 1461468,
+
+2020-04-09 10:00:43 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-test-source.c:
+         ges: Fix sending EOS on testclip when using timeoverlay
+         Basically when using timeoverlay we where waiting for input-selector
+         to receive EOS on its active on the output-selector streaming thread
+         but... EOS was being sent from that same thread waiting for input-selector
+         to unblock to send EOS on its other pad.
+         In our specific use case we want EOS to be sent only on the active pad.
+         Fixes: https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/103
+
+2020-04-09 09:29:17 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/deprecated.md:
+       * docs/libs/GESAudioTestSource-children-props.md:
+       * docs/libs/GESAudioUriSource-children-props.md:
+       * docs/libs/GESTitleSource-children-props.md:
+       * docs/libs/GESTransitionClip-children-props.md:
+       * docs/libs/GESVideoTestSource-children-props.md:
+       * docs/libs/GESVideoUriSource-children-props.md:
+       * docs/libs/document-children-props.py:
+       * docs/sitemap.txt:
+       * ges/ges-audio-test-source.h:
+       * ges/ges-audio-uri-source.h:
+       * ges/ges-effect-asset.c:
+       * ges/ges-source-clip-asset.c:
+       * ges/ges-title-source.c:
+       * ges/ges-title-source.h:
+       * ges/ges-transition-clip.h:
+       * ges/ges-types.h:
+       * ges/ges-uri-asset.c:
+       * ges/ges-uri-asset.h:
+       * ges/ges-video-source.c:
+       * ges/ges-video-test-source.c:
+       * ges/ges-video-test-source.h:
+       * ges/ges-video-uri-source.h:
+         ges: Update documentation
+         And start generating TrackElement children property with a stupid
+         simple script
+
+2020-04-09 09:24:12 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-test-source.c:
+         ges: Add the foreground color child property
+
+2020-04-07 10:53:15 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+       * tests/check/python/test_assets.py:
+         ges: Fix reloading UriClipAsset synchronously
+         And add tests for that
+
+2020-04-07 10:47:07 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-internal.h:
+       * ges/ges-track-element.c:
+       * ges/ges-transition-clip.c:
+       * ges/ges-uri-asset.c:
+       * tests/check/python/test_clip.py:
+         ges: Rework the way we ensure core elements are not wrongly moved between clips
+         Instead of focusing on the instances of the clips and their children,
+         we relax the check to allow moving track element clip between clips
+         that share a common asset. This makes it as correct conceptually but
+         more flexible, and the code becomes simpler.
+
+2020-04-02 11:58:18 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * ges/ges-group.c:
+       * ges/ges-timeline.c:
+         group: tidied timeline membership in copy-paste
+         Previously, the GESContainer ->paste method and GESGroup ->paste methods
+         were unnecessarily setting the timeline of groups, even though this is
+         handled by the GESGroup ->child_added method. This could result in the
+         group being added multiple times.
+
+2020-04-01 21:34:48 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline-tree.c:
+       * tests/check/python/test_timeline.py:
+         timeline-tree: fix overlap check
+         Previously, the code was not able to detect that an element overlaps on
+         its end, nor could it detect that an element overlaps two elements that
+         already overlap.
+
+2020-04-06 12:44:30 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * tests/check/ges/clip.c:
+         clip: tidy grouping
+         Make the grouping of clips cleaner by checking that the clips share the
+         same asset.
+
+2020-04-06 12:42:03 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-layer.c:
+         clip: secure adding clip to layer
+         Add more checks when adding a clip to a layer, or moving a clip to a new
+         layer. Also, mark the "layer" property as explicit-notify.
+
+2020-04-06 12:28:13 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-uri-clip.c:
+         uri-clip: match children by track
+         When the asset of a uri clip is reset, its core children are removed and
+         replaced by the new core children. When replacing, the `set_asset`
+         method attempts to copy children properties from the previous children
+         to the new children. However, the children were matched by track-type
+         only. This would not function as intended when a URI contains multiple
+         audio or video streams. Instead, we now match children by the tracks
+         themselves. This should work better, provided the user's
+         select-tracks-for-object is well behaved.
+         Also, fix a memory problem in `set_mute` for when a child is not in a
+         track.
+
+2020-04-06 12:26:11 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline-element.c:
+       * ges/ges-track-element.c:
+         timeline-element: only copy read-write properties
+         Only copy the properties that can be both read and written, and are not
+         construct only. Similarly for child properties when a track-element is
+         deep copied.
+
+2020-04-06 12:17:43 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline.c:
+         timeline: return sunk element on pasting
+         Technically, an element can still be floating on the return from
+         `->paste` (e.g. a clip not in a layer). Since the return of the `_paste`
+         methods are (return full) a non-floating object is probably expected in
+         all cases.
+
+2020-04-06 12:16:11 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+       * tests/check/ges/basic.c:
+         auto-transition: select track directly
+         By-pass the select-tracks-for-object signal for auto-transitions since
+         their track element must land in the same track as the elements it is
+         the auto-transition for.
+
+2020-04-06 12:09:54 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * ges/ges-internal.h:
+       * ges/ges-layer.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track.c:
+       * tests/check/ges/basic.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/test-utils.h:
+         timeline: re-handle clip children track selection
+         The way a clip's track elements are added to tracks was re-handled. This
+         doesn't affect the normal usage of a simple audio-video timeline, where
+         the tracks are added before any clips, but usage for multi-track
+         timelines has improved. The main changes are:
+         + We can now handle a track being selected for more than one track,
+         including a full copy of their children properties and bindings.
+         (Previously broken.)
+         + When a clip is split, we copy the new elements directly into the same
+         track, avoiding select-tracks-for-object.
+         + When a clip is grouped or ungrouped, we avoid moving the elements to
+         or from tracks.
+         + Added API to allow users to copy the core elements of a clip directly
+         into a track, complementing select-tracks-for-object.
+         + Enforced the rule that a clip can only contain one core child in a
+         track, and all the non-core children must be added to tracks that
+         already contains a core child. This extends the previous condition
+         that two sources from the same clip should not be added to the same
+         track.
+         + Made ges_track_add_element check that the newly added track element
+         does not break the configuration rules of the timeline.
+         + When adding a track to a timeline, we only use
+         select-tracks-for-object to check whether track elements should be
+         added to the new track, not existing ones.
+         + When removing a track from a timeline, we empty it of all the track
+         elements that are controlled by a clip. Thus, we ensure that a clip
+         only contains elements that are in the tracks of the same timeline, or
+         no track. Similarly, when removing a clip from a timeline.
+         + We can now avoid unsupported timeline configurations when a layer is
+         added to a timeline, and already contains clips.
+         + We can now avoid unsupported timeline configurations when a track is
+         added to a timeline, and the timeline already contains clips.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/84
+
+2020-03-26 09:21:42 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: stop connecting to track-element-added
+         This was used to connect to the track element's notify::start signal in
+         order to update the duration of the timeline (it is not clear why the
+         notify::duration signal was not also connected to for the same reason).
+         However, this is already covered by the timeline_tree_move method, which
+         is always called to update the start of a track element, even if it is not
+         part of a clip (and similarly for timeline_tree_trim, which is called
+         when the duration is set).
+
+2020-03-25 19:35:11 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-uri-clip.c:
+       * tests/check/ges/clip.c:
+         clip: allow arbitrary max-duration when no core children
+         Before the max-duration could be set arbitrarily when the clip was empty,
+         to indicate what the max-duration would be once the core children were
+         created. Now, we can also do this whilst the clip only contains non-core
+         children.
+
+2020-03-25 18:49:16 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * tests/check/python/test_clip.py:
+         track-element: change owner to creator
+         Rename the private "owners" to "creators" to avoid confusing this with
+         the owner of the track element's memory.
+         Also made the ungroup method for GESClip symmetric by making all the
+         children of the resulting clips share their creators, which allows them
+         to be added to any of the other ungrouped clips. Once the clips are
+         grouped back together, the tracks loose these extra creators.
+
+2020-04-06 12:21:54 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * tests/check/ges/clip.c:
+         container: change ownership when adding
+         Make sure we sink the child on adding, and keep it alive until the end
+         in case the method fails.
+         Also, since the child mappings hold a ref to the child, they should give
+         them up in their free method. This way, the ref will be given up on
+         disposing, even if ges_container_remove fails.
+         Also, reverse setting of the start of the container if adding fails.
+
+2020-04-06 23:06:29 +0530  Nirbheek Chauhan <nirbheek@centricular.com>
+
+       * ges/ges-uri-clip.c:
+       * ges/ges-video-source.c:
+         ges: Fix build with GCC 10
+         gcc-10 defaults to -fno-common, which exposes a symbol conflict, so
+         use `static` correctly. Also we don't use `parent_extractable_iface`
+         in `ges-uri-clip.c`.
+         See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85678
+
+2020-03-31 11:25:49 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-container.c:
+       * tests/check/python/test_timeline.py:
+         ges: Fix trimming clip inside deeply nested groups
+         This broke in 6b7c658b6a551a5b9170987ba44592d1d819e1ae
+
+2020-03-24 22:47:01 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-clip.c:
+         uri-clip: Remove dead code
+         GES_TESTING_ASSETS_DIRECTORY is prehistoric and since then
+         new mechanism for asset relocation have been added, it makes
+         no sense to keep that unused code path
+
+2020-03-24 22:44:07 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-clip.c:
+         uri-clip: Remove ->create_track_element implementation
+         It is dead code
+
+2020-03-24 22:35:35 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-image-source.c:
+       * ges/ges-multi-file-source.c:
+       * ges/ges-uri-asset.c:
+       * ges/ges-uri-asset.h:
+       * ges/ges-video-source.c:
+       * ges/ges-video-source.h:
+       * ges/ges-video-uri-source.c:
+       * tests/check/ges/uriclip.c:
+       * tests/check/python/test_clip.py:
+         ges: Deprecate GESImageSource and GESMultiFileSource
+         Refactoring GESVideoSource so that #GESUriVideoSource can handle
+         still image in a simple way
+         MultiFileSource has been replaced with the new `imagesequencesrc`
+         element, this was totally broken anyway as `multifilesrc` can not seek
+         properly.
+
+2020-03-24 22:30:38 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-uri-source.c:
+       * ges/ges-track-element.c:
+       * ges/ges-video-source.c:
+       * ges/ges-video-uri-source.c:
+         track-element: Create nleobject on GESExtractable::set_asset
+         This means that we have all the information about the asset
+         when constructing the underlying GstElements.
+         This also allows to cleanup some code all around
+
+2020-03-24 22:25:47 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.c:
+         timeline:element: Refactor the way we 'copy'
+         Simplifying the implementation and making sure assets are set asap
+
+2020-03-24 22:23:16 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-source.c:
+       * ges/ges-audio-test-source.c:
+       * ges/ges-audio-transition.c:
+       * ges/ges-audio-transition.h:
+       * ges/ges-effect-clip.c:
+       * ges/ges-group.c:
+       * ges/ges-text-overlay.c:
+       * ges/ges-text-overlay.h:
+       * ges/ges-title-source.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track-element.h:
+       * ges/ges-video-source.c:
+       * ges/ges-video-test-source.c:
+       * ges/ges-video-transition.c:
+       * ges/ges-video-transition.h:
+         ges: Use assets to instantiate track elements/group
+         And deprecate all GESTrackElement constructors, but the GESEffect one.
+         Those should **never** be created by users and should become internal
+         in the future.
+         Stop having docstring for the constructors that were internal.
+
+2020-03-18 16:24:08 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/assets/audio_only.ogg:
+       * tests/check/assets/audio_video.ogg:
+       * tests/check/assets/image.png:
+       * tests/check/assets/test-auto-transition.xges:
+       * tests/check/assets/test-project.xges:
+       * tests/check/assets/test-properties.xges:
+       * tests/check/ges/test-utils.c:
+       * tests/check/meson.build:
+       * tests/check/python/test_clip.py:
+         tests: Cleanup test files handling
+
+2020-03-13 15:03:17 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-layer.h:
+       * ges/ges-xml-formatter.c:
+         formatter: Serialize source properties
+         This way we ensure that the TrackElement 'active' property is
+         properly serialized
+
+2020-03-06 18:56:52 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-layer.c:
+       * ges/ges-layer.h:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline-tree.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track.c:
+       * ges/ges-validate.c:
+       * ges/ges-xml-formatter.c:
+       * tests/check/meson.build:
+       * tests/check/python/common.py:
+       * tests/check/python/test_timeline.py:
+       * tests/check/scenarios/check_layer_activness_gaps.scenario:
+         ges: Add a way to set layer activeness by track
+         a.k.a muting layers.
+         Adding unit tests and making sure serialization works properly
+
+2020-03-23 21:21:10 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.c:
+         element: Add API safe guard against invalid position in edit()
+
+2020-03-23 21:11:45 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Refactor actions implementation
+         Making them simpler to read and avoiding leaks
+
+2020-03-23 15:14:13 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         structured-interface: Fix adding clip to layer error reporting
+
+2020-03-17 11:53:47 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip-asset.c:
+       * ges/ges-source-clip-asset.c:
+       * ges/ges-source-clip-asset.h:
+       * ges/ges-source-clip.c:
+       * ges/ges-test-clip.c:
+       * ges/ges-uri-asset.c:
+       * ges/ges-uri-asset.h:
+       * ges/meson.build:
+         ges: Add a SourceClipAsset class
+         Cleaning up the way we use the default framerate for natural
+         frame rate.
+
+2020-03-10 16:10:12 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.c:
+         launch: Add a way to disable validate at runtime
+         Also avoid to add useless bin in our sinks
+
+2020-03-09 15:38:58 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+       * ges/ges-timeline.c:
+       * ges/ges-uri-asset.c:
+       * ges/ges-validate.c:
+       * tests/check/meson.build:
+       * tools/ges-launch.c:
+       * tools/ges-launcher.c:
+       * tools/ges-validate.c:
+         ges: Plug some leaks
+
+2020-02-28 11:56:22 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+       * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario:
+         validate: Add support to seek in frames
+
+2020-02-28 11:47:25 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-extractable.c:
+       * ges/ges-extractable.h:
+       * ges/ges-internal.h:
+       * ges/ges-structure-parser.c:
+       * ges/ges-test-clip.c:
+       * ges/ges-video-test-source.c:
+       * tests/check/python/test_timeline.py:
+         ges: support test clips assets natural size/framerate
+         This way we can test this kind of behaviour without requiring
+         real sources.
+         Also add simple tests.
+
+2020-02-21 09:17:11 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip-asset.c:
+       * ges/ges-clip-asset.h:
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-gerror.h:
+       * ges/ges-internal.h:
+       * ges/ges-structured-interface.c:
+       * ges/ges-timeline.c:
+       * ges/ges-timeline.h:
+       * ges/ges-types.h:
+       * ges/ges-utils.c:
+       * ges/ges-validate.c:
+       * tests/check/meson.build:
+       * tests/check/scenarios/check_edit_in_frames.scenario:
+       * tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario:
+         ges: Add APIs to have a sens of frame numbers
+         APIs:
+         - ges_timeline_get_frame_time
+         - ges_timeline_get_frame_at
+         - ges_clip_asset_get_frame_time
+         - ges_clip_get_timeline_time_from_source_frame
+         Extracting ges_util_structure_get_clocktime to internal utilities adding
+         support for specifying timing values in frames with the special
+         f<frame-number> synthax.
+
+2019-10-29 16:52:52 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * tools/utils.c:
+         utils: fix argument sanitization
+         _sanitize_argument is supposed to wrap arguments in '"' quote marks such
+         that they can be parsed and copied into a GstStructure string. This
+         purpose is now supported more directly, which fixes some bugs, e.g.:
+         arguments                before                  fix
+         +title my=title          +title my="title"       +title "my=title"
+         +title abc n=my=name     +title abc n="my="name" +title abc n="my=name"
+         +title my"title          +title "my"title"       +title "my\"title"
+         +title my\title          +title "my\title"       +title "my\\title"
+
+2020-02-28 11:52:38 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/utils.c:
+         launch: Fix memory management issue with the rendering format
+
+2020-02-25 17:42:47 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Rename edit-container to edit
+         Keeping the old version for backward compat
+
+2020-02-21 17:17:10 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-source.c:
+       * ges/ges-video-test-source.c:
+         ges: Add a timeoverlay to video test sources
+         This is often very useful to have a timeoverlay inside test sources.
+         We do not want to use it as an effect as segments are not the sames
+         in GES when it comes to nleoperations.
+
+2020-02-25 18:39:47 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.c:
+         element: Handle using own property as child property
+         Avoiding ref cycles
+
+2020-02-21 17:16:01 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip.c:
+         ges: Ensure GESClips assets are always ClipAssets
+
+2020-02-18 15:21:38 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-uri-source.c:
+       * ges/ges-clip-asset.c:
+       * ges/ges-clip-asset.h:
+       * ges/ges-clip.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element-asset.c:
+       * ges/ges-track-element-asset.h:
+       * ges/ges-track-element.c:
+       * ges/ges-uri-asset.c:
+       * ges/ges-video-uri-source.c:
+         ges: Add API to retrieve the natural framerate of an element
+
+2020-02-28 17:53:55 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track.c:
+         ges: Some memory management fixes setting track mixing
+         Also fix 'mixing' property notifies
+
+2020-02-28 17:50:05 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-enums.c:
+         ges: Cleanup GESEdge and GESEditMode GEnum values
+         By duplicating the registered values, so that bindings have
+         better values to use
+
+2020-03-02 14:35:33 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launch: Make command line provided sinks override scenario defined ones
+
+2020-02-28 11:58:30 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/gstframepositioner.c:
+         framepositioner: Avoid dereferencing NULL pointer
+
+2020-03-04 16:03:30 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Initialize GValue before calling g_object_get_value
+         This is required with GLib < 2.60
+
+2020-03-17 18:13:51 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.h:
+       * ges/ges-audio-source.h:
+       * ges/ges-audio-test-source.h:
+       * ges/ges-audio-track.h:
+       * ges/ges-audio-transition.h:
+       * ges/ges-audio-uri-source.h:
+       * ges/ges-auto-transition.h:
+       * ges/ges-base-effect-clip.h:
+       * ges/ges-base-effect.h:
+       * ges/ges-base-transition-clip.h:
+       * ges/ges-base-xml-formatter.h:
+       * ges/ges-clip-asset.h:
+       * ges/ges-clip.h:
+       * ges/ges-command-line-formatter.h:
+       * ges/ges-container.h:
+       * ges/ges-effect-asset.h:
+       * ges/ges-effect-clip.h:
+       * ges/ges-effect.h:
+       * ges/ges-enums.h:
+       * ges/ges-extractable.h:
+       * ges/ges-formatter.h:
+       * ges/ges-gerror.h:
+       * ges/ges-group.h:
+       * ges/ges-image-source.h:
+       * ges/ges-internal.h:
+       * ges/ges-layer.h:
+       * ges/ges-marker-list.h:
+       * ges/ges-meta-container.h:
+       * ges/ges-multi-file-source.h:
+       * ges/ges-operation-clip.h:
+       * ges/ges-operation.h:
+       * ges/ges-overlay-clip.h:
+       * ges/ges-pipeline.h:
+       * ges/ges-pitivi-formatter.h:
+       * ges/ges-prelude.h:
+       * ges/ges-project.h:
+       * ges/ges-screenshot.h:
+       * ges/ges-smart-adder.h:
+       * ges/ges-smart-video-mixer.h:
+       * ges/ges-source-clip.h:
+       * ges/ges-source.h:
+       * ges/ges-structure-parser.h:
+       * ges/ges-structured-interface.h:
+       * ges/ges-test-clip.h:
+       * ges/ges-text-overlay-clip.h:
+       * ges/ges-text-overlay.h:
+       * ges/ges-timeline-element.h:
+       * ges/ges-timeline-tree.h:
+       * ges/ges-timeline.h:
+       * ges/ges-title-clip.h:
+       * ges/ges-title-source.h:
+       * ges/ges-track-element-asset.h:
+       * ges/ges-track-element.h:
+       * ges/ges-track.h:
+       * ges/ges-transition-clip.h:
+       * ges/ges-transition.h:
+       * ges/ges-types.h:
+       * ges/ges-uri-asset.h:
+       * ges/ges-uri-clip.h:
+       * ges/ges-utils.h:
+       * ges/ges-version.h.in:
+       * ges/ges-video-source.h:
+       * ges/ges-video-test-source.h:
+       * ges/ges-video-track.h:
+       * ges/ges-video-transition.h:
+       * ges/ges-video-uri-source.h:
+       * ges/ges-xml-formatter.h:
+       * ges/ges.h:
+       * plugins/ges/gesbasebin.h:
+       * tests/check/ges/test-utils.h:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.h:
+         ges: Use #pragma once everywhere
+
+2020-03-17 15:51:39 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.h:
+       * ges/ges-audio-source.h:
+       * ges/ges-audio-test-source.h:
+       * ges/ges-audio-track.h:
+       * ges/ges-audio-transition.h:
+       * ges/ges-audio-uri-source.h:
+       * ges/ges-auto-transition.h:
+       * ges/ges-base-effect-clip.h:
+       * ges/ges-base-effect.h:
+       * ges/ges-base-transition-clip.h:
+       * ges/ges-base-xml-formatter.h:
+       * ges/ges-clip-asset.h:
+       * ges/ges-clip.h:
+       * ges/ges-command-line-formatter.h:
+       * ges/ges-container.h:
+       * ges/ges-effect-asset.h:
+       * ges/ges-effect-clip.h:
+       * ges/ges-effect.h:
+       * ges/ges-extractable.h:
+       * ges/ges-formatter.h:
+       * ges/ges-group.h:
+       * ges/ges-image-source.h:
+       * ges/ges-internal.h:
+       * ges/ges-layer.h:
+       * ges/ges-meta-container.h:
+       * ges/ges-multi-file-source.h:
+       * ges/ges-operation-clip.h:
+       * ges/ges-operation.c:
+       * ges/ges-operation.h:
+       * ges/ges-overlay-clip.h:
+       * ges/ges-pipeline.h:
+       * ges/ges-pitivi-formatter.h:
+       * ges/ges-project.h:
+       * ges/ges-smart-video-mixer.c:
+       * ges/ges-source-clip.h:
+       * ges/ges-source.h:
+       * ges/ges-test-clip.h:
+       * ges/ges-text-overlay-clip.h:
+       * ges/ges-text-overlay.h:
+       * ges/ges-timeline-element.h:
+       * ges/ges-timeline.h:
+       * ges/ges-title-clip.h:
+       * ges/ges-title-source.h:
+       * ges/ges-track-element-asset.h:
+       * ges/ges-track-element.h:
+       * ges/ges-track.h:
+       * ges/ges-transition-clip.h:
+       * ges/ges-transition.c:
+       * ges/ges-transition.h:
+       * ges/ges-types.h:
+       * ges/ges-uri-asset.c:
+       * ges/ges-uri-asset.h:
+       * ges/ges-uri-clip.h:
+       * ges/ges-video-source.h:
+       * ges/ges-video-test-source.h:
+       * ges/ges-video-track.h:
+       * ges/ges-video-transition.h:
+       * ges/ges-video-uri-source.h:
+       * ges/ges-xml-formatter.h:
+       * tools/ges-launcher.h:
+         ges: Cleanup the way we declare object types
+         We create our own _DECLARE_ macro because we have instance structures
+
+2020-03-19 09:15:07 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.c:
+         ges: Stop using hash_table_steal_extended
+         This appeard in GLib 2.58
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/99
+
+2020-03-18 13:36:47 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-uri-clip.c:
+       * tests/check/assets/30frames.ogv:
+       * tests/check/ges/clip.c:
+       * tests/check/python/test_clip.py:
+         clip: Allow setting max-duration clips without TrackElements
+         Otherwise this breaks quite a few assumption in user code, several
+         pitivi tests broke because of that.
+
+2020-03-18 12:56:06 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * tests/check/assets/30frames.ogv:
+       * tests/check/python/test_clip.py:
+         ges: Make it so core elements can be re added to their 'owners'
+         The user might want to add/remove/add core children to clips and be able
+         to regroup ungrouped clip. This is needed for undo/redo in Pitivi for
+         example
+
+2020-03-18 11:12:55 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-container.c:
+         container: Let subclass know adding child was interrupted
+         When the `child-added` signal emission was called, the
+         `GESContainer->child_added` vmethod was called (the signal is
+         `G_SIGNAL_RUN_FIRST`) so we need to call `GESContainer->child_removed`
+         ourself so subclasses know they do not control the child anymore.
+
+2020-03-10 16:01:02 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element.c:
+         timeline-element: make start and duration EXPLICIT_NOTIFY
+         The properties will only have their signal emitted when they change in
+         value, even when g_object_set, etc, methods are used.
+         The _set_start method already did this, but start was missing the
+         EXPLICIT_NOTIFY flag. There should be no need to check that the property
+         has changed in ->set_start or ->set_duration
+
+2020-03-10 15:27:20 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-track-element.c:
+       * tests/check/ges/clip.c:
+         timeline-element: make max-duration cap in-point
+         Do not allow the in-point to exceed the max-duration of any timeline
+         element.
+
+2020-03-10 11:53:09 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * tests/check/ges/clip.c:
+         clip: only allow children with the same timeline
+         Refuse the addition of children whose timeline is neither NULL nor the
+         clip's timeline.
+
+2020-03-10 11:38:58 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-transition-clip.c:
+       * ges/ges-uri-clip.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/overlays.c:
+       * tests/check/ges/test-utils.h:
+       * tests/check/ges/titles.c:
+       * tests/check/ges/transition.c:
+         clip: re-handle child in-point and max-duration
+         The in-point of a clip is kept in sync with its core children, unless they
+         have no has-internal-source.
+         The max-duration is defined as the minimum max-duration amongst the
+         clip's core children. If it is set to a new value, this sets the
+         max-duration of its core children to the same value if they have
+         has-internal-source set as TRUE.
+         Non-core children (such as effects on a source clip) do not influence
+         these values.
+         As part of this, we no longer track in-point in GESContainer. Unlike start
+         and duration, the in-point of a timeline element does not refer to its
+         extent in the timeline. As such, it has little meaning for most
+         collections of timeline-elements, in particular GESGroups. As such, there
+         is no generic way to relate the in-point of a container to its children.
+
+2020-03-10 11:35:23 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-group.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+         timeline-element: make in-point and max-duration EXPLICIT_NOTIFY
+         As such, they only emit a signal if their value changes, either through
+         their _set_inpoint or _set_max_duration methods, or through
+         g_object_set, etc.
+         Also, we now require the ->set_max_duration method to be implemented.
+         This was added to GESGroup, which will only allow the max-duration to be
+         set to GST_CLOCK_TIME_NONE.
+
+2020-03-10 11:29:40 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-image-source.c:
+       * ges/ges-source.c:
+       * ges/ges-title-clip.c:
+       * ges/ges-title-source.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track-element.h:
+         track-element: add has-internal-source property
+         Unless this property is set to TRUE, the in-point must be 0 and the
+         max-duration must be GST_CLOCK_TIME_NONE.
+         Also added EXPLICIT_NOTIFY flags to the active and track-type
+         properties such that their notifies are emitted only if the property
+         changes, even when the g_object_set, etc, methods are used.
+         Also added a missing notify signal to the set_active method.
+
+2020-03-03 18:00:51 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * tests/check/ges/clip.c:
+         clip: copy and paste control bindings
+         Previously the control bindings were not properly copied into the pasted
+         clip. Also changed the order so that elements are added to the clip
+         before the clip is added to the timeline.
+
+2020-03-03 14:31:10 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/group.c:
+       * tests/check/ges/test-utils.c:
+       * tests/check/ges/test-utils.h:
+         timeline-element: add signals for child properties
+         Add the child-property-added and child-property-removed signals to
+         GESTimelineElement.
+         GESContainer is able to use this to keep their child properties in sync
+         with their children: if they are added or removed from the child, they
+         are also added or removed from the container.
+
+2020-03-02 12:23:07 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-group.c:
+       * tests/check/ges/clip.c:
+         container: freeze notifies during add and remove
+         Hold the notify signals for the container and the children until after
+         the child has been fully added or removed.
+         After the previous commit, this was used to ensure that the
+         notify::priority signal was sent for children of a clip *after* the
+         child-removed signal. This stopped being the case when the code in
+         ->child_removed was moved to ->remove_child (the latter is called before
+         the child-removed signal is emitted, whilst the former is called
+         afterwards). Rather than undo this move of code, which was necessary to
+         ensure that ->add_child was always reversed, the notify::priority signal
+         is now simply delayed until after removing the child has completed. This
+         was done for all notify signals, as well as in the add method, to ensure
+         consistency.
+         This allows the test_clips.py test_signal_order_when_removing_effect to
+         pass.
+         Also make subclasses take a copy of the list of the children before
+         setting the start and duration, since this can potentially re-order the
+         children (if they have the SET_SIMPLE flag set).
+
+2020-03-02 13:35:20 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: make remove_child a reverse of add_child
+         Previously, we relied on ->child_removed to reverse the priority changes
+         that occured in ->add_child. However, ->child_removed is not always
+         called (the signal child-removed is not always emitted) when a
+         ->add_child needs to be removed. However, ->remove_child is always
+         called to reverse ->add_child, so the code was moved here. Otherwise, we
+         risk that the priorities of the clip will contain gaps, which will cause
+         problems when another child is added to the clip.
+
+2020-03-02 13:25:21 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: tidy handling of child priorities
+         Handle the child priorities in a way that keeps the container children
+         list sorted by priority at all times. Also, no longer rely on the
+         control_mode of the container, since we have less control over its value,
+         compared to private variables.
+         Also fixed the changing of priorities in set_top_effect_index:
+         previously *all* children whose priority was above or below the new
+         priority were shifted, when we should have been only shifting priorities
+         for the children whose priority lied *between* the old and the new
+         priority of the effect. E.g.
+         effect:   A   B   C   D   E   F
+         index:    0   1   2   3   4   5
+         After moving effect E to index 1, previously, we would get
+         effect:   A   B   C   D   E   F
+         index:    0   2   3   4   1   6
+         (this would have also shifted the priority for the core children as
+         well!). Whereas now, we have the correct:
+         effect:   A   B   C   D   E   F
+         index:    0   2   3   4   1   5
+
+2020-03-02 12:56:03 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-base-effect-clip.c:
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+       * ges/ges-container.c:
+       * ges/ges-internal.h:
+       * ges/ges-source-clip.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * tests/check/ges/clip.c:
+       * tests/check/ges/effects.c:
+       * tests/check/ges/test-utils.h:
+         clip: only allow core elements as children
+         Only allow elements that were created by ges_clip_create_track_elements
+         (or copied from such an element) to be added to a clip. This prevents
+         users from adding arbitrary elements to a clip.
+         As an exception, a user can add GESBaseEffects to clips whose class
+         supports it, i.e. to a GESSourceClip and a GESBaseEffectClip.
+         This change also introduces a distinction between the core elements of a
+         clip (created by ges_clip_create_track_elements) and non-core elements
+         (currently, only GESBaseEffects, for some classes). In particular,
+         GESBaseEffectClip will now distinguish between its core elements and
+         effects added by the user. This means that the core elements will always
+         have the lowest priority, and will not be listed as top effects. This is
+         desirable because it brings the behaviour of GESBaseEffectClip in line
+         with other clip types.
+
+2020-03-11 19:38:19 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+       * plugins/nle/nleobject.c:
+         nle: Delay marking object as not in composition
+         Instead of doing it at the time of resetting `object->in_composition`
+         when user calls `gst_bin_remove` do it after we actually removed
+         it from the object thread, and do it in the `nle_object_reset`
+         method where it belongs
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/96
+
+2020-03-10 21:54:56 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-auto-transition.c:
+         auto-transition: fix setting of SET_SIMPLE flag
+         Previously, the SET_SIMPLE flag was non unset for auto-transitions after
+         it had been set.
+
+2020-03-11 13:42:50 +0200  Sebastian Dröge <sebastian@centricular.com>
+
+       * meson.build:
+         Fix build with Python 3.8 by also checking for python-3.X-embed.pc
+         Since Python 3.8 the normal checks don't include the Python libraries
+         anymore and linking of the Python formatters would fail.
+         See also https://github.com/mesonbuild/meson/issues/5629
+         and https://gitlab.freedesktop.org/gstreamer/gst-python/issues/28
+
+2020-03-09 11:49:33 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Handle checking/setting subprojects ges properties
+
+2020-03-09 11:49:02 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+         project: Do not warn when resetting URI to the same one
+
+2020-03-05 15:56:28 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-auto-transition.c:
+       * ges/ges-clip.c:
+       * ges/ges-source-clip.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-tree.c:
+       * ges/ges-timeline.c:
+       * tests/check/ges/group.c:
+       * tests/check/python/common.py:
+       * tests/check/python/test_timeline.py:
+         ges: Make setting start/duration move or trim generic
+         We were implementing the logic for moving/trimming elements specific
+         to SourceClip but this was not correct ass the new timeline tree allows
+         us to handle that for all element types in a generic and nice way.
+         This make us need to have groups trimming properly implemented in the
+         timeline tree, leading to some fixes in the group tests.
+         This adds tests for the various cases known to not be handled properly
+         by the previous code.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/92
+
+2020-03-04 17:42:46 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-group.c:
+       * tests/check/python/common.py:
+       * tests/check/python/test_group.py:
+         group: Update priority when a child is removed
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/93
+
+2020-03-04 17:16:18 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-clip.c:
+       * tests/check/python/test_timeline.py:
+         clip: Don't split clips at illegal position
+         Make sure that when we split a clip, the resulting timeline would
+         not be in an illegal state.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/94
+
+2020-03-05 19:00:20 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: don't link tracks unnecessarily
+         Unless the pipeline is in certain modes, we do not want to try and link
+         every track. The previous debug message implied this, but the method did
+         not actually end early.
+         Also, we always end early if we receive a track that is neither video
+         nor audio.
+
+2020-03-05 18:15:41 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-asset.c:
+       * tests/check/ges/asset.c:
+         asset: fix handling of proxies
+         Previous usage of the property proxy-target seemed to alternate between
+         the two definitions:
+         + The asset we are the default proxy of
+         + The asset we are in the proxy list of
+         Now, the latter definition is used, which seems more useful to a user
+         since knowing the latter can easily allow you to find out the former.
+         The previous behaviour of ges_asset_set_proxy (asset, NULL) was not very
+         clear. It is now defined so that it clears all the proxies for 'asset'.
+         This means that after this call, the GESAsset:proxy property will indeed
+         be NULL.
+         Also fixed:
+         + We can call ges_asset_set_proxy (asset, proxy) when 'proxy' is already
+         in the proxy list of 'asset'.
+         + Handling of removing the default proxy in ges_asset_unproxy. This was
+         sending out the wrong notifies.
+         + Prohibiting circular proxying. Before we could only prevent one case,
+         we should now be able to prevent all cases. This will prevent a hang
+         in ges_asset_request.
+
+2020-03-04 17:00:46 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * tests/check/ges/asset.c:
+         test: remove asset test that needs internal method
+         The test_proxy_asset test needs the internal method
+         ges_asset_finish_proxy. The test also uses the associated internal methods
+         ges_asset_try_proxy and ges_asset_cache_lookup. However, these are
+         marked with GES_API in ges-internal.h, which allows us access to them
+         here.
+         The new method is not marked as GES_API because it would not allow us to
+         remove the method in the future without removing it from the symbols list.
+         We do not want to add to the problem.
+         The test was simply commented out since we may wish to support tests
+         that access internal methods in the future using meson.
+
+2020-03-04 13:05:58 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-asset.c:
+         asset: fix ownership in ges_asset_request
+         Fix the ownership in ges_asset_request. This should be transfer-full,
+         but for proxies it would fail to add a reference. Also,
+         ges_asset_cache_put was leaking memory if the asset already existed.
+
+2020-03-04 11:31:32 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-project.c:
+       * tests/check/ges/asset.c:
+         asset: move set_proxy (NULL, proxy) behaviour to new method
+         We should not be accepting ges_asset_set_proxy (NULL, proxy) as part of
+         the API! This behaviour was used internally in combination with
+         ges_asset_try_proxy, which is called on a still loading asset, so it was
+         moved to ges_asset_finish_proxy.
+
+2020-03-04 10:34:45 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-asset.h:
+         asset: deprecate ->proxied method
+         This method was no longer called, so it has been deprecated.
+
+2020-03-04 09:59:33 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-asset.c:
+         asset: make proxy-target read only
+         We should not be able to set this property.
+
+2020-02-27 16:08:45 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: fix layer priority argument in trim
+         Previously, we tested that the given priority was `>0`, when it seems
+         that `>=0` was intended. A priority of `-1` means leave the priority
+         unchanged, whilst a priority of 0, or more, means move to this layer
+         priority.
+
+2020-02-21 09:23:34 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element.c:
+         timeline-element: use default ->list_children_properties
+         Stop overwriting the ->list_children_properties virtual method in
+         subclasses because the timeline element class handles everything itself
+         anyway.
+         Note that containers already automatically add the children properties of
+         their child elements in ges_container_add.
+
+2020-02-25 08:16:58 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-group.c:
+         group: fix memory leak in child layer callback
+         We were leaking the sigids->layer argument because gst_clip_get_layer
+         returns a new reference.
+
+2020-02-24 20:19:12 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+         container: fix child duration callback
+         Previously, we were setting the inpoint_offset using the start offset in
+         the duration callback!
+         Also added a notify for when the duration is changed in the child start
+         callback.
+
+2020-02-24 18:58:55 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-group.c:
+         group: fix max layer priority
+         The maximum priority is `height - prio - 1`. Previously missing the -1.
+         Related to, but does not completely fix,
+         https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/91
+
+2020-02-18 18:02:08 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+         clip: allow for neither track nor type in search
+         Previously, either the track or track_type arguments had to be specified
+         in order to find **any** track elements. Now, you can specify neither,
+         which will match any track element, of the specified type.
+
+2020-02-18 12:17:50 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * tests/check/python/test_timeline.py:
+         pythontests: change num layers in timeline to 1
+         In the test_timeline.test_auto_transition, the corresponding xges only
+         has one layer, so we should only expect one layer when we extract the
+         timeline. This fixes a change that was missing from commit
+         d3e2cf55e3ad6258ff09220ee6393655fdd833f1
+
+2020-02-18 12:14:25 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-extractable.c:
+         extractable: check extractable-type of set asset
+         When setting the asset of a GESExtractable object, first make sure that
+         the asset's extractable-type matches the type of the object.
+
+2020-02-18 09:17:09 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-layer.c:
+         layer: fix ownership when failing to add clip
+         If a clip is already part of a layer, then adding it to another layer
+         should fail. Previously, in this case, `ges_layer_add_clip` was adding a
+         reference to the clip instead, without subsequently giving up ownership.
+         This meant that the clip would be left with an unowned reference.
+         This has now been corrected by also calling `unref` after the
+         `ref_sink`.
+         Note that, since `clip` is already part of `current_layer`, it should
+         already be non-floating, so the `ref_sink`-`unref` should do nothing
+         overall. But we keep both to make the ownership (transfer floating/none)
+         explicit.
+
+2020-02-12 22:23:38 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-audio-track.c:
+       * ges/ges-video-track.c:
+         docs: update GESAudioTrack and GESVideoTrack
+
+2020-01-21 12:01:41 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-enums.h:
+       * ges/ges-pipeline.c:
+         docs: update GESPipeline
+
+2020-01-17 20:10:23 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-meta-container.c:
+       * ges/ges-meta-container.h:
+         docs: update GESMetaContainer
+
+2020-01-17 15:27:29 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-extractable.c:
+       * ges/ges-extractable.h:
+         docs: update GESExtractable
+
+2020-01-17 12:20:11 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-asset.h:
+         docs: update GESAsset
+
+2020-01-15 14:46:02 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-track-element.c:
+       * ges/ges-track-element.h:
+         docs: update GESTrackElement
+
+2020-01-15 14:44:38 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-track.c:
+       * ges/ges-track.h:
+         docs: update GESTrack
+
+2020-01-09 12:11:35 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-clip.h:
+         docs: update GESClip
+
+2020-01-09 12:09:15 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * ges/ges-group.c:
+         docs: update GESGroup
+
+2020-01-08 09:26:07 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-container.c:
+       * ges/ges-container.h:
+         docs: update GESContainer
+
+2020-01-07 17:40:53 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-enums.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+         docs: update GESTimelineElement
+
+2019-12-20 12:30:54 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: unref copied and pasted
+
+2019-12-20 11:20:49 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: fix paste ownership
+         The method steals ownership of `copied_from`, so should be responsible
+         for unreffing it. Also make sure we fail when `layer != -1`, since this
+         functionality is not supported.
+
+2019-12-18 20:33:45 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-layer.c:
+       * ges/ges-layer.h:
+       * ges/ges-timeline.c:
+       * ges/ges-timeline.h:
+       * ges/ges-utils.c:
+         docs: update GESTimeline and GESLayer
+
+2020-03-03 18:07:32 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+         python: Cleanup overrides using monkey patching
+         Following the PyGObject guidelines[0], this starts monkey patching
+         overridden elements instead of subclassing them.
+         [0]: https://pygobject.readthedocs.io/en/latest/devguide/override_guidelines.html#python-override-guidelines
+
+2018-11-29 19:12:24 +0100  Jens Göpfert <mail@jensgoepfert.de>
+
+       * examples/c/concatenate.c:
+         add assets to layer and adjust position and duration (closes #45)
+
+2020-03-02 19:06:17 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+       * tests/check/python/test_timeline.py:
+         python: Add a Timeline.iter_clips() helper to iterate clips
+
+2020-02-24 12:21:11 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * meson.build:
+       * meson_options.txt:
+         meson: Add an option to enable/disable validate integration
+
+2020-02-22 14:23:45 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/gstframepositioner.c:
+       * tests/check/meson.build:
+       * tests/check/scenarios/check_video_track_restriction_scale.scenario:
+       * tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario:
+         framepositioner: Reposition source when the user positioned them
+         Keeping the same proportion in the size and position and only if
+         the aspect ratio is conserved.
+
+2020-02-24 08:50:04 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.c:
+       * tools/ges-validate.h:
+         ges:launch: Handle setting playback information in scenarios
+         This way we can avoid real sinks when implementing scenarios
+
+2020-02-24 08:47:11 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+       * ges/ges-track-element.c:
+       * ges/ges-validate.c:
+         validate: Handle absolute control binding support when setting keyframes
+         And minor fix in set-control-source
+
+2020-02-19 18:09:19 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-source.c:
+       * ges/ges-video-uri-source.c:
+       * ges/gstframepositioner.c:
+       * ges/gstframepositioner.h:
+       * tests/check/scenarios/check_video_track_restriction_scale.scenario:
+         ges: Properly position video sources in the scene by default
+         We try to do our best to have the video frames scaled the best way
+         to fill most space on the final frames, keeping aspect ratio. The user
+         can later on rescale or move the sources as usual but it makes the
+         default behaviour a better and more natural especially now that we
+         set default restriction caps to the video tracks.
+         And fix the unit test to take that change into account
+
+2020-02-19 18:06:26 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-image-source.c:
+       * ges/ges-internal.h:
+       * ges/ges-video-source.c:
+       * ges/ges-video-source.h:
+       * ges/ges-video-test-source.c:
+       * ges/ges-video-uri-source.c:
+         ges: Add a method to retrieve the 'natural' size of VideoSource
+         This way the user can easily know how the clip would look like
+         if no scaling was applied to the clip, this is useful to be able
+         to properly position the clips with the framepositionner element.
+
+2020-02-19 15:31:28 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+       * ges/ges-container.c:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+         ges: Call the right ->set_child_property vmethod
+         We used to always call the `->set_child_property` virtual method
+         of the object that `ges_timeline_element_set_child_property` was called
+         from, but that means that, in the case of referencing GESContainer
+         children properties from its children, the children wouldn't know
+         what child property have been set, and the children override wouldn't
+         be takent into account, in turns, it means that the behaviour could be
+         different in the setter depending on parent the method was called,
+         which is totally unexpected.
+         We now make sure that the vmethod from the element that introduced the
+         child property is called whatever parent method is called, making the
+         behaviour more uniform.
+         Fix the python override to make sure that new behaviour is respected.
+
+2020-02-18 16:31:15 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.h:
+         ges: Deprecate the GESTimeline::track field
+         It is not MT safe to access it, and user should use the proper getter
+
+2020-02-18 16:09:55 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-internal.h:
+       * ges/ges-video-track.c:
+         ges: Set default caps for GESVideoTrack
+         By default, video track output full HD@30fps, this makes the behaviour
+         of clip position much more understandable and guarantess that we
+         always have a framerate.
+         The user can modify the values whenever he wants
+
+2020-02-20 12:28:59 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/gstframepositioner.c:
+       * tests/check/ges/timelineedition.c:
+       * tests/check/meson.build:
+       * tests/check/scenarios/check_video_track_restriction_scale.scenario:
+       * tools/meson.build:
+         framepositioner: Stop lying about the source size
+         Basically we were advertising that the source size would be the
+         size of the track if it hadn't been defined by end user, but since
+         we started to let scaling happen in the compositor, this is not true
+         as the source size is now the natural size of the underlying video
+         stream.
+         Remove the unit test and reimplemented using a validate scenario which
+         make the test much simpler to read :=)
+
+2020-02-20 12:27:37 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Add action types to set/check various child properties at once
+         And add a way to take into account control bindings.
+
+2020-02-20 12:22:19 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.c:
+         ges: Allow setting children property using the set_object_arg format
+         This make it much simpler for the user to set enum values and should not cause any issue
+
+2020-02-20 17:13:46 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+       * ges/ges-validate.c:
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.c:
+       * tools/utils.c:
+       * tools/utils.h:
+         ges: Plug leaks in new ges-launch and related
+
+2020-02-25 17:38:15 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Port to the new REPORT_ACTION API
+
+2020-02-18 23:08:53 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlesource.c:
+         nlesource: When standalone consider object.duration==0 as not set
+         nleobject.duration defaults to 0, but this is pretty unintuitive for
+         end user in the case nlesource is use standalone, just consider
+         duration=0 equivalent to duration=GST_CLOCK_TIME_NONE as it makes
+         the element much simpler to use, we could actually forbid 0 as a value
+         in the future.
+         Also take into account potential CLOCK_TIME_NONE
+
+2020-02-10 18:05:38 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-source.c:
+       * ges/ges-internal.h:
+       * ges/ges-source.c:
+       * ges/ges-video-source.c:
+       * ges/ges-video-source.h:
+       * ges/ges-video-test-source.c:
+       * ges/ges-video-uri-source.c:
+         ges: Avoid adding unnecessary converters for nested timelines
+         Basically we know that if we are using mixing, compositor will be
+         able to do video conversion and scaling for us, so avoid adding those
+         usless elements.
+         This optimizes a lot caps negotiation for deeply nested timelines.
+
+2020-02-10 18:00:33 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesbasebin.c:
+       * plugins/ges/gesdemux.c:
+         plugins:ges: Fix pushing tags after e8c782d119eccf364fa24812cdc90c40f60d65d6
+         Basically the tags we send before STREAM_START are now ignored, meaning
+         that we could not detect nested timelines anymore, this commits makes
+         sure that we send our tag event after getting pushing STREAM_START.
+
+2020-02-06 16:42:25 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Optimize prerolling when using nested compositions
+         When a composition is nested into anotherone, we *know* that the
+         toplevel composition is going to send a stack initializing seek,
+         we can thus avoid sending it on the subcomposition itself when
+         prerolling. This avoid seeking roundtrips and we now have one and
+         only one seek travelling in the overall pipeline (after it has
+         prerolled).
+
+2020-02-06 12:43:57 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlesource.c:
+         nlesource: Fix seeks when used standalone
+         The 'start' of nleobject is in the 'composition' scale, inpoint is in
+         the media scale, when outside a composition, a nleobject->start value
+         doesn't mean anything.
+
+2020-02-06 12:39:12 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+       * plugins/nle/nlesource.c:
+         nle: Seek the whole stack on initialization
+         Instead of seeking each nleobject separately to setup new stack, wait
+         for the whole stack to preroll and then seek that newly setup stack,
+         leading to the same code path and seek 'tweaking' as when processing
+         a seek on the composition (without stack changes).
+         This is mandatory to properly handle filter that tweak segments to handle
+         time remapping for example.
+
+2020-02-06 12:37:37 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+       * plugins/nle/nleghostpad.c:
+         nle: Minor typo fixes
+
+2020-02-04 17:07:39 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/ges-launcher.h:
+       * tools/ges-validate.c:
+       * tools/ges-validate.h:
+         validate: Allow overriding ges-launch options through scenarios
+         In 99c45d42cfd1cafb658b63abf0b506db20167499 we allowed setting
+         track-types but in the end we could do it generically using the
+         following synthax in the scenario 'properties' metadata:
+         `ges-options={--track-types=video,--disable-mixing}`
+
+2020-02-07 09:39:39 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-effect-clip.c:
+       * ges/ges-effect.c:
+       * ges/ges-extractable.c:
+       * ges/ges-extractable.h:
+       * ges/ges-internal.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-transition-clip.c:
+       * ges/ges-uri-clip.c:
+         ges: Ignore deprecation of GParameter
+         GParameter is part of our API, and for GLib < 2.54 we do not even have
+         a way around avoiding it (namely `g_object_new_with_properties`).
+         We should stop using GParameter once we depend on GLib 2.54.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/86
+
+2019-08-20 17:46:09 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-container.h:
+       * ges/ges-formatter.c:
+       * ges/ges-formatter.h:
+       * ges/ges-layer.h:
+       * ges/ges-prelude.h:
+       * ges/ges-screenshot.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-title-clip.c:
+       * ges/ges-title-clip.h:
+       * ges/ges-track-element-deprecated.h:
+       * ges/ges-track-element.h:
+       * ges/meson.build:
+         ges: Use G_DEPRECATE to mark deprecated methods
+         Cleanup a few things on the way.
+         And move ges-track-element deprecations to a dedicated header file
+
+2019-12-14 17:04:54 +0000  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-source-clip.c:
+       * ges/ges-timeline-element.c:
+       * tests/check/ges/clip.c:
+         ges-source-clip: fixed return of duration setter
+         In general, brought the behaviour of the `start`, `duration` and
+         `inpoint` setters in line with each other. In particular:
+         1. fixed return value the GESSourceClip `duration` setter
+         2. changed the GESClip `start` setter
+         3. fixed the inpoint callback for GESContainer
+         4. changed the type of `res` in GESTimelineElement to be gint to
+         emphasise that the GES library is using the hack that a return of -1
+         from klass->set_duration means no notify signal should be sent out.
+         Also added a new test for clips to ensure that the setters work for
+         clips within and outside of timelines, and that the `start`, `inpoint`
+         and `duration` of a clip will match its children.
+
+2019-12-05 14:23:04 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: Ensure that encodebin enforces a single segment sent to encoders
+
+2019-10-04 09:58:17 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/validate/geslaunch.py:
+         validate: Move to the new GstValidateEncodingTestInterface API
+
+2019-11-20 07:52:56 +0100  Edward Hervey <edward@centricular.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Free structure after usage
+         CID: 1416901
+         CID: 1439518
+         CID: 1439527
+
+2019-11-20 07:46:47 +0100  Edward Hervey <edward@centricular.com>
+
+       * ges/ges-pitivi-formatter.c:
+         formatter: Free path object after usage
+         As it's done everywhere else
+         CID: 1455511
+
+2019-11-07 16:54:32 +0530  Nirbheek Chauhan <nirbheek@centricular.com>
+
+       * meson.build:
+         meson: Fix disabling of the python support
+         Cannot call python.dependency() if the python module was not found.
+
+2019-08-29 07:45:45 +0200  Niels De Graef <nielsdegraef@gmail.com>
+
+       * ges/ges-container.c:
+       * ges/ges-layer.c:
+       * ges/ges-marker-list.c:
+       * ges/ges-meta-container.c:
+       * ges/ges-project.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline.c:
+       * ges/ges-track-element.c:
+       * ges/ges-track.c:
+       * plugins/nle/nlecomposition.c:
+       * plugins/nle/nleoperation.c:
+         Don't pass default GLib marshallers for signals
+         By passing NULL to `g_signal_new` instead of a marshaller, GLib will
+         actually internally optimize the signal (if the marshaller is available
+         in GLib itself) by also setting the valist marshaller. This makes the
+         signal emission a bit more performant than the regular marshalling,
+         which still needs to box into `GValue` and call libffi in case of a
+         generic marshaller.
+         Note that for custom marshallers, one would use
+         `g_signal_set_va_marshaller()` with the valist marshaller instead.
+
+2019-10-16 19:26:55 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-marker-list.c:
+       * ges/ges-meta-container.h:
+       * tests/check/ges/markerlist.c:
+         marker: add color meta
+         Support optionally coloring markers by reserving GES_META_MARKER_COLOR
+         for an ARGB guint.
+
+2019-10-16 13:40:57 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-meta-container.c:
+       * ges/ges-meta-container.h:
+         meta-container: add register_static_meta
+         Allows us to register a static meta without having to set a value.
+
+2019-10-16 11:37:23 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-meta-container.c:
+         meta-container: move comment
+         The comment that was above _register_meta is actually meant for
+         _set_value.
+
+2019-10-23 16:04:01 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * tools/ges-launch.c:
+       * tools/ges-launcher.c:
+         ges-launch: Document timeline description format under --help
+         Making it simpler for user to get the documentation
+
+2019-10-22 22:51:41 +0200  Rico Tzschichholz <ricotz@ubuntu.com>
+
+       * ges/ges-marker-list.c:
+         marker-list: Use proper parameters names even in the docs
+         Otherwise there will be parameters with hyphen in their name in the GIR.
+
+2019-10-22 13:30:36 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Enhance dumping stack output
+
+2019-10-22 12:21:04 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-marker-list.c:
+         ges: Handle empty marker lists
+
+2019-10-22 11:53:36 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Tear down pipeline when openning a new project
+         Avoiding potential deadlock when we remove tracks on a playing pipeline
+
+2019-10-22 11:50:02 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         ges: Fix setting GError when adding children to containers
+         We were misusing assertion and not properly setting the GError value
+
+2019-10-22 11:31:04 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-smart-video-mixer.c:
+         smart-video-mixer: Handle segment updates
+         We were basically ignoring any segment update which could potentially
+         lead to setting a wrong stream time leading to wrong alpha value
+         being used.
+
+2019-10-17 16:30:49 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/utils.c:
+       * tools/utils.h:
+         launcher: Enhance printed output
+
+2019-10-17 16:21:28 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/utils.c:
+       * tools/utils.h:
+         launcher: Use the output URI extension to set encoding format
+         And print a description of the encoding profile.
+
+2019-10-17 16:19:11 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-pipeline.c:
+         pipeline: Be smarter about how we match encoding profiles and tracks
+
+2019-10-18 00:50:16 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * meson.build:
+         meson: build gir even when cross-compiling if introspection was enabled explicitly
+         This can be made to work in certain circumstances when
+         cross-compiling, so default to not building g-i stuff
+         when cross-compiling, but allow it if introspection was
+         enabled explicitly via -Dintrospection=enabled.
+         See gstreamer/gstreamer#454 and gstreamer/gstreamer#381
+
+2019-10-16 16:40:27 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-marker-list.c:
+       * tests/check/ges/markerlist.c:
+         marker-list: add prev position to ::marker-moved
+         Additionally give the previous marker position in the
+         GESMarkerList::marker-moved signal, since a user may want to know
+         where a move was from.
+         Also, fixed the documentation for GESMarkerList::marker-added
+         https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/78
+
+2019-10-13 13:37:11 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * .gitignore:
+       * .gitmodules:
+       * Makefile.am:
+       * autogen.sh:
+       * bindings/Makefile.am:
+       * bindings/python/Makefile.am:
+       * bindings/python/gi/Makefile.am:
+       * bindings/python/gi/overrides/Makefile.am:
+       * common:
+       * configure.ac:
+       * examples/.gitignore:
+       * examples/Makefile.am:
+       * examples/c/Makefile.am:
+       * ges/.gitignore:
+       * ges/Makefile.am:
+       * m4/Makefile.am:
+       * pkgconfig/.gitignore:
+       * pkgconfig/Makefile.am:
+       * plugins/Makefile.am:
+       * plugins/ges/Makefile.am:
+       * plugins/nle/.gitignore:
+       * plugins/nle/Makefile.am:
+       * tests/.gitignore:
+       * tests/Makefile.am:
+       * tests/benchmarks/Makefile.am:
+       * tests/check/Makefile.am:
+       * tests/check/ges/.gitignore:
+       * tests/validate/Makefile.am:
+       * tests/validate/scenarios/Makefile.am:
+       * tools/Makefile.am:
+         Remove autotools build system
+         Todo:
+         - hook up data/completions/ges-launch-1.0 in Meson (#77)
+
+2019-10-01 18:02:27 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * ges/ges-internal.h:
+       * ges/ges.c:
+         ges: Hide internal debug category behind a GOnce
+         Otherwise it might be used (e.g. by the plugin loader via the GES
+         plugin!) before ges_init() is called.
+
+2019-10-01 18:01:21 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Initialize debug category before first using it
+         Prevents critical warnings during class_init()
+
+2019-09-23 16:10:59 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+         project: Add missing safe guard when listing assets
+
+2019-09-23 16:07:58 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-command-line-formatter.c:
+       * ges/ges-structured-interface.c:
+       * tools/ges-launcher.c:
+         launch: Add an option to embed nested timelines when saving
+
+2019-08-19 14:38:12 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: increase xges version to 0.6
+         Increase minor_version to 6 if a sub-project is saved under an asset or an asset includes a child stream-info element.
+
+2019-08-23 17:26:51 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+       * tests/check/python/test_timeline.py:
+         tests: Fix transition project tests
+         Basically the test project was plain broken as it had fully overlapping
+         clips is prohibited since the timeline edition API was reimplemented.
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/62
+
+2019-08-21 14:41:46 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+         asset: Fix asset cache for CLips and TrackElement with same ID
+         We clearly uniquely identify assets by both their IDs and their
+         extractable type, and we should make sure that you can have a
+         TrackElement and a Clip with the same ID.
+         There is one exception in our implementation which is GESFormatter
+         because we treat their subclasses as 1 type with different IDs.
+
+2019-08-17 11:59:38 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-effect.c:
+       * ges/ges.c:
+         ges: Expose ges mixer to be used as effects
+
+2019-08-17 11:59:02 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-effect-clip.c:
+       * ges/ges-layer.c:
+         ges: Add support for EffectClip assets
+
+2019-08-28 18:13:06 +1000  Matthew Waters <matthew@centricular.com>
+
+       * plugins/ges/gesdemux.c:
+       * plugins/ges/gessrc.c:
+         build: also suppress unused-function warnings about g_autoptr
+         ../plugins/ges/gesdemux.c:50:1: error: unused function 'glib_autoptr_cleanup_GESDemux' [-Werror,-Wunused-function]
+         G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, DEMUX, GESBaseBin);
+         ^
+         /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/gobject/gtype.h:1401:3: note: expanded from macro 'G_DECLARE_FINAL_TYPE'
+         _GLIB_DEFINE_AUTOPTR_CHAINUP (ModuleObjName, ParentName)                                               \
+         ^
+         /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:451:22: note: expanded from macro '_GLIB_DEFINE_AUTOPTR_CHAINUP'
+         static inline void _GLIB_AUTOPTR_FUNC_NAME(ModuleObjName) (ModuleObjName **_ptr) {                     \
+         ^
+         /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:441:43: note: expanded from macro '_GLIB_AUTOPTR_FUNC_NAME'
+         #define _GLIB_AUTOPTR_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName
+         ^
+         <scratch space>:81:1: note: expanded from here
+         glib_autoptr_cleanup_GESDemux
+         ^
+         ../plugins/ges/gessrc.c:56:1: error: unused function 'glib_autoptr_cleanup_GESSrc' [-Werror,-Wunused-function]
+         G_DECLARE_FINAL_TYPE (GESSrc, ges_src, GES, SRC, GESBaseBin);
+         ^
+         /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/gobject/gtype.h:1401:3: note: expanded from macro 'G_DECLARE_FINAL_TYPE'
+         _GLIB_DEFINE_AUTOPTR_CHAINUP (ModuleObjName, ParentName)                                               \
+         ^
+         /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:451:22: note: expanded from macro '_GLIB_DEFINE_AUTOPTR_CHAINUP'
+         static inline void _GLIB_AUTOPTR_FUNC_NAME(ModuleObjName) (ModuleObjName **_ptr) {                     \
+         ^
+         /home/matt/Projects/cerbero/build/dist/android_universal/x86_64/include/glib-2.0/glib/gmacros.h:441:43: note: expanded from macro '_GLIB_AUTOPTR_FUNC_NAME'
+         #define _GLIB_AUTOPTR_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName
+         ^
+         <scratch space>:158:1: note: expanded from here
+         glib_autoptr_cleanup_GESSrc
+         ^
+
+2019-08-27 10:02:04 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesbasebin.h:
+       * plugins/ges/gesdemux.c:
+       * plugins/ges/gessrc.c:
+         ges: fix G_DECLARE_FINAL_TYPE -Werror with clang
+         Also fix wrong casing the `G_DECLARE` for GESDemux.
+         ../subprojects/gst-editing-services/plugins/ges/gessrc.c:56:1: warning: unused function 'GES_SRC' [-Wunused-function]
+         G_DECLARE_FINAL_TYPE (GESSrc, ges_src, GES, SRC, GESBaseBin);
+         ^
+         /usr/include/glib-2.0/gobject/gtype.h:1405:33: note: expanded from macro 'G_DECLARE_FINAL_TYPE'
+         static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) {                                     \
+         ^
+         <scratch space>:39:1: note: expanded from here
+         GES_SRC
+         ^
+         ../subprojects/gst-editing-services/plugins/ges/gessrc.c:56:1: warning: unused function 'GES_IS_SRC' [-Wunused-function]
+         /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE'
+         static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) {                                         \
+         ^
+         <scratch space>:42:1: note: expanded from here
+         GES_IS_SRC
+         ^
+         ../subprojects/gst-editing-services/plugins/ges/gesdemux.c:50:1: warning: unused function 'GES_Demux' [-Wunused-function]
+         G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, Demux, GESBaseBin);
+         ^
+         /usr/include/glib-2.0/gobject/gtype.h:1405:33: note: expanded from macro 'G_DECLARE_FINAL_TYPE'
+         static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) {                                     \
+         ^
+         <scratch space>:72:1: note: expanded from here
+         GES_Demux
+         ^
+         ../subprojects/gst-editing-services/plugins/ges/gesdemux.c:50:1: warning: unused function 'GES_IS_Demux' [-Wunused-function]
+         /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE'
+         static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) {                                         \
+         ^
+         <scratch space>:75:1: note: expanded from here
+         GES_IS_Demux
+         ^
+
+2019-08-27 13:52:52 +1000  Matthew Waters <matthew@centricular.com>
+
+       * ges/ges-timeline.c:
+         ges/timeline: remove unused function get_toplevel_container
+         Fixes -Werror build with clang:
+         ../subprojects/gst-editing-services/ges/ges-timeline.c:695:1: warning: unused function 'get_toplevel_container' [-Wunused-function]
+         get_toplevel_container (gpointer element)
+         ^
+
+2019-08-23 12:36:38 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+       * ges/ges-enums.h:
+         doc: Update cache and fix usage of <ulink>
+
+2019-08-22 18:50:00 +0200  Millan Castro <m.castrovilarino@gmail.com>
+
+       * ges/Makefile.am:
+       * ges/ges-internal.h:
+       * ges/ges-marker-list.c:
+       * ges/ges-marker-list.h:
+       * ges/ges-meta-container.c:
+       * ges/ges-meta-container.h:
+       * ges/ges-types.h:
+       * ges/ges.c:
+       * ges/ges.h:
+       * ges/meson.build:
+       * tests/check/ges/layer.c:
+       * tests/check/ges/markerlist.c:
+       * tests/check/meson.build:
+         markerlist: implement GESMarkerList
+         Co-authored by Mathieu Duponchelle <mathieu@centricular.com>
+
+2019-08-20 15:29:12 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Fix querying if we need stack reloading
+         We are probing upstream queries, not downstream ones
+         This was clearly a small test that slipt into previous commit
+
+2019-08-16 17:41:17 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: strip "caps" from the "properties" attribute of a track element
+         We already have the separate "caps" attribute for xges track
+         elements, which is actually used in parsing.
+
+2019-08-19 16:35:49 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: fix cb of ::error-loading-asset
+         Corrected typo that attached project_loaded_cb, rather than error_loading_asset_cb, to ::error-loading-asset, which meant data.error would be left unset if an error occurred in loading.
+
+2019-08-15 17:32:12 +0100  Henry Wilkes <hwilkes@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+         Test that gst_structure_get succeeds to ensure gchar *restriction is actually set before reading it. Warn if no caps are returned by gst_caps_from_string.
+
+2019-08-14 15:48:46 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+       * ges/ges-structured-interface.c:
+         structured-interface: Properly error out when a child property could not be set
+
+2019-08-12 17:37:39 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/nle/nlecomposition.c:
+         tests:nle: Unref the bus before unrefing the pipeline
+         Aiming at fixing a rare race condition where we get:
+         ../subprojects/gstreamer/libs/gst/check/gstcheck.c:1258:F:nlecomposition:test_seek_on_nested:0: nested_src0_0x1a1a310 is not destroyed, 1 refcounts left!
+         The idea is that there might have a remaining GstMessage
+         with the nested_src as `message.src` on the bus that has
+         yet to be processed in some conditions leading to a reference
+         still existing when unrefing the pipeline.
+
+2019-08-12 17:17:53 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * ges/ges-xml-formatter.c:
+         ges-xml-formatter: Use g_filename_to_uri() instead of deprecated gst_uri_construct()
+         ges-xml-formatter.c: In function ‘_parse_asset’:
+         ges-xml-formatter.c:357:7: error: ‘gst_uri_construct’ is deprecated: Use 'gst_uri_new' instead [-Werror=deprecated-declarations]
+         357 |       id = gst_uri_construct ("file", subproj_data->filename);
+         |       ^~
+
+2019-08-12 17:16:44 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-uri-asset.c:
+         Fix old-style C function declarations
+         ges-uri-asset.c: In function ‘create_discoverer’:
+         ges-uri-asset.c:53:1: error: old-style function definition [-Werror=old-style-definition]
+         53 | create_discoverer ()
+         | ^~~~~~~~~~~~~~~~~
+         ges-uri-asset.c: In function ‘get_discoverer’:
+         ges-uri-asset.c:67:1: error: old-style function definition [-Werror=old-style-definition]
+         67 | get_discoverer ()
+         | ^~~~~~~~~~~~~~
+         CC       libges_1.0_la-ges-auto-transition.lo
+         ges-asset.c: In function ‘_get_type_entries’:
+         ges-asset.c:489:1: error: old-style function definition [-Werror=old-style-definition]
+         489 | _get_type_entries ()
+         | ^~~~~~~~~~~~~~~~~
+
+2019-08-12 09:49:45 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track.c:
+       * ges/ges-uri-asset.c:
+       * plugins/ges/gesbasebin.c:
+         doc: Add some missing Since:
+
+2019-08-11 21:20:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+         project: Properly handle NULL project asset ID
+
+2019-07-30 18:24:07 -0700  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         structured: Enhance error message when no clip duration set
+
+2019-07-30 18:22:18 -0700  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         structured-interface: Avoid setting invalid clip duration
+
+2019-07-16 21:51:10 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track.c:
+       * ges/ges-track.h:
+         track: Add a getter for restriction_caps
+
+2019-07-13 21:27:46 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launch: Set user restriction caps even when loading projects
+
+2019-07-13 21:26:35 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track.c:
+         track: Enhance restriction capsfilter name
+
+2019-07-13 13:25:48 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Serialize DiscovererStreamInfo
+         We do not use it yet but it gives interesting information to
+         users
+
+2019-07-12 16:15:35 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-xml-formatter.c:
+       * tests/check/ges/project.c:
+         formatter: Plug lists of TimedValue leak
+
+2019-07-05 09:40:57 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-formatter.c:
+       * ges/ges-formatter.h:
+       * ges/ges-xml-formatter.c:
+         formatter: Better document metadata registration
+         And fix xges mimetype to match typefind mimetype
+
+2019-07-04 16:51:54 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/meson.build:
+         doc: Do not require the GStreamer cache generator
+
+2019-07-04 15:58:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges.c:
+       * ges/python/gesotioformatter.py:
+       * plugins/ges/gesdemux.c:
+         gesdemux: Compute sinkpad caps based on formatter mimetypes
+         Implement lazy loading asset cache so gesdemux use the formatters
+         assets while GES hasn't been initialized.
+         And set extensions to temporary files as some formatters require
+         the information (otio)
+
+2019-07-03 20:15:23 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-formatter.c:
+       * ges/ges-formatter.h:
+       * ges/ges-project.c:
+         formatter: Add a method to retrieve the best formatter for a givent URI
+         Uses the file extension as hint falling back to the default formatter
+         if none is found
+         Make use of that function in when saving a project and not formatter
+         is specified.
+
+2019-02-05 15:46:49 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-formatter.c:
+       * ges/ges.resource:
+       * ges/meson.build:
+       * ges/python/gesotioformatter.py:
+       * meson.build:
+       * meson_options.txt:
+         Implement a formatter based on [OpenTimelineIO]
+         [OpenTimelineIO]: http://opentimeline.io/
+
+2019-04-19 09:07:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-formatter.c:
+         formatter: Handle coma separated extensions in formatter metas
+
+2019-03-11 19:25:23 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-formatter.c:
+       * ges/ges-formatter.h:
+         formatter: Duplicate const gchar* for metadatas
+
+2019-02-05 16:08:10 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+       * ges/ges-project.h:
+         project: Expose the ges_project_add_formatter method
+         This method is useful when implementing a formatter outside
+         GES that end up converting to xges and uses the default formatter
+         to finally load the timeline.
+
+2019-07-11 16:23:47 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-asset.c:
+       * ges/ges-uri-asset.h:
+       * ges/ges-uri-clip.c:
+       * plugins/ges/gesdemux.c:
+         Mark nested timeline assets as such
+         Adding a property to let the application know
+         Also make sure that the duration of nested timeline assets is reported
+         as CLOCK_TIME_NONE as those are extended as necessary.
+         And make a difference between asset duration and their max duration
+         As nested timelines can be extended 'infinitely' those max duration
+         is GST_CLOCK_TIME_NONE, but their duration is the real duration of
+         the timeline.
+
+2019-07-11 15:54:27 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-pitivi-formatter.c:
+       * ges/ges-project.c:
+       * plugins/ges/gesdemux.c:
+       * tools/ges-launcher.c:
+         formatter: Enhance error reporting
+         And add a "loading-error" signal in GESProject so we can report
+         issue when loading async elements for the timeline.
+
+2019-07-11 15:43:47 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * tests/check/ges/test-properties.xges:
+         xml-formatter: Fix loading sources
+         And fix the project file which couldn't be load now that we
+         properly check clips coherency
+
+2019-07-10 19:36:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Add a testsrc to timelines if parent nleobject duration is too long
+
+2019-07-10 12:06:01 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         ges:validate: Properly error when editing container fails
+
+2019-07-10 11:02:07 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+       * plugins/ges/gesdemux.c:
+       * plugins/nle/nlecomposition.c:
+         nle: Handle nested timelines update when file changes
+         When we have nested timelines, we need to make sure the underlying
+         formatted file is reloaded when commiting the main composition to
+         take into account the new timeline.
+         In other to make the implementation as simple as possible we make
+         sure that whenever the toplevel composition is commited, the decodebin
+         holding the gesdemux is torn down so that a new demuxer is created
+         with the new content of the timeline.
+         To do that a we do a NleCompositionQueryNeedsTearDown query to which
+         gesdemux answers leading to a full nlecomposition stack
+         deactivation/activation cycle.
+
+2019-07-10 10:15:31 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+       * ges/ges-structured-interface.h:
+       * ges/ges-validate.c:
+         ges:validate: Add a way to execute actions on serialized timelines
+         This way we can modify nested timelines.
+
+2019-07-09 01:03:56 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-internal.h:
+       * ges/ges-project.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-utils.c:
+         ges: Implement our own idle_add which uses the thread local maincontext
+
+2019-07-09 00:28:29 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Allow passing 'uri' to 'load-project'
+         The action type was thought to allow that but it wasn't implemented.
+
+2019-07-09 00:07:16 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Lower down borring debug to _LOG
+
+2019-07-09 00:05:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+         project: Use asset ID as URI if possible
+         It was making no sense to consider it an empty timeline when the user
+         had passed the project URI when requesting the asset. Usually user
+         use `ges_project_new` with the URI but it is also valid to use
+         `ges_asset_request` with the uri as ID so let's handle that properly.
+
+2019-07-08 19:25:32 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+         python: Add a better asset __repr__
+
+2019-07-07 20:55:53 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-base-xml-formatter.h:
+       * ges/ges-internal.h:
+       * ges/ges-project.c:
+       * ges/ges-xml-formatter.c:
+       * ges/ges.c:
+       * tests/check/python/common.py:
+       * tests/check/python/test_timeline.py:
+         ges: Implement subprojects
+         Subprojects simply consist of adding the GESProject
+         to the main project asset list. Then those are recursively
+         serialized in the main project in the <asset> not, when deserializing,
+         temporary files are created and those will be used in clips
+         as necessary
+
+2019-07-07 20:35:14 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+         project: Fix our asset cache
+         It was not talking into account the fact that you can have
+         several assets with a same ID but different exactractable types.
+
+2019-07-14 16:28:23 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+         asset: Handle trying to proxy an asset to itself
+         And avoid infinite recursion
+
+2019-07-03 12:10:24 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Detect recursively loading the same project file
+         And error out when it is the case.
+
+2019-07-03 12:09:23 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Create proper stream-ids
+
+2019-07-03 10:10:42 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/nle/nlecomposition.c:
+         nle: Check seeking on deeply nested composition
+
+2019-06-28 20:19:49 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-track.c:
+         track: Disable last gap by default
+         And let the GESPipeline logic handle that
 
-2019-10-01 18:02:27 +0300  Sebastian Dröge <sebastian@centricular.com>
+2019-06-28 20:19:20 -0400  Thibault Saunier <tsaunier@igalia.com>
 
-       * ges/ges-internal.h:
-       * ges/ges.c:
-         ges: Hide internal debug category behind a GOnce
-         Otherwise it might be used (e.g. by the plugin loader via the GES
-         plugin!) before ges_init() is called.
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Drop all group-done but the last one
+
+2019-06-28 17:35:40 -0400  Thibault Saunier <tsaunier@igalia.com>
 
-=== release 1.16.1 ===
+       * tools/ges-launcher.c:
+       * tools/ges-validate.c:
+       * tools/ges-validate.h:
+         validate: Allow scenarios to set track types
 
-2019-09-23 11:18:40 +0100  Tim-Philipp Müller <tim@centricular.com>
+2019-06-19 15:52:21 +0530  Swayamjeet <swayam1998@gmail.com>
 
-       * ChangeLog:
-       * NEWS:
-       * RELEASE:
-       * configure.ac:
-       * gst-editing-services.doap:
+       * tests/validate/geslaunch.py:
+         tests: Add ges-sample-path-recurse with projects location
+         So that project files are found when using nested timelines
+
+2019-06-23 13:03:54 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/validate/geslaunch.py:
+       * tools/ges-validate.c:
+         validate: Add a way to use validate configs with scenarios
+         Config files should have the-scenario-name.scenario.config to be picked automatically
+
+2019-06-23 13:03:04 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/validate/geslaunch.py:
+         validate: Use proper sink and give them good names
+
+2019-06-23 12:42:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-validate.c:
+         validate: Create folders as needed when serializing timelines
+
+2019-06-22 23:49:50 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlesource.c:
+         nlesource: Wait for the seek to actualy happen before removing the probe
+         Make sure that an event resulting from the seek happens before removing
+         the pad probe, dropping anything while it is not the case.
+         This guarantees that the seek happens before `nlesource` outputs
+         anything. This was not necessary as with decodebin or usual source
+         flushing seeks lead to synchronous flush_start/flush_stop and we could
+         safely assume that once the seek is sent, it was happenning.
+         With nested `nlecomposition` this assumption is simply not true as
+         in the composition seeks are basically cached and happen later in
+         the composition updating thread.
+         This fixes races where we ended up removing the blocking probe before
+         the seek actually started to be executed in the nlecomposition
+         nested inside an nlesource which leaded to data from *before* the seek
+         to be outputed which means we could display wrong frames,
+         and it was leading to interesting deadlocks.
+
+2019-06-22 23:25:57 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Minor debugging enhancements
+
+2019-06-21 11:45:20 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-asset.c:
+       * tests/check/python/test_assets.py:
+         uri-asset: Fix retrieving a relocated asset sync twice
+         Add a simple test for that.
+
+2019-06-21 10:47:34 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: Make adding/removing track MT safe
+         It was almost the case already so make it happen fully
+
+2019-06-19 18:14:52 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Ensure flushes after seek have the right seqnum
+         Seeks that lead to a stack change lead to deactivating the current
+         stack. At that point we explicitely flush downstream as a reaction to
+         the flushing seek. Until now those flushes had a random seqnum, this
+         fails if we are a nested compostion as the parent composition will end
+         up dropping that flush which in turns might lead to deadlocks. For
+         example, the flush goes through a `compositor` which wants to flush
+         downstream to stop its srcpad task, but that flush wouldn't have
+         "released" its srcpad thread if the composition srcpad drops it, meaning
+         it won't be able to stop the task ever.
+
+2019-06-17 18:23:43 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+       * tests/check/nle/nlecomposition.c:
+         nlecomposition: Shutdown children when setting state to NULL
+         Otherwise if we shutdown a composition whith an nested composition
+         (inside a source in the test) and leak it, we end up with the nested
+         composition task still running (in READY) which is bad.
+         Add a test for that which leaks the pipeline on purpose.
+
+2019-06-17 18:23:07 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nle: Parent the GstTask to ourself
+         This allows accessing the nlecomposition in gdb when a task is
+         'dangling' making debugging easier.
+
+2019-06-11 23:51:14 +0530  Swayamjeet <swayam1998@gmail.com>
+
+       * tests/validate/geslaunch.py:
+         tests: Implement nested timelines tests
+
+2019-06-16 23:03:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Properly set seqnum on flush events
+
+2019-06-16 23:00:31 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: Drop ASYNC_/START/DONE messages
+         When we have nested timelines, we do not want those messages to pop
+         to the parent timelines as we handle the sequence ourself in the
+         timeline.
+
+2019-06-14 23:48:20 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         demux: Create timeline from the streaming thread
+         First marshilling it to the main thread is dangerous as it is a blocking
+         operation and it should never happen there.
+         The asset cache is MT safe now so it is possible to load the timeline
+         from that thread directly
+
+2019-06-16 21:27:47 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-asset.c:
+       * ges/ges-uri-asset.h:
+       * plugins/ges/gesdemux.c:
+         uri-asset: Implement multi threading support
+         Making sure to have 1 GstDiscoverer per thread.
+         Use that new feature in gesdemux by loading the timeline directly from
+         the streaming thread. Modifying the timeline is not supported allowed
+         anyway.
+
+2019-06-09 19:35:21 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/nle/nlecomposition.c:
+         nle: Add a seeking test for nested composition
+
+2019-06-07 16:12:26 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-uri-asset.c:
+       * plugins/ges/gesdemux.c:
+         Use the new GstDiscoverer caching feature
+
+2019-06-07 16:06:39 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+         timeline: Do not post upstream translated composition update messages
+         In the case of nested timeline in the toplevel timeline we ended up
+         with CompositionUpdate for seeks sent by our own composition to
+         granchildren composition. This was not causing essential issues
+         if all tracks where containing nested timelines but in cases
+         where one of the tracks only had a nested timelines, then we
+         were waiting forever for a `CompositionUpdateDone`.
+         CompositionUpdate translated into ASYNC_START/ASYNC_DONE should
+         be kept inside the GESTimeline and not travel up (possibly to some
+         parent GESTimeline).
+
+2019-06-07 09:10:53 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-structured-interface.c:
+         structured-interface: Handle track-types in clip addition
+         The field was already expected in the launcher
+
+2019-06-06 23:19:38 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+       * ges/ges-track.c:
+       * plugins/ges/gesbasebin.c:
+       * plugins/ges/gesdemux.c:
+       * plugins/nle/nlecomposition.c:
+         Implement and use the GstStream API
+
+2019-06-06 17:21:01 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline.c:
+       * ges/ges-track.c:
+       * plugins/nle/nlecomposition.c:
+         timeline: Create stable stream IDs
+
+2019-06-06 15:40:57 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/meson.build:
+       * docs/plugins/index.md:
+       * docs/plugins/nle.md:
+       * docs/plugins/sitemap.txt:
        * meson.build:
-         Release 1.16.1
+       * plugins/ges/gessrc.c:
+       * plugins/nle/nleoperation.c:
+         docs: Generate ges plugin doc
+
+2019-06-06 13:51:45 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+       * plugins/ges/gesbasebin.c:
+       * plugins/ges/gesbasebin.h:
+       * plugins/ges/gesdemux.c:
+       * plugins/ges/gessrc.c:
+       * plugins/ges/meson.build:
+         plugins:ges: Factor out a GESBaseBin class
+         And use it in both gesdemux and gessrc
+
+2019-06-06 13:02:33 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Emit no-more-pad as required
+
+2019-06-06 12:46:08 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/nle/nlecomposition.c:
+         nlecomposition: Respect seek seqnum in output EOS/SEGMENT
+         Allowing a proper seek EOS handling with nested compositions
+
+2019-06-06 11:26:45 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+         gesdemux: Properly combine flows
+
+2019-06-06 10:16:50 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * plugins/ges/gesdemux.c:
+       * plugins/ges/gesdemux.h:
+       * plugins/ges/gesplugin.c:
+       * plugins/ges/gessrc.c:
+       * plugins/ges/gessrc.h:
+         plugin: Make use of G_DECLARE
+         And remove useless .h files
+
+2019-06-16 11:09:46 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-xml-formatter.c:
+         xml-formatter: Plug some leaks
+
+2019-06-15 16:44:50 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+         xml-formatter: Refactor the way we handle loading state
+
+2019-06-15 15:11:38 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+         xml-formatter: Cleanup removing all now useless pending fields
+
+2018-06-23 11:26:03 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-base-xml-formatter.c:
+       * ges/ges-layer.c:
+       * ges/ges-uri-asset.c:
+       * tests/check/python/test_clip.py:
+         xml-formatter: Load assets before their proxies
+         Paving the way to removing pending fields to make the code
+         simpler to follow.
+
+2019-06-15 01:33:49 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+         assets: Recurse in the chain of proxies
+         When linking loaded proxies and trying to setup their targets
+
+2019-06-06 09:48:32 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+       * plugins/ges/meson.build:
+         docs: Add gstges plugin
 
 2019-05-26 09:55:03 -0400  Thibault Saunier <tsaunier@igalia.com>
 
          element: Properly handle the fact that pasting can return NULL
          And fix paste annotation
 
+2019-05-31 23:13:48 +0200  Niels De Graef <niels.degraef@barco.com>
+
+       * configure.ac:
+       * meson.build:
+         meson: Bump minimal GLib version to 2.44
+         This means we can use some newer features and get rid of some
+         boilerplate code using the G_DECLARE_* macros.
+         As discussed on IRC, 2.44 is old enough by now to start depending on it.
+
+2019-05-29 23:12:11 +0200  Mathieu Duponchelle <mathieu@centricular.com>
+
+       * plugins/nle/nleobject.c:
+       * plugins/nle/nleoperation.c:
+         doc: remove xml from comments
+
+2019-05-17 19:54:51 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-video-transition.c:
+         video-transition: When using non crossfade effect use 'over' operations
+         For smptealph element to work as expected the following compositing
+         element should mix with the default "over" operator, as described
+         in its documentation.
+
+2019-05-23 18:43:06 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         launcher: Remove duplicated track types option
+
+2019-05-23 18:42:34 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-layer.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline.c:
+         docs: Minor documentation fixes
+
+2019-05-23 17:20:56 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+         overrides: Make sure overrides are in hierarchy order
+         Otherwise method order resolution will not be correct
+
+2019-01-24 19:39:48 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.c:
+         ges: Minor reorganisation of timeline-element.c
+
+2019-01-24 08:43:00 -0300  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-timeline-element.h:
+         ges: Cleanup timeline-element.h indentation
+
+2019-05-01 18:20:42 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-project.c:
+       * ges/ges-project.h:
+         project: Add a signal to notify when a new timeline is starting to load
+
+2019-05-23 16:58:25 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+         tools: Initialize GStreamer before parsin options
+         We need it to be initialized to be able to parse our options
+
+2019-05-01 17:28:26 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tools/ges-launcher.c:
+       * tools/utils.c:
+       * tools/utils.h:
+         tools: Use a proper implementation of get_flags_from_string
+
+2019-05-01 17:26:51 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * tests/check/ges/test-utils.h:
+         tests: Simply include ges-internal.h instead of redefining the same macros
+
+2019-05-16 09:07:03 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/gst_plugins_cache.json:
+       * docs/meson.build:
+         docs: Stop building the doc cache by default
+         And update the cache
+         Fixes https://gitlab.freedesktop.org/gstreamer/gst-docs/issues/36
+
+2019-05-16 15:09:51 +0300  Sebastian Dröge <sebastian@centricular.com>
+
+       * ges/ges-timeline-element.c:
+         timeline-element: Mark edit() as Since: 1.18
+
 2019-05-16 15:06:14 +0300  Sebastian Dröge <sebastian@centricular.com>
 
        * ges/ges-timeline-element.c:
        * ges/ges.c:
          ges: Sprinkle around some Since: 1.16 markers
 
+2019-05-01 13:19:42 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * docs/sitemap.txt:
+       * ges/ges-pipeline.c:
+       * ges/ges-screenshot.c:
+         ges: Deprecate ges_play_sink_convert_frame
+         It has nothing to do in our namespace/API
+
+2019-05-01 12:56:44 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-audio-source.c:
+       * ges/ges-title-source.c:
+       * ges/ges-types.h:
+       * ges/ges-video-source.c:
+         More porting to markdown
+
+2019-05-01 11:53:07 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-title-clip.c:
+         title-clip: Enhance documentation
+
+2018-10-22 08:22:52 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * Makefile.am:
+       * configure.ac:
+       * docs/Makefile.am:
+       * docs/base-classes.md:
+       * docs/gst_plugins_cache.json:
+       * docs/images/layer_track_overview.png:
+       * docs/index.md:
+       * docs/libs/.gitignore:
+       * docs/libs/Makefile.am:
+       * docs/libs/architecture.xml:
+       * docs/libs/ges-docs.sgml:
+       * docs/libs/ges-sections.txt:
+       * docs/libs/ges.types:
+       * docs/libs/meson.build:
+       * docs/low_level.md:
+       * docs/meson.build:
+       * docs/nle-index.md:
+       * docs/nle-sitemap.txt:
+       * docs/nle.md:
+       * docs/sitemap.txt:
+       * ges/meson.build:
+       * meson.build:
+       * meson_options.txt:
+       * plugins/meson.build:
+       * plugins/nle/meson.build:
+         doc: Build documentation with hotdoc
+
+2018-10-22 11:39:03 +0200  Thibault Saunier <tsaunier@igalia.com>
+
+       * ges/ges-asset.c:
+       * ges/ges-pitivi-formatter.h:
+       * ges/ges-project.c:
+       * ges/ges-track-element-asset.c:
+       * ges/ges-track-element.c:
+       * ges/ges-uri-asset.c:
+       * ges/ges.c:
+       * ges/meson.build:
+         docs: Minor fixes
+
+2019-05-07 13:33:09 -0400  Nicolas Dufresne <nicolas.dufresne@collabora.com>
+
+       * docs/libs/ges-sections.txt:
+         doc: ges-track: Add ges_track_set_create_element_for_gap_func
+
 2019-05-05 11:38:28 -0400  Thibault Saunier <tsaunier@igalia.com>
 
        * tools/ges-launcher.c:
          python: Avoid warning about using deprecated methods
          Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/69
 
-2019-05-02 12:35:36 +0100  Tim-Philipp Müller <tim@centricular.com>
+2019-05-02 11:41:10 -0400  Thibault Saunier <tsaunier@igalia.com>
 
-       * .gitlab-ci.yml:
-         ci: use template from 1.16 branch
+       * ges/ges-clip.c:
+       * ges/ges-group.c:
+       * ges/ges-source-clip.c:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element.c:
+         element: Make return value of setters mean something
+         Setters return values should return %FALSE **only** when the value
+         could not be set, not when unchanged or when the subclass handled
+         it itself!
+         This patches makes it so the return value is meaningul by allowing
+         subclasses return anything different than `TRUE` or `FALSE` (convention
+         is -1) to let the subclass now that it took care of everything and
+         no signal should be emited.
+
+2019-05-01 12:09:45 -0400  Thibault Saunier <tsaunier@igalia.com>
+
+       * bindings/python/gi/overrides/GES.py:
+       * ges/ges-clip.c:
+       * ges/ges-container.c:
+       * ges/ges-container.h:
+       * ges/ges-timeline-element.c:
+       * ges/ges-timeline-element.h:
+       * ges/ges-track-element.c:
+         ges: Move `ges_container_edit` to GESTimelineElement
+         Now that the notion of layer has been moved down to #GESTimelineElement
+         (through the new #ges_timeline_element_get_layer_priority method), this
+         method make much more sense directly in the base class.
 
 2019-04-20 01:36:10 +0530  Nirbheek Chauhan <nirbheek@centricular.com>
 
          meson: Generate a pkgconfig file for the GES plugin
          This was missing due to a typo.
 
+2019-04-19 10:41:39 +0100  Tim-Philipp Müller <tim@centricular.com>
+
+       * RELEASE:
+       * configure.ac:
+       * meson.build:
+         Back to development
+
 === release 1.16.0 ===
 
 2019-04-19 00:35:57 +0100  Tim-Philipp Müller <tim@centricular.com>
diff --git a/Makefile.am b/Makefile.am
deleted file mode 100644 (file)
index c19529e..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-DISTCHECK_CONFIGURE_FLAGS=--disable-gtk-doc --with-bash-completion-dir=no
-
-if BUILD_EXAMPLES
-EXAMPLES_SUBDIRS= examples
-else
-EXAMPLES_SUBDIRS=
-endif
-
-SUBDIRS = ges tests tools common m4 pkgconfig docs bindings plugins $(EXAMPLES_SUBDIRS)
-
-DIST_SUBDIRS = $(SUBDIRS)
-
-EXTRA_DIST = \
-       depcomp \
-       AUTHORS COPYING NEWS README RELEASE \
-       ChangeLog autogen.sh gst-editing-services.doap \
-       $(shell find "$(top_srcdir)" -type f -name meson.build ! -path "$(top_srcdir)/$(PACKAGE_TARNAME)-*" ) \
-       meson_options.txt
-
-DISTCLEANFILES = _stdint.h
-
-ACLOCAL_AMFLAGS = -I m4 -I common/m4
-
-include $(top_srcdir)/common/release.mak
-include $(top_srcdir)/common/po.mak
-
-include $(top_srcdir)/common/coverage/lcov.mak
-
-check-valgrind:
-       $(MAKE) -C tests/check check-valgrind
-
-# Test actual high-level functionnality.
-check-integration:
-       $(MAKE) -C tests/check check-integration
-
-if ENABLE_BASH_COMPLETION
-bashcompletiondir = $(BASH_COMPLETION_DIR)
-dist_bashcompletion_DATA = data/completions/ges-launch-1.0
-endif
-
-if HAVE_GST_CHECK
-check-torture:
-       $(MAKE) -C tests/check torture
-build-checks:
-       $(MAKE) -C tests/check build-checks
-else
-check-torture:
-       true
-build-checks:
-       true
-endif
-
-# cruft: plugins that have been merged or moved or renamed
-CRUFT_FILES = \
-       $(top_builddir)/gst-editing-services.spec \
-       $(top_builddir)/common/shave \
-       $(top_builddir)/common/shave-libtool
-
-include $(top_srcdir)/common/cruft.mak
-
-all-local: check-cruft
diff --git a/NEWS b/NEWS
index 98dc512..0e581c3 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,18 +1,23 @@
+GStreamer 1.20 Release Notes
 
+GStreamer 1.20 has not been released yet. It is scheduled for release
+around October/November 2021.
 
-GSTREAMER 1.16 RELEASE NOTES
+1.19.x is the unstable development version that is being developed in
+the git main branch and which will eventually result in 1.20, and 1.19.2
+is the current development release in that series
 
+It is expected that feature freeze will be in early October 2021,
+followed by one or two 1.19.9x pre-releases and the new 1.20 stable
+release around October/November 2021.
 
-GStreamer 1.16.0 was originally released on 19 April 2019.
+1.20 will be backwards-compatible to the stable 1.18, 1.16, 1.14, 1.12,
+1.10, 1.8, 1.6,, 1.4, 1.2 and 1.0 release series.
 
-The latest bug-fix release in the 1.16 series is 1.16.2 and was released
-on 3 December 2019.
-
-See https://gstreamer.freedesktop.org/releases/1.16/ for the latest
+See https://gstreamer.freedesktop.org/releases/1.20/ for the latest
 version of this document.
 
-_Last updated: Tuesday 03 December 2019, 08:00 UTC (log)_
-
+Last updated: Wednesday 22 September 2021, 18:00 UTC (log)
 
 Introduction
 
@@ -23,1149 +28,169 @@ framework!
 As always, this release is again packed with many new features, bug
 fixes and other improvements.
 
-
 Highlights
 
--   GStreamer WebRTC stack gained support for data channels for
-    peer-to-peer communication based on SCTP, BUNDLE support, as well as
-    support for multiple TURN servers.
-
--   AV1 video codec support for Matroska and QuickTime/MP4 containers
-    and more configuration options and supported input formats for the
-    AOMedia AV1 encoder
-
--   Support for Closed Captions and other Ancillary Data in video
-
--   Support for planar (non-interleaved) raw audio
-
--   GstVideoAggregator, compositor and OpenGL mixer elements are now in
-    -base
-
--   New alternate fields interlace mode where each buffer carries a
-    single field
-
--   WebM and Matroska ContentEncryption support in the Matroska demuxer
-
--   new WebKit WPE-based web browser source element
-
--   Video4Linux: HEVC encoding and decoding, JPEG encoding, and improved
-    dmabuf import/export
-
--   Hardware-accelerated Nvidia video decoder gained support for VP8/VP9
-    decoding, whilst the encoder gained support for H.265/HEVC encoding.
-
--   Many improvements to the Intel Media SDK based hardware-accelerated
-    video decoder and encoder plugin (msdk): dmabuf import/export for
-    zero-copy integration with other components; VP9 decoding; 10-bit
-    HEVC encoding; video post-processing (vpp) support including
-    deinterlacing; and the video decoder now handles dynamic resolution
-    changes.
-
--   The ASS/SSA subtitle overlay renderer can now handle multiple
-    subtitles that overlap in time and will show them on screen
-    simultaneously
-
--   The Meson build is now feature-complete (*) and it is now the
-    recommended build system on all platforms. The Autotools build is
-    scheduled to be removed in the next cycle.
-
--   The GStreamer Rust bindings and Rust plugins module are now
-    officially part of upstream GStreamer.
-
--   The GStreamer Editing Services gained a gesdemux element that allows
-    directly playing back serialized edit list with playbin or
-    (uri)decodebin
-
--   Many performance improvements
-
+-   this section will be completed in due course
 
 Major new features and changes
 
-Noteworthy new API
-
--   GstAggregator has a new "min-upstream-latency" property that forces
-    a minimum aggregate latency for the input branches of an aggregator.
-    This is useful for dynamic pipelines where branches with a higher
-    latency might be added later after the pipeline is already up and
-    running and where a change in the latency would be disruptive. This
-    only applies to the case where at least one of the input branches is
-    live though, it won’t force the aggregator into live mode in the
-    absence of any live inputs.
-
--   GstBaseSink gained a "processing-deadline" property and
-    setter/getter API to configure a processing deadline for live
-    pipelines. The processing deadline is the acceptable amount of time
-    to process the media in a live pipeline before it reaches the sink.
-    This is on top of the systemic latency that is normally reported by
-    the latency query. This defaults to 20ms and should make pipelines
-    such as v4l2src ! xvimagesink not claim that all frames are late in
-    the QoS events. Ideally, this should replace the "max-lateness"
-    property for most applications.
-
--   RTCP Extended Reports (XR) parsing according to RFC 3611:
-    Loss/Duplicate RLE, Packet Receipt Times, Receiver Reference Time,
-    Delay since the last Receiver (DLRR), Statistics Summary, and VoIP
-    Metrics reports. This only provides the ability to parse such
-    packets, generation of XR packets is not supported yet and XR
-    packets are not automatically parsed by rtpbin / rtpsession but must
-    be actively handled by the application.
-
--   a new mode for interlaced video was added where each buffer carries
-    a single field of interlaced video, with buffer flags indicating
-    whether the field is the top field or bottom field. Top and bottom
-    fields are expected to alternate in this mode. Caps for this
-    interlace mode must also carry a format:Interlaced caps feature to
-    ensure backwards compatibility.
-
--   The video library has gained support for three new raw pixel
-    formats:
-
-    -   Y410: packed 4:4:4 YUV, 10 bits per channel
-    -   Y210: packed 4:2:2 YUV, 10 bits per channel
-    -   NV12_10LE40: fully-packed 10-bit variant of NV12_10LE32,
-        i.e. without the padding bits
-
--   GstRTPSourceMeta is a new meta that can be used to transport
-    information about the origin of depayloaded or decoded RTP buffers,
-    e.g. when mixing audio from multiple sources into a single stream. A
-    new "source-info" property on the RTP depayloader base class
-    determines whether depayloaders should put this meta on outgoing
-    buffers. Similarly, the same property on RTP payloaders determines
-    whether they should use the information from this meta to construct
-    the CSRCs list on outgoing RTP buffers.
-
--   gst_sdp_message_from_text() is a convenience constructor to parse
-    SDPs from a string which is particularly useful for language
-    bindings.
-
-Support for Planar (Non-Interleaved) Raw Audio
-
-Raw audio samples are usually passed around in interleaved form in
-GStreamer, which means that if there are multiple audio channels the
-samples for each channel are interleaved in memory,
-e.g. |LEFT|RIGHT|LEFT|RIGHT|LEFT|RIGHT| for stereo audio. A
-non-interleaved or planar arrangement in memory would look like
-|LEFT|LEFT|LEFT|RIGHT|RIGHT|RIGHT| instead, possibly with
-|LEFT|LEFT|LEFT| and |RIGHT|RIGHT|RIGHT| residing in separate memory
-chunks or separated by some padding.
-
-GStreamer has always had signalling for non-interleaved audio since
-version 1.0, but it was never actually properly implemented in any
-elements. audioconvert would advertise support for it, but wasn’t
-actually able to handle it correctly.
-
-With this release we now have full support for non-interleaved audio as
-well, which means more efficient integration with external APIs that
-handle audio this way, but also more efficient processing of certain
-operations like interleaving multiple 1-channel streams into a
-multi-channel stream which can be done without memory copies now.
-
-New API to support this has been added to the GStreamer Audio support
-library: There is now a new GstAudioMeta which describes how data is
-laid out inside the buffer, and buffers with non-interleaved audio must
-always carry this meta. To access the non-interleaved audio samples you
-must map such buffers with gst_audio_buffer_map() which works much like
-gst_buffer_map() or gst_video_frame_map() in that it will populate a
-little GstAudioBuffer helper structure passed to it with the number of
-samples, the number of planes and pointers to the start of each plane in
-memory. This function can also be used to map interleaved audio buffers
-in which case there will be only one plane of interleaved samples.
-
-Of course support for this has also been implemented in the various
-audio helper and conversion APIs, base classes, and in elements such as
-audioconvert, audioresample, audiotestsrc, audiorate.
-
-Support for Closed Captions and Other Ancillary Data in Video
-
-The video support library has gained support for detecting and
-extracting Ancillary Data from videos as per the SMPTE S291M
-specification, including:
-
--   a VBI (Vertical Blanking Interval) parser that can detect and
-    extract Ancillary Data from Vertical Blanking Interval lines of
-    component signals. This is currently supported for videos in v210
-    and UYVY format.
-
--   a new GstMeta for closed captions: GstVideoCaptionMeta. This
-    supports the two types of closed captions, CEA-608 and CEA-708,
-    along with the four different ways they can be transported (other
-    systems are a superset of those).
-
--   a VBI (Vertical Blanking Interval) encoder for writing ancillary
-    data to the Vertical Blanking Interval lines of component signals.
-
-The new closedcaption plugin in gst-plugins-bad then makes use of all
-this new infrastructure and provides the following elements:
-
--   cccombiner: a closed caption combiner that takes a closed captions
-    stream and another stream and adds the closed captions as
-    GstVideoCaptionMeta to the buffers of the other stream.
-
--   ccextractor: a closed caption extractor which will take
-    GstVideoCaptionMeta from input buffers and output them as a separate
-    closed captions stream.
-
--   ccconverter: a closed caption converter that can convert between
-    different formats
-
--   line21encoder, line21decoder: inject/extract line21 closed captions
-    to/from SD video streams
-
--   cc708overlay: decodes CEA 608/708 captions and overlays them on
-    video
-
-Additionally, the following elements have also gained Closed Caption
-support:
-
--   qtdemux and qtmux support CEA 608/708 Closed Caption tracks
-
--   mpegvideoparse, h264parse extracts Closed Captions from MPEG-2/H.264
-    video streams
-
--   avviddec, avvidenc, x264enc got support for extracting/injecting
-    Closed Captions
-
--   decklinkvideosink can output closed captions and decklinkvideosrc
-    can extract closed captions
-
--   playbin and playbin3 learned how to autoplug CEA 608/708 CC overlay
-    elements
-
--   the externally maintained ajavideosrc element for AJA capture cards
-    has support for extracting closed captions
-
-The rsclosedcaption plugin in the Rust plugins collection includes a
-MacCaption (MCC) file parser and encoder.
-
-New Elements
-
--   overlaycomposition: New element that allows applications to draw
-    GstVideoOverlayCompositions on a stream. The element will emit the
-    "draw" signal for each video buffer, and the application then
-    generates an overlay for that frame (or not). This is much more
-    performant than e.g. cairooverlay for many use cases, e.g. because
-    pixel format conversions can be avoided or the blitting of the
-    overlay can be delegated to downstream elements (such as
-    gloverlaycompositor). It’s particularly useful for cases where only
-    a small section of the video frame should be drawn on.
-
--   gloverlaycompositor: New OpenGL-based compositor element that
-    flattens any overlays from GstVideoOverlayCompositionMetas into the
-    video stream. This element is also always part of glimagesink.
-
--   glalpha: New element that adds an alpha channel to a video stream.
-    The values of the alpha channel can either be set to a constant or
-    can be dynamically calculated via chroma keying. It is similar to
-    the existing alpha element but based on OpenGL. Calculations are
-    done in floating point so results may not be identical to the output
-    of the existing alpha element.
-
--   rtpfunnel funnels together RTP streams into a single session. Use
-    cases include multiplexing and bundle. webrtcbin uses it to
-    implement BUNDLE support.
-
--   testsrcbin is a source element that provides an audio and/or video
-    stream and also announces them using the recently-introduced
-    GstStream API. This is useful for testing elements such as playbin3
-    or uridecodebin3 etc.
+Noteworthy new features and API
 
--   New closed caption elements: cccombiner, ccextractor, ccconverter,
-    line21encoder, line21decoder and cc708overlay (see above)
+-   this section will be filled in in due course
 
--   wpesrc: new source element acting as a Web Browser based on WebKit
-    WPE
+New elements
 
--   Two new OpenCV-based elements: cameracalibrate and cameraundistort
-    that can communicate to figure out distortion correction parameters
-    for a camera and correct for the distortion.
-
--   New sctp plugin based on usrsctp with sctpenc and sctpdec elements.
-    These elements are used inside webrtcbin for implementing data
-    channels.
+-   this section will be filled in in due course
 
 New element features and additions
 
--   playbin3, playbin and playsink have gained a new "text-offset"
-    property to adjust the positioning of the selected subtitle stream
-    vis-a-vis the audio and video streams. This uses subtitleoverlay’s
-    new "subtitle-ts-offset" property. GstPlayer has gained matching API
-    for this, namely gst_player_get_text_video_offset().
-
--   playbin3 buffering improvements: in network playback scenarios there
-    may be multiple inputs to decodebin3, and buffering will be done
-    before decodebin3 using queue2 or downloadbuffer elements inside
-    urisourcebin. Since this is before any parsers or demuxers there may
-    not be any bitrate information available for the various streams, so
-    it was difficult to configure the buffering there smartly within
-    global constraints. This was improved now: The queue2 elements
-    inside urisourcebin will now use the new bitrate query to figure out
-    a bitrate estimate for the stream if no bitrate was provided by
-    upstream, and urisourcebin will use the bitrates of the individual
-    queues to distribute the globally-set "buffer-size" budget in bytes
-    to the various queues. urisourcebin also gained "low-watermark" and
-    "high-watermark" properties which will be proxied to the internal
-    queues, as well as a read-only "statistics" property which allows
-    querying of the minimum/maximum/average byte and time levels of the
-    queues inside the urisourcebin in question.
-
--   splitmuxsink has gained a couple of new features:
-
-    -   new "async-finalize" mode: This mode is useful for muxers or
-        outputs that can take a long time to finalize a file. Instead of
-        blocking the whole upstream pipeline while the muxer is doing
-        its stuff, we can unlink it and spawn a new muxer + sink
-        combination to continue running normally. This requires us to
-        receive the muxer and sink (if needed) as factories via the new
-        "muxer-factory" and "sink-factory" properties, optionally
-        accompanied by their respective properties structures (set via
-        the new "muxer-properties" and "sink-properties" properties).
-        There are also new "muxer-added" and "sink-added" signals in
-        case custom code has to be called for them to configure them.
-
-    -   "split-at-running-time" action signal: When called by the user,
-        this action signal ends the current file (and starts a new one)
-        as soon as the given running time is reached. If called multiple
-        times, running times are queued up and processed in the order
-        they were given.
-
-    -   "split-after" action signal to finish outputting the current GOP
-        to the current file and then start a new file as soon as the GOP
-        is finished and a new GOP is opened (unlike the existing
-        "split-now" which immediately finishes the current file and
-        writes the current GOP into the next newly-started file).
-
-    -   "reset-muxer" property: when unset, the muxer is reset using
-        flush events instead of setting its state to NULL and back. This
-        means the muxer can keep state across resets, e.g. mpegtsmux
-        will keep the continuity counter continuous across segments as
-        required by hlssink2.
-
--   qtdemux gained PIFF track encryption box support in addition to the
-    already-existing PIFF sample encryption support, and also allows
-    applications to select which encryption system to use via a
-    "drm-preferred-decryption-system-id" context in case there are
-    multiple options.
-
--   qtmux: the "start-gap-threshold" property determines now whether an
-    edit list will be created to account for small gaps or offsets at
-    the beginning of a stream in case the start timestamps of tracks
-    don’t line up perfectly. Previously the threshold was hard-coded to
-    1% of the (video) frame duration, now it is 0 by default (so edit
-    list will be created even for small differences), but fully
-    configurable.
-
--   rtpjitterbuffer has improved end-of-stream handling
-
--   rtpmp4vpay will be preferred over rtpmp4gpay for MPEG-4 video in
-    autoplugging scenarios now
-
--   rtspsrc now allows applications to send RTSP SET_PARAMETER and
-    GET_PARAMETER requests using action signals.
-
--   rtspsrc has a small (100ms) configurable teardown delay by default
-    to try and make sure an RTSP TEARDOWN request gets sent out when the
-    source element shuts down. This will block the downward PAUSED to
-    READY state change for a short time, but can be disabled where it’s
-    a problem. Some servers only allow a limited number of concurrent
-    clients, so if no proper TEARDOWN is sent new clients may have
-    problems connecting to the server for a while.
-
--   souphttpsrc behaves better with low bitrate streams now. Before it
-    would increase the read block size too quickly which could lead to
-    it not reading any data from the socket for a very long time with
-    low bitrate streams that are output live downstream. This could lead
-    to servers kicking off the client.
-
--   filesink: do internal buffering to avoid performance regression with
-    small writes since we bypass libc buffering by using writev()
-    instead of fwrite()
-
--   identity: add "eos-after" property and fix "error-after" property
-    when the element is reused
-
--   input-selector: lets context queries pass through, so that
-    e.g. upstream OpenGL elements can use contexts and displays
-    advertised by downstream elements
-
--   queue2: avoid ping-pong between 0% and 100% buffering messages if
-    upstream is pushing buffers larger than one of its limits, plus
-    performance optimisations
-
--   opusdec: new "phase-inversion" property to control phase inversion.
-    When enabled, this will slightly increase stereo quality, but
-    produces a stream that when downmixed to mono will suffer audio
-    distortions.
-
--   The x265enc HEVC encoder also exposes a "key-int-max" property to
-    configure the maximum allowed GOP size now.
-
--   decklinkvideosink has seen stability improvements for long-running
-    pipelines (potential crash due to overflow of leaked clock refcount)
-    and clock-slaving improvements when performing flushing seeks
-    (causing stalls in the output timeline), pausing and/or buffering.
-
--   srtpdec, srtpenc: add support for MKIs which allow multiple keys to
-    be used with a single SRTP stream
-
--   srtpdec, srtpenc: add support for AES-GCM and also add support for
-    it in gst-rtsp-server and rtspsrc.
-
--   The srt Secure Reliable Transport plugin has integrated server and
-    client elements srt{client,server}{src,sink} into one (srtsrc and
-    srtsink), since SRT connection mode can be changed by uri
-    parameters.
-
--   h264parse and h265parse will handle SEI recovery point messages and
-    mark recovery points as keyframes as well (in addition to IDR
-    frames)
-
--   webrtcbin: "add-turn-server" action signal to pass multiple ICE
-    relays (TURN servers).
-
--   The removesilence element has received various new features and
-    properties, such as a "threshold" property, detecting silence only
-    after minimum silence time/buffers, a "silent" property to control
-    bus message notifications as well as a "squash" property.
-
--   AOMedia AV1 decoder gained support for 10/12bit decoding whilst the
-    AV1 encoder supports more image formats and subsamplings now and
-    acquired support for rate control and profile related configuration.
-
--   The Fraunhofer fdkaac plugin can now be built against the 2.0.0
-    version API and has improved multichannel support
-
--   kmssink now supports unpadded 24-bit RGB and can configure mode
-    setting from video info, which enables display of multi-planar
-    formats such as I420 or NV12 with modesetting. It has also gained a
-    number of new properties: The "restore-crtc" property does what it
-    says on the tin and is enabled by default. "plane-properties" and
-    "connector-properties" can be used to pass custom properties to the
-    DRM.
-
--   waylandsink has a "fullscreen" property now and supports the
-    XDG-Shell protocol.
-
--   decklinkvideosink, decklinkvideosrc support selecting between
-    half/full duplex
-
--   The vulkan plugin gained support for macOS and iOS via MoltenVK in
-    addition to the existing support for X11 and Wayland
-
--   imagefreeze has a new num-buffers property to limit the number of
-    buffers that are produced and to send an EOS event afterwards
-
--   webrtcbin has a new, introspectable get-transceiver signal in
-    addition to the old get-transceivers signal that couldn’t be used
-    from bindings
-
--   Support for per-element latency information was added to the latency
-    tracer
+-   this section will be filled in in due course
 
 Plugin and library moves
 
--   The stereo element was moved from -bad into the existing audiofx
-    plugin in -good. If you get duplicate type registration warnings
-    when upgrading, check that you don’t have a stale stereoplugin lying
-    about somewhere.
-
-GstVideoAggregator, compositor, and OpenGL mixer elements moved from -bad to -base
-
-GstVideoAggregator is a new base class for raw video mixers and muxers
-and is based on GstAggregator. It provides defined-latency mixing of raw
-video inputs and ensures that the pipeline won’t stall even if one of
-the input streams stops producing data.
-
-As part of the move to stabilise the API there were some last-minute API
-changes and clean-ups, but those should mostly affect internal elements.
-Most notably, the "ignore-eos" pad property was renamed to
-"repeat-after-eos" and the conversion code was moved to a
-GstVideoAggregatorConvertPad subclass to avoid code duplication, make
-things less awkward for subclasses like the OpenGL-based video mixer,
-and make the API more consistent with the audio aggregator API.
-
-It is used by the compositor element, which is a replacement for
-‘videomixer’ which did not handle live inputs very well. compositor
-should behave much better in that respect and generally behave as one
-would expected in most scenarios.
-
-The compositor element has gained support for per-pad blending mode
-operators (SOURCE, OVER, ADD) which determines what operator to use for
-blending this pad over the previous ones. This can be used to implement
-crossfading and the available operators can be extended in the future as
-needed.
-
-A number of OpenGL-based video mixer elements (glvideomixer, glmixerbin,
-glvideomixerelement, glstereomix, glmosaic) which are built on top of
-GstVideoAggregator have also been moved from -bad to -base now. These
-elements have been merged into the existing OpenGL plugin, so if you get
-duplicate type registration warnings when upgrading, check that you
-don’t have a stale openglmixers plugin lying about somewhere.
+-   this section will be filled in in due course
 
-Plugin removals
+-   There were no plugin moves or library moves in this cycle.
 
-The following plugins have been removed from gst-plugins-bad:
-
--   The experimental daala plugin has been removed, since it’s not so
-    useful now that all effort is focused on AV1 instead, and it had to
-    be enabled explicitly with --enable-experimental anyway.
-
--   The spc plugin has been removed. It has been replaced by the gme
-    plugin.
+Plugin removals
 
--   The acmmp3dec and acmenc plugins for Windows have been removed. ACM
-    is an ancient legacy API and there was no point in keeping the
-    plugins around for a licensed MP3 decoder now that the MP3 patents
-    have expired and we have a decoder in -good. We also didn’t ship
-    these in our cerbero-built Windows packages, so it’s unlikely that
-    they’ll be missed.
+The following elements or plugins have been removed:
 
+-   this section will be filled in in due course
 
 Miscellaneous API additions
 
--   GstBitwriter: new generic bit writer API to complement the existing
-    bit reader
-
--   gst_buffer_new_wrapped_bytes() creates a wrap buffer from a GBytes
-
--   gst_caps_set_features_simple() sets a caps feature on all the
-    structures of a GstCaps
-
--   New GST_QUERY_BITRATE query: This allows determining from downstream
-    what the expected bitrate of a stream may be which is useful in
-    queue2 for setting time based limits when upstream does not provide
-    timing information. tsdemux, qtdemux and matroskademux have basic
-    support for this query on their sink pads.
-
--   elements: there is a new “Hardware” class specifier. Elements
-    interacting with hardware devices should specify this classifier in
-    their element factory class metadata. This is useful to advertise as
-    one might need to put such elements into READY state to test if the
-    hardware is present in the system for example.
-
--   protection: Add a new definition for unspecified system protection,
-    GST_PROTECTION_UNSPECIFIED_SYSTEM_ID
-
--   take functions for various mini objects that didn’t have them yet:
-    gst_query_take(), gst_message_take(), gst_tag_list_take(),
-    gst_buffer_list_take(). Unlike the various _replace() functions
-    _take() does not increase the reference count but takes ownership of
-    the mini object passed.
-
--   clear functions for various mini object types and GstObject which
-    unrefs the object or mini object (if non-NULL) and sets the variable
-    pointed to to NULL: gst_clear_structure(), gst_clear_tag_list(),
-    gst_clear_query(), gst_clear_message(), gst_clear_event(),
-    gst_clear_caps(), gst_clear_buffer_list(), gst_clear_buffer(),
-    gst_clear_mini_object(), gst_clear_object()
-
--   miniobject: new API gst_mini_object_add_parent() and
-    gst_mini_object_remove_parent() to set parent pointers on mini
-    objects to ensure correct writability: Every container of
-    miniobjects now needs to store itself as parent in the child object,
-    and remove itself again later. A mini object is then only writable
-    if there is at most one parent, that parent is writable itself, and
-    the reference count of the mini object is 1. GstBuffer (for
-    memories), GstBufferList (for buffers), GstSample (for caps, buffer,
-    bufferlist), and GstVideoOverlayComposition were updated
-    accordingly. Without this it was possible to have e.g. a buffer list
-    with a refcount of 2 used in two places at once that both modify the
-    same buffer with refcount 1 at the same time wrongly thinking it is
-    writable even though it’s really not.
-
--   poll: add API to watch for POLLPRI and stop treating POLLPRI as a
-    read. This is useful to wait for video4linux events which are
-    signalled via POLLPRI.
-
--   sample: new API to update the contents of a GstSample and make it
-    writable: gst_sample_set_buffer(), gst_sample_set_caps(),
-    gst_sample_set_segment(), gst_sample_set_info(), plus
-    gst_sample_is_writable() and gst_sample_make_writable(). This makes
-    it possible to reuse a sample object and avoid unnecessary memory
-    allocations, for example in appsink.
-
--   ClockIDs now keep a weak reference to underlying clock to avoid
-    crashes in basesink in corner cases where a clock goes away while
-    the ClockID is still in use, plus some new API
-    (gst_clock_id_get_clock(), gst_clock_id_uses_clock()) to check the
-    clock a ClockID is linked to.
-
--   The GstCheck unit test library gained a
-    fail_unless_equals_clocktime() convenience macro as well as some new
-    GstHarness API for for proposing meta APIs from the allocation
-    query: gst_harness_add_propose_allocation_meta(). ASSERT_CRITICAL()
-    checks in unit tests are now skipped if GStreamer was compiled with
-    GST_DISABLE_GLIB_CHECKS.
-
--   gst_audio_buffer_truncate() convenience function to truncate a raw
-    audio buffer
-
--   GstDiscoverer has support for caching the results of discovery in
-    the default cache directory. This can be enabled with the use-cache
-    property and is disabled by default.
-
--   GstMeta that are attached to GstBuffers are now always stored in the
-    order in which they were added.
-
--   Additional support for signalling ONVIF specific features were
-    added: the SEEK event can store a trickmode-interval now and support
-    for the Rate-Control and Frames RTSP headers was added to the RTSP
-    library.
-
-
-Miscellaneous performance and memory optimisations
-
-As always there have been many performance and memory usage improvements
-across all components and modules. Some of them (such as dmabuf
-import/export) have already been mentioned elsewhere so won’t be
-repeated here.
-
-The following list is only a small snapshot of some of the more
-interesting optimisations that haven’t been mentioned in other contexts
-yet:
-
--   The GstVideoEncoder and GstVideoDecoder base classes now release the
-    STREAM_LOCK when pushing out buffers, which means (multi-threaded)
-    encoders and decoders can now receive and continue to process input
-    buffers whilst waiting for downstream elements in the pipeline to
-    process the buffer that was pushed out. This increases throughput
-    and reduces processing latency, also and especially for
-    hardware-accelerated encoder/decoder elements.
-
--   GstQueueArray has seen a few API additions
-    (gst_queue_array_peek_nth(), gst_queue_array_set_clear_func(),
-    gst_queue_array_clear()) so that it can be used in other places like
-    GstAdapter instead of a GList, which reduces allocations and
-    improves performance.
-
--   appsink now reuses the sample object in pull_sample() if possible
-
--   rtpsession only starts the RTCP thread when it’s actually needed now
-
--   udpsrc uses a buffer pool now and the GstUdpSrc object structure was
-    optimised for better cache performance
-
-GstPlayer
-
--   API was added to fine-tune the synchronisation offset between
-    subtitles and video
-
-
-Miscellaneous changes
-
--   As a result of moving to newer FFmpeg APIs, encoder and decoder
-    elements exposed by the GStreamer FFmpeg wrapper plugin (gst-libav)
-    may have seen possibly incompatible changes to property names and/or
-    types, and not all properties exposed might be functional. We are
-    still reviewing the new properties and aim to minimise breaking
-    changes at least for the most commonly-used properties, so please
-    report any issues you run into!
-
-OpenGL integration
-
--   The OpenGL mixer elements have been moved from -bad to
-    gst-plugins-base (see above)
-
--   The Mesa GBM backend now supports headless mode
-
--   gloverlaycompositor: New OpenGL-based compositor element that
-    flattens any overlays from GstVideoOverlayCompositionMetas into the
-    video stream.
+-   this section will be filled in in due course
 
--   glalpha: New element that adds an alpha channel to a video stream.
-    The values of the alpha channel can either be set to a constant or
-    can be dynamically calculated via chroma keying. It is similar to
-    the existing alpha element but based on OpenGL. Calculations are
-    done in floating point so results may not be identical to the output
-    of the existing alpha element.
+Miscellaneous performance, latency and memory optimisations
 
--   glupload: Implement direct dmabuf uploader, the idea being that some
-    GPUs (like the Vivante series) can actually perform the YUV->RGB
-    conversion internally, so no custom conversion shaders are needed.
-    To make use of this feature, we need an additional uploader that can
-    import DMABUF FDs and also directly pass the pixel format, relying
-    on the GPU to do the conversion.
+-   this section will be filled in in due course
 
--   The OpenGL library no longer restores the OpenGL viewport. This is a
-    performance optimization to not require performing multiple
-    expensive glGet*() function calls per frame. This affects any
-    application or plugin use of the following functions and objects:
-    -   glcolorconvert library object (not the element)
-    -   glviewconvert library object (not the element)
-    -   gst_gl_framebuffer_draw_to_texture()
-    -   custom GstGLWindow implementations
+Miscellaneous other changes and enhancements
 
+-   this section will be filled in in due course
 
 Tracing framework and debugging improvements
 
--   There is now a GDB PRETTY PRINTER FOR VARIOUS GSTREAMER TYPES: For
-    GstObject pointers the type and name is added, e.g.
-    0x5555557e4110 [GstDecodeBin|decodebin0]. For GstMiniObject pointers
-    the object type is added, e.g. 0x7fffe001fc50 [GstBuffer]. For
-    GstClockTime and GstClockTimeDiff the time is also printed in human
-    readable form, e.g. 150116219955 [+0:02:30.116219955].
-
--   GDB EXTENSION WITH TWO CUSTOM GDB COMMANDS gst-dot AND gst-print:
-
-    -   gst-dot creates dot files that a very close to what
-        GST_DEBUG_BIN_TO_DOT_FILE() produces, but object properties and
-        buffer contents such as codec-data in caps are not available.
-
-    -   gst-print produces high-level information about a GStreamer
-        object. This is currently limited to pads for GstElements and
-        events for the pads. The output may look like this:
-
--   gst_structure_to_string() now serialises the actual value of
-    pointers when serialising GstStructures instead of claiming they’re
-    NULL. This makes debug logging in various places less confusing,
-    because it’s clear now that structure fields actually hold valid
-    objects. Such object pointer values will never be deserialised
-    however.
-
+-   this section will be filled in in due course
 
 Tools
 
--   gst-inspect-1.0 has coloured output now and will automatically use a
-    pager if the output does not fit on a page. This only works in a
-    UNIX environment and if the output is not piped, and on Windows 10
-    build 16257 or newer. If you don’t like the colours you can disable
-    them by setting the GST_INSPECT_NO_COLORS=1 environment variable or
-    passing the --no-color command line option.
-
+-   this section will be filled in in due course
 
 GStreamer RTSP server
 
--   Improved backlog handling when using TCP interleaved for data
-    transport. Before there was a fixed maximum size for backlog
-    messages, which was prone to deadlocks and made it difficult to
-    control memory usage with the watch backlog. The RTSP server now
-    limits queued TCP data messages to one per stream, moving queuing of
-    the data into the pipeline and leaving the RTSP connection
-    responsive to RTSP messages in both directions, preventing all those
-    problems.
-
--   Initial ULP Forward Error Correction support in rtspclientsink and
-    for RECORD mode in the server.
-
--   API to explicitly enable retransmission requests (RTX)
-
--   Lots of multicast-related fixes
-
--   rtsp-auth: Add support for parsing .htdigest files
-
+-   this section will be filled in in due course
 
 GStreamer VAAPI
 
--   Support Wayland’s display for context sharing, so the application
-    can pass its own wl_display in order to be used for the VAAPI
-    display creation.
-
--   A lot of work to support new Intel hardware using media-driver as VA
-    backend.
-
--   For non-x86 devices, VAAPI display can instantiate, through DRM,
-    with no PCI bus. This enables the usage of libva-v4l2-request
-    driver.
-
--   Added support for XDG-shell protocol as wl_shell replacement which
-    is currently deprecated. This change add as dependency
-    wayland-protocol.
-
--   GstVaapiFilter, GstVaapiWindow, and GstVaapiDecoder classes now
-    inherit from GstObject, gaining all the GStreamer’s instrumentation
-    support.
-
--   The metadata now specifies the plugin as Hardware class.
-
--   H264 decoder is more stable with problematic streams.
-
--   In H265 decoder added support for profiles main-422-10 (P010_10LE),
-    main-444 (AYUV) and main-444-10 (Y410)
-
--   JPEG decoder handles dynamic resolution changes.
-
--   More specification adherence in H264 and H265 encoders.
-
+-   this section will be filled in in due course
 
 GStreamer OMX
 
--   Add support of NV16 format to video encoders input.
-
--   Video decoders now handle the ALLOCATION query to tell upstream
-    about the number of buffers they require. Video encoders will also
-    use this query to adjust their number of allocated buffers
-    preventing starvation when using dynamic buffer mode.
-
--   The OMX_PERFORMANCE debug category has been renamed to OMX_API_TRACE
-    and can now be used to track a widder variety of interactions
-    between OMX and GStreamer.
-
--   Video encoders will now detect frame rate only changes and will
-    inform OMX about it rather than doing a full format reset.
-
--   Various Zynq UltraScale+ specific improvements:
-    -   Video encoders are now able to import dmabuf from upstream.
-    -   Support for HEVC range extension profiles and more AVC profiles.
-    -   We can now request video encoders to generate an IDR using the
-        force key unit event.
-
+-   this section will be filled in in due course
 
 GStreamer Editing Services and NLE
 
--   Added a gesdemux element, it is an auto pluggable element that
-    allows decoding edit list like files supported by GES
-
--   Added gessrc which wraps a GESTimeline as a standard source element
-    (implementing the ges protocol handler)
-
--   Added basic support for videorate::rate property potentially
-    allowing changing playback speed
-
--   Layer priority is now fully automatic and they should be moved with
-    the new ges_timeline_move_layer method, ges_layer_set_priority is
-    now deprecated.
-
--   Added a ges_timeline_element_get_layer_priority so we can simply get
-    all information about GESTimelineElement position in the timeline
-
--   GESVideoSource now auto orientates the images if it is defined in a
-    meta (overridable).
-
--   Added some PyGObject overrides to make the API more pythonic
-
--   The threading model has been made more explicit with safe guard to
-    make sure not thread safe APIs are not used from the wrong threads.
-    It is also now possible to properly handle in what thread the API
-    should be used.
-
--   Optimized GESClip and GESTrackElement creation
-
--   Added a way to compile out the old, unused and deprecated
-    GESPitiviFormatter
-
--   Re implemented the timeline editing API making it faster and making
-    the code much more maintainable
-
--   Simplified usage of nlecomposition outside GES by removing quirks in
-    it API usage and removing the need to treat it specially from an
-    application perspective.
-
--   ges-launch-1.0:
-
-    -   Added support to add titles to the timeline
-    -   Enhance the help auto generating it from the code
-
--   Deprecate ges_timeline_load_from_uri as loading the timeline should
-    be done through a project now
-
--   MANY leaks have been plugged and the unit testsuite is now “leak
-    free”
-
+-   this section will be filled in in due course
 
 GStreamer validate
 
--   Added an action type to verify the checksum of the sink last-sample
-
--   Added an include keyword to validate scenarios
-
--   Added the notion of variable in scenarios, with the set-vars keyword
-
--   Started adding support for “performance” like tests by allowing to
-    define the number of dropped buffers or the minimum buffer frequency
-    on a specific pad
-
--   Added a validateflow plugin which allows defining the data flow to
-    be seen on a particular pad and verifying that following runs match
-    the expectations
-
--   Added support for appsrc based test definition so we can instrument
-    the data pushed into the pipeline from scenarios
-
--   Added a mockdecryptor allowing adding tests with on encrypted files,
-    the element will potentially be instrumented with a validate
-    scenario
-
--   gst-validate-launcher:
-
-    -   Cleaned up output
-
-    -   Changed the default for “muting” tests as user doesn’t expect
-        hundreds of windows to show up when running the testsuite
-
-    -   Fixed the outputted xunit files to be compatible with GitLab
-
-    -   Added support to run tests on media files in push mode (using
-        pushfile://)
-
-    -   Added support for running inside gst-build
-
-    -   Added support for running ssim tests on rendered files
-
-    -   Added a way to simply define tests on pipelines through a simple
-        .json file
-
-    -   Added a python app to easily run python testsuite reusing all
-        the launcher features
-
-    -   Added flatpak knowledge so we can print backtrace even when
-        running from within flatpak
-
-    -   Added a way to automatically generated “known issues”
-        suppressions lines
-
-    -   Added a way to rerun tests to check if they are flaky and added
-        a way to tolerate tests known to be flaky
-
-    -   Add a way to output html log files
-
+-   this section will be filled in in due course
 
 GStreamer Python Bindings
 
--   add binding for gst_pad_set_caps()
-
--   pygobject dependency requirement was bumped to >= 3.8
-
--   new audiotestsrc, audioplot, and mixer plugin examples, and a
-    dynamic pipeline example
-
+-   this section will be filled in in due course
 
 GStreamer C# Bindings
 
--   bindings for the GstWebRTC library
-
-
-GStreamer Rust Bindings
-
-The GStreamer Rust bindings are now officially part of the GStreamer
-project and are also maintained in the GStreamer GitLab.
-
-The releases will generally not be synchronized with the releases of
-other GStreamer parts due to dependencies on other projects.
-
-Also unlike the other GStreamer libraries, the bindings will not commit
-to full API stability but instead will follow the approach that is
-generally taken by Rust projects, e.g.:
-
-1)  0.12.X will be completely API compatible with all other 0.12.Y
-    versions.
-2)  0.12.X+1 will contain bugfixes and compatible new feature additions.
-3)  0.13.0 will _not_ be backwards compatible with 0.12.X but projects
-    will be able to stay at 0.12.X without any problems as long as they
-    don’t need newer features.
-
-The current stable release is 0.12.2 and the next release series will be
-0.13, probably around March 2019.
-
-At this point the bindings cover most of GStreamer core (except for most
-notably GstAllocator and GstMemory), and most parts of the app, audio,
-base, check, editing-services, gl, net. pbutils, player, rtsp,
-rtsp-server, sdp, video and webrtc libraries.
-
-Also included is support for creating subclasses of the following types
-and writing GStreamer plugins:
-
--   gst::Element
--   gst::Bin and gst::Pipeline
--   gst::URIHandler and gst::ChildProxy
--   gst::Pad, gst::GhostPad
--   gst_base::Aggregator and gst_base::AggregatorPad
--   gst_base::BaseSrc and gst_base::BaseSink
--   gst_base::BaseTransform
-
-Changes to 0.12.X since 0.12.0
-
-Fixed
-
--   PTP clock constructor actually creates a PTP instead of NTP clock
-
-Added
-
--   Bindings for GStreamer Editing Services
--   Bindings for GStreamer Check testing library
--   Bindings for the encoding profile API (encodebin)
-
--   VideoFrame, VideoInfo, AudioInfo, StructureRef implements Send and
-    Sync now
--   VideoFrame has a function to get the raw FFI pointer
--   From impls from the Error/Success enums to the combined enums like
-    FlowReturn
--   Bin-to-dot file functions were added to the Bin trait
--   gst_base::Adapter implements SendUnique now
--   More complete bindings for the gst_video::VideoOverlay interface,
-    especially
-    gst_video::is_video_overlay_prepare_window_handle_message()
-
-Changed
-
--   All references were updated from GitHub to freedesktop.org GitLab
--   Fix various links in the README.md
--   Link to the correct location for the documentation
--   Remove GitLab badge as that only works with gitlab.com currently
-
-Changes in git master for 0.13
-
-Fixed
-
--   gst::tag::Album is the album tag now instead of artist sortname
-
-Added
-
--   Subclassing infrastructure was moved directly into the bindings,
-    making the gst-plugin crate deprecated. This involves many API
-    changes but generally cleans up code and makes it more flexible.
-    Take a look at the gst-plugins-rs crate for various examples.
-
--   Bindings for CapsFeatures and Meta
--   Bindings for
-    ParentBufferMeta,VideoMetaandVideoOverlayCompositionMeta`
--   Bindings for VideoOverlayComposition and VideoOverlayRectangle
--   Bindings for VideoTimeCode
-
--   UniqueFlowCombiner and UniqueAdapter wrappers that make use of the
-    Rust compile-time mutability checks and expose more API in a safe
-    way, and as a side-effect implement Sync and Send now
-
--   More complete bindings for Allocation Query
--   pbutils functions for codec descriptions
--   TagList::iter() for iterating over all tags while getting a single
-    value per tag. The old ::iter_tag_list() function was renamed to
-    ::iter_generic() and still provides access to each value for a tag
--   Bus::iter() and Bus::iter_timed() iterators around the corresponding
-    ::pop\*() functions
-
--   serde serialization of Value can also handle Buffer now
-
--   Extensive comments to all examples with explanations
--   Transmuxing example showing how to use typefind, multiqueue and
-    dynamic pads
--   basic-tutorial-12 was ported and added
-
-Changed
-
--   Rust 1.31 is the minimum supported Rust version now
--   Update to latest gir code generator and glib bindings
-
--   Functions returning e.g. gst::FlowReturn or other “combined” enums
-    were changed to return split enums like
-    Result<gst::FlowSuccess, gst::FlowError> to allow usage of the
-    standard Rust error handling.
-
--   MiniObject subclasses are now newtype wrappers around the underlying
-    GstRc<FooRef> wrapper. This does not change the API in any breaking
-    way for the current usages, but allows MiniObjects to also be
-    implemented in other crates and makes sure rustdoc places the
-    documentation in the right places.
-
--   BinExt extension trait was renamed to GstBinExt to prevent conflicts
-    with gtk::Bin if both are imported
-
--   Buffer::from_slice() can’t possible return None
-
--   Various clippy warnings
-
-
-GStreamer Rust Plugins
-
-Like the GStreamer Rust bindings, the Rust plugins are now officially
-part of the GStreamer project and are also maintained in the GStreamer
-GitLab.
-
-In the 0.3.x versions this contained infrastructure for writing
-GStreamer plugins in Rust, and a set of plugins.
-
-In git master that infrastructure was moved to the GLib and GStreamer
-bindings directly, together with many other improvements that were made
-possible by this, so the gst-plugins-rs repository only contains
-GStreamer elements now.
-
-Elements included are:
-
--   Tutorials plugin: identity, rgb2gray and sinesrc with extensive
-    comments
-
--   rsaudioecho, a port of the audiofx element
-
--   rsfilesrc, rsfilesink
-
--   rsflvdemux, a FLV demuxer. Not feature-equivalent with flvdemux yet
-
--   threadshare plugin: ts-appsrc, ts-proxysrc/sink, ts-queue, ts-udpsrc
-    and ts-tcpclientsrc elements that use a fixed number of threads and
-    share them between instances. For more background about these
-    elements see Sebastian’s talk “When adding more threads adds more
-    problems - Thread-sharing between elements in GStreamer” at the
-    GStreamer Conference 2017.
-
--   rshttpsrc, a HTTP source around the hyper/reqwest Rust libraries.
-    Not feature-equivalent with souphttpsrc yet.
-
--   togglerecord, an element that allows to start/stop recording at any
-    time and keeps all audio/video streams in sync.
-
--   mccparse and mccenc, parsers and encoders for the MCC closed caption
-    file format.
+-   this section will be filled in in due course
+
+GStreamer Rust Bindings and Rust Plugins
+
+The GStreamer Rust bindings are released separately with a different
+release cadence that’s tied to gtk-rs, but the latest release has
+already been updated for the upcoming new GStreamer 1.20 API.
+
+gst-plugins-rs, the module containing GStreamer plugins written in Rust,
+has also seen lots of activity with many new elements and plugins.
+
+What follows is a list of elements and plugins available in
+gst-plugins-rs, so people don’t miss out on all those potentially useful
+elements that have no C equivalent.
+
+-   FIXME: add new elements
+
+Rust audio plugins
+
+-   audiornnoise: New element for audio denoising which implements the
+    noise removal algorithm of the Xiph RNNoise library, in Rust
+-   rsaudioecho: Port of the audioecho element from gst-plugins-good
+    rsaudioloudnorm: Live audio loudness normalization element based on
+    the FFmpeg af_loudnorm filter
+-   claxondec: FLAC lossless audio codec decoder element based on the
+    pure-Rust claxon implementation
+-   csoundfilter: Audio filter that can use any filter defined via the
+    Csound audio programming language
+-   lewtondec: Vorbis audio decoder element based on the pure-Rust
+    lewton implementation
+
+Rust video plugins
+
+-   cdgdec/cdgparse: Decoder and parser for the CD+G video codec based
+    on a pure-Rust CD+G implementation, used for example by karaoke CDs
+-   cea608overlay: CEA-608 Closed Captions overlay element
+-   cea608tott: CEA-608 Closed Captions to timed-text (e.g. VTT or SRT
+    subtitles) converter
+-   tttocea608: CEA-608 Closed Captions from timed-text converter
+-   mccenc/mccparse: MacCaption Closed Caption format encoder and parser
+-   sccenc/sccparse: Scenarist Closed Caption format encoder and parser
+-   dav1dec: AV1 video decoder based on the dav1d decoder implementation
+    by the VLC project
+-   rav1enc: AV1 video encoder based on the fast and pure-Rust rav1e
+    encoder implementation
+-   rsflvdemux: Alternative to the flvdemux FLV demuxer element from
+    gst-plugins-good, not feature-equivalent yet
+-   rsgifenc/rspngenc: GIF/PNG encoder elements based on the pure-Rust
+    implementations by the image-rs project
+
+Rust text plugins
+
+-   textwrap: Element for line-wrapping timed text (e.g. subtitles) for
+    better screen-fitting, including hyphenation support for some
+    languages
+
+Rust network plugins
+
+-   reqwesthttpsrc: HTTP(S) source element based on the Rust
+    reqwest/hyper HTTP implementations and almost feature-equivalent
+    with the main GStreamer HTTP source souphttpsrc
+-   s3src/s3sink: Source/sink element for the Amazon S3 cloud storage
+-   awstranscriber: Live audio to timed text transcription element using
+    the Amazon AWS Transcribe API
+
+Generic Rust plugins
+
+-   sodiumencrypter/sodiumdecrypter: Encryption/decryption element based
+    on libsodium/NaCl
+-   togglerecord: Recording element that allows to pause/resume
+    recordings easily and considers keyframe boundaries
+-   fallbackswitch/fallbacksrc: Elements for handling potentially
+    failing (network) sources, restarting them on errors/timeout and
+    showing a fallback stream instead
+-   threadshare: Set of elements that provide alternatives for various
+    existing GStreamer elements but allow to share the streaming threads
+    between each other to reduce the number of threads
+-   rsfilesrc/rsfilesink: File source/sink elements as replacements for
+    the existing filesrc/filesink elements
 
-Changes to 0.3.X since 0.3.0
-
--   All references were updated from GitHub to freedesktop.org GitLab
--   Fix various links in the README.md
--   Link to the correct location for the documentation
-
-Changes in git master for 0.4
-
--   togglerecord: Switch to parking_lot crate for mutexes/condition
-    variables for lower overhead
--   Merge threadshare plugin here
--   New closedcaption plugin with mccparse and mccenc elements
--   New identity element for the tutorials plugin
-
--   Register plugins statically in tests instead of relying on the
-    plugin loader to find the shared library in a specific place
-
--   Update to the latest API changes in the GLib and GStreamer bindings
--   Update to the latest versions of all crates
+Build and Dependencies
 
+-   this section will be filled in in due course
 
-Build and Dependencies
+gst-build
 
--   The MESON BUILD SYSTEM BUILD IS NOW FEATURE-COMPLETE (*) and it is
-    now the recommended build system on all platforms and also used by
-    Cerbero to build GStreamer on all platforms. The Autotools build is
-    scheduled to be removed in the next cycle. Developers who currently
-    use gst-uninstalled should move to gst-build. The build option
-    naming has been cleaned up and made consistent and there are now
-    feature options to enable/disable plugins and various other features
-    on a case-by-case basis. (*) with the exception of plugin docs which
-    will be handled differently in future
-
--   Symbol export in libraries is now controlled via explicit exports
-    using symbol visibility or export defines where supported, to ensure
-    consistency across all platforms. This also allows libraries to have
-    exports that vary based on detected platform features and configure
-    options as is the case with the GStreamer OpenGL integration library
-    for example. A few symbols that had been exported by accident in
-    earlier versions may no longer be exported. These symbols will not
-    have had declarations in any public header files then though and
-    would not have been usable.
-
--   The GStreamer FFmpeg wrapper plugin (gst-libav) now depends on
-    FFmpeg 4.x and uses the new FFmpeg 4.x API and stopped relying on
-    ancient API that was removed with the FFmpeg 4.x release. This means
-    that it is no longer possible to build this module against an older
-    system-provided FFmpeg 3.x version. Use the internal FFmpeg 4.x copy
-    instead if you build using autotools, or use gst-libav 1.14.x
-    instead which targets the FFmpeg 3.x API and _should_ work fine in
-    combination with a newer GStreamer. It’s difficult for us to support
-    both old and new FFmpeg APIs at the same time, apologies for any
-    inconvenience caused.
-
--   Hardware-accelerated Nvidia video encoder/decoder plugins nvdec and
-    nvenc can be built against CUDA Toolkit versions 9 and 10.0 now. The
-    dynlink interface has been dropped since it’s deprecated in 10.0.
-
--   The (optional) OpenCV requirement has been bumped to >= 3.0.0 and
-    the plugin can also be built against OpenCV 4.x now.
-
--   New sctp plugin based on usrsctp (for WebRTC data channels)
+-   this section will be filled in in due course
 
 Cerbero
 
@@ -1173,741 +198,102 @@ Cerbero is a meta build system used to build GStreamer plus dependencies
 on platforms where dependencies are not readily available, such as
 Windows, Android, iOS and macOS.
 
-Cerbero has seen a number of improvements:
-
--   Cerbero has been ported to Python 3 and requires Python 3.5 or newer
-    now
-
--   Source tarballs are now protected by checksums in the recipes to
-    guard against download errors and malicious takeover of projects or
-    websites. In addition, downloads are only allowed via secure
-    transports now and plain HTTP, FTP and git:// transports are not
-    allowed anymore.
+General improvements
 
--   There is now a new fetch-bootstrap command which downloads sources
-    required for bootstrapping, with an optional --build-tools-only
-    argument to match the bootstrap --build-tools-only command.
+-   this section will be filled in in due course
 
--   The bootstrap, build, package and bundle-source commands gained a
-    new --offline switch that ensures that only sources from the cache
-    are used and never downloaded via the network. This is useful in
-    combination with the fetch and fetch-bootstrap commands that acquire
-    sources ahead of time before any build steps are executed. This
-    allows more control over the sources used and when sources are
-    updated, and is particularly useful for build environments that
-    don’t have network access.
+macOS / iOS
 
--   bootstrap --assume-yes will automatically say ‘yes’ to any
-    interactive prompts during the bootstrap stage, such as those from
-    apt-get or yum.
+-   this section will be filled in in due course
 
--   bootstrap --system-only will only bootstrap the system without build
-    tools.
-
--   Manifest support: The build manifest can be used in continuous
-    integration (CI) systems to fixate the Git revision of certain
-    projects so that all builds of a pipeline are on the same reference.
-    This is used in GStreamer’s gitlab CI for example. It can also be
-    used in order to re-produce a specific build. To set a manifest, you
-    can set manifest = 'my_manifest.xml' in your configuration file, or
-    use the --manifest command line option. The command line option will
-    take precedence over anything specific in the configuration file.
+Windows
 
--   The new build-deps command can be used to build only the
-    dependencies of a recipe, without the recipe itself.
+-   this section will be filled in in due course
 
--   new --list-variants command to list available variants
+Windows MSI installer
 
--   variants can now be set on the command line via the -v option as a
-    comma-separated list. This overrides any variants set in any
-    configuration files.
+-   this section will be filled in in due course
 
--   new qt5, intelmsdk and nvidia variants for enabling Qt5 and hardware
-    codec support. See the Enabling Optional Features with Variants
-    section in the Cerbero documentation for more details how to enable
-    and use these variants.
+Linux
 
--   When building on Windows, Cerbero can now build GStreamer recipes
-    and core dependencies such as glib with Visual Studio. This is
-    controlled by the visualstudio variant. Visual Studio 2015, 2017,
-    and 2019 are supported. Currently, only 64-bit x86 is supported due
-    to a known bug which will be fixed for the next release.
+-   this section will be filled in in due course
 
--   A new -t / --timestamp command line switch makes commands print
-    timestamps
+Android
 
+-   this section will be filled in in due course
 
 Platform-specific changes and improvements
 
 Android
 
--   toolchain: update compiler to clang and NDKr18. NDK r18 removed the
-    armv5 target and only has Android platforms that target at least
-    armv7 so the armv5 target is not useful anymore.
-
--   The way that GIO modules are named has changed due to upstream GLib
-    natively adding support for loading static GIO modules. This means
-    that any GStreamer application using gnutls for SSL/TLS on the
-    Android or iOS platforms (or any other setup using static libraries)
-    will fail to link looking for the g_io_module_gnutls_load_static()
-    function. The new function name is now
-    g_io_gnutls_load(gpointer data). data can be NULL for a static
-    library. Look at this commit for the necessary change in the
-    examples.
-
--   various build issues on Android have been fixed.
+-   this section will be filled in in due course
 
 macOS and iOS
 
--   various build issues on iOS have been fixed.
+-   this section will be filled in in due course
 
--   the minimum required iOS version is now 9.0. The difference in
-    adoption between 8.0 and 9.0 is 0.1% and the bump to 9.0 fixes some
-    build issues.
+Windows
 
--   The way that GIO modules are named has changed due to upstream GLib
-    natively adding support for loading static GIO modules. This means
-    that any GStreamer application using gnutls for SSL/TLS on the
-    Android or iOS platforms (or any other setup using static libraries)
-    will fail to link looking for the g_io_module_gnutls_load_static()
-    function. The new function name is now
-    g_io_gnutls_load(gpointer data). data can be NULL for a static
-    library. Look at this commit for the necessary change in the
-    examples.
+-   this section will be filled in in due course
 
-Windows
+Linux
 
--   The webrtcdsp element is shipped again as part of the Windows binary
-    packages, the build system issue has been resolved.
+-   this section will be filled in in due course
 
--   ‘Inconsistent DLL linkage’ warnings when building with MSVC have
-    been fixed
+Documentation improvements
 
--   Hardware-accelerated Nvidia video encoder/decoder plugins nvdec and
-    nvenc build on Windows now, also with MSVC and using Meson.
+-   this section will be filled in in due course
 
--   The ksvideosrc camera capture plugin supports 16-bit grayscale video
-    now
+Possibly Breaking Changes
 
--   The wasapisrc audio capture element implements loopback recording
-    from another output device or sink
+-   this section will be filled in in due course
+-   MPEG-TS SCTE-35 API changes (FIXME: flesh out)
+-   gst_parse_launch() and friends now error out on non-existing
+    properties on top-level bins where they would silently fail and
+    ignore those before.
 
--   wasapisink recover from low buffer levels in shared mode and some
-    exclusive mode fixes
+Known Issues
+
+-   this section will be filled in in due course
 
--   dshowsrc now implements the GstDeviceMonitor interface
+-   There are a couple of known WebRTC-related regressions/blockers:
 
+    -   webrtc: DTLS setup with Chrome is broken
+    -   webrtcbin: First keyframe is usually lost
 
 Contributors
 
-Aaron Boxer, Aleix Conchillo Flaqué, Alessandro Decina, Alexandru Băluț,
-Alex Ashley, Alexey Chernov, Alicia Boya García, Amit Pandya, Andoni
-Morales Alastruey, Andreas Frisch, Andre McCurdy, Andy Green, Anthony
-Violo, Antoine Jacoutot, Antonio Ospite, Arun Raghavan, Aurelien Jarno,
-Aurélien Zanelli, ayaka, Bananahemic, Bastian Köcher, Branko Subasic,
-Brendan Shanks, Carlos Rafael Giani, Charlie Turner, Christoph Reiter,
-Corentin Noël, Daeseok Youn, Damian Vicino, Dan Kegel, Daniel Drake,
-Daniel Klamt, Danilo Spinella, Dardo D Kleiner, David Ing, David
-Svensson Fors, Devarsh Thakkar, Dimitrios Katsaros, Edward Hervey,
-Emilio Pozuelo Monfort, Enrique Ocaña González, Erlend Eriksen, Ezequiel
-Garcia, Fabien Dessenne, Fabrizio Gennari, Florent Thiéry, Francisco
-Velazquez, Freyr666, Garima Gaur, Gary Bisson, George Kiagiadakis, Georg
-Lippitsch, Georg Ottinger, Geunsik Lim, Göran Jönsson, Guillaume
-Desmottes, H1Gdev, Haihao Xiang, Haihua Hu, Harshad Khedkar, Havard
-Graff, He Junyan, Hoonhee Lee, Hosang Lee, Hyunjun Ko, Ilya Smelykh,
-Ingo Randolf, Iñigo Huguet, Jakub Adam, James Stevenson, Jan Alexander
-Steffens, Jan Schmidt, Jerome Laheurte, Jimmy Ohn, Joakim Johansson,
-Jochen Henneberg, Johan Bjäreholt, John-Mark Bell, John Bassett, John
-Nikolaides, Jonathan Karlsson, Jonny Lamb, Jordan Petridis, Josep Torra,
-Joshua M. Doe, Jos van Egmond, Juan Navarro, Julian Bouzas, Jun Xie,
-Junyan He, Justin Kim, Kai Kang, Kim Tae Soo, Kirill Marinushkin, Kyrylo
-Polezhaiev, Lars Petter Endresen, Linus Svensson, Louis-Francis
-Ratté-Boulianne, Lucas Stach, Luis de Bethencourt, Luz Paz, Lyon Wang,
-Maciej Wolny, Marc-André Lureau, Marc Leeman, Marco Trevisan (Treviño),
-Marcos Kintschner, Marian Mihailescu, Marinus Schraal, Mark Nauwelaerts,
-Marouen Ghodhbane, Martin Kelly, Matej Knopp, Mathieu Duponchelle,
-Matteo Valdina, Matthew Waters, Matthias Fend, memeka, Michael Drake,
-Michael Gruner, Michael Olbrich, Michael Tretter, Miguel Paris, Mike
-Wey, Mikhail Fludkov, Naveen Cherukuri, Nicola Murino, Nicolas Dufresne,
-Niels De Graef, Nirbheek Chauhan, Norbert Wesp, Ognyan Tonchev, Olivier
-Crête, Omar Akkila, Pat DeSantis, Patricia Muscalu, Patrick Radizi,
-Patrik Nilsson, Paul Kocialkowski, Per Forlin, Peter Körner, Peter
-Seiderer, Petr Kulhavy, Philippe Normand, Philippe Renon, Philipp Zabel,
-Pierre Labastie, Piotr Drąg, Roland Jon, Roman Sivriver, Roman Shpuntov,
-Rosen Penev, Russel Winder, Sam Gigliotti, Santiago Carot-Nemesio,
-Sean-Der, Sebastian Dröge, Seungha Yang, Shi Yan, Sjoerd Simons, Snir
-Sheriber, Song Bing, Soon, Thean Siew, Sreerenj Balachandran, Stefan
-Ringel, Stephane Cerveau, Stian Selnes, Suhas Nayak, Takeshi Sato,
-Thiago Santos, Thibault Saunier, Thomas Bluemel, Tianhao Liu,
-Tim-Philipp Müller, Tobias Ronge, Tomasz Andrzejak, Tomislav Tustonić,
-U. Artie Eoff, Ulf Olsson, Varunkumar Allagadapa, Víctor Guzmán, Víctor
-Manuel Jáquez Leal, Vincenzo Bono, Vineeth T M, Vivia Nikolaidou, Wang
-Fei, wangzq, Whoopie, Wim Taymans, Wind Yuan, Wonchul Lee, Xabier
-Rodriguez Calvar, Xavier Claessens, Haihao Xiang, Yacine Bandou,
-Yeongjin Jeong, Yuji Kuwabara, Zeeshan Ali,
+-   this section will be filled in in due course
 
 … and many others who have contributed bug reports, translations, sent
 suggestions or helped testing.
 
+Stable 1.20 branch
 
-Stable 1.16 branch
-
-After the 1.16.0 release there will be several 1.16.x bug-fix releases
+After the 1.20.0 release there will be several 1.20.x bug-fix releases
 which will contain bug fixes which have been deemed suitable for a
 stable branch, but no new features or intrusive changes will be added to
-a bug-fix release usually. The 1.16.x bug-fix releases will be made from
-the git 1.16 branch, which is a stable branch.
-
-1.16.0
-
-1.16.0 was released on 19 April 2019.
-
-1.16.1
-
-The first 1.16 bug-fix release (1.16.1) was released on 23 September
-2019.
-
-This release only contains bugfixes and it _should_ be safe to update
-from 1.16.0.
-
-Highlighted bugfixes in 1.16.1
-
--   GStreamer-vaapi: fix green frames and decoding artefacts in some
-    cases
--   OpenGL: fix wayland event source burning CPU in certain
-    circumstances
--   Memory leak fixes and memory footprint improvements
--   Performance improvements
--   Stability and security fixes
--   Fix enum for GST_MESSAGE_DEVICE_CHANGED which is technically an API
-    break, but this is only used internally in GStreamer and duplicated
-    another message enum
--   hls: Make crypto dependency optional when hls-crypto is auto
--   player: fix switching back and forth between forward and reverse
-    playback
--   decklinkaudiosink: Drop late buffers
--   openh264enc: Fix compilation with openh264 v2.0
--   wasapisrc: fix segtotal value being always 2
--   android: Fix gnutls issue causing a FORTIFY crash on Android Q
--   windows: Fix two crashes due to cross-CRT free when using MSVC
-
-gstreamer core
-
--   device: gst_device_create_element() is transfer floating, not
-    transfer full
--   filesink, fdsink: respect IOV_MAX for the writev iovec array
-    (Solaris)
--   miniobject: free qdata array when the last qdata is removed (reduces
-    memory footprint)
--   bin: Fix minor race when adding to a bin
--   aggregator: Actually handle NEED_DATA return from update_src_caps()
--   aggregator: Ensure that the source pad is created as a
-    GstAggregatorPad if no type is given in the pad template
--   latency: fix custom event leaks
--   registry: Use plugin directory from the build system for
-    relocateable Windows builds
--   message: fix up enum value for GST_MESSAGE_DEVICE_CHANGED
--   info: Fix deadlock in gst_ring_buffer_logger_log()
--   downloadbuffer: Check for flush after seek
--   identity: Non-live upstream have no max latency
--   identity: Fix the ts-offset property getter
--   aggregator: Make parsing of explicit sink pad names more robust
--   bufferpool: Fix the buffer size reset code
--   fakesink, fakesrc, identity: sync gst_buffer_get_flags_string() with
-    new flags
--   multiqueue: never unref queries we do not own
--   concat: Reset last_stop on FLUSH_STOP too
--   aggregator: fix flow-return boolean return type mismatch
--   gstpad: Handle probes that reset the data field
--   gst: Add support for g_autoptr(GstPromise)
--   gst-inspect: fix unused-const-variable error in windows
--   base: Include gstbitwriter.h in the single-include header
--   Add various Since: 1.16 markers
--   GST_MESSAGE_DEVICE_CHANGED duplicates GST_MESSAGE_REDIRECT
--   Targetting wrong meson version
--   meson: Make get_flex_version.py script executable
--   meson: Link to objects instead of static helper library
--   meson: set correct install path for gdb helper
--   meson: fix warning about configure_file() install kwarg
-
-gst-plugins-base
-
--   video-info: parse field-order for all interleaved formats
--   tests: fix up valgrind suppressions for glibc getaddrinfo leaks
--   meson: Reenable NEON support (in audio resampler)
--   audio-resampler: Update NEON to handle remainders not multiples of 4
--   eglimage: Fix memory leak
--   audiodecoder: Set output caps with negotiated caps to avoid critical
-    info printed
--   video-frame: Take TFF flag from the video info if it was set in
-    there
--   glcolorconvert: Fix external-oes shader
--   video-anc: Fix ADF detection when trying to extract data from vanc
--   gl/wayland: fix wayland event source burning CPU
--   configure: add used attribute in order to make NEON detection
-    working with -flto.
--   audioaggregator: Return a valid rate range from caps query if
-    downstream supports a whole range
--   rtspconnection: data-offset increase not set
--   rtpsconnection: Fix number of n_vectors
--   video-color: Add compile-time assert for ColorimetryInfo enum
--   audiodecoder: Fix leak on failed audio gaps
--   glupload: Keep track of cached EGLImage texture format
--   playsink: Set ts-offset to text sink.
--   meson.build: use join_paths() on prefix
--   compositor: copy frames as-is when possible
--   compositor: Skip background when a pad obscures it completely
--   rtspconnection: Start CSeq at 1 (some servers don’t cope well with
-    seqnum 0)
--   viv-fb: fix build break for GST_GL_API
--   gl/tests: fix shader creation tests part 2
--   gl/tests: fix shader creation tests
--   wayland: set the event queue also for the xdg_wm_base object
--   video: Added GI annotation for gstvideoaffinetransformationmeta
-    apply_matrix
--   compositor: Remove unneeded left shift for ARGB/AYUV SOURCE operator
--   Colorimetry fixes
--   alsasrc: Don’t use driver timestamp if it’s zero
--   gloverlaycompositor: fix crash if buffer doesn’t have video meta
--   meson: Don’t try to find gio-unix on Windows
--   glshader: fix default external-oes shaders
--   subparse: fix pushing WebVTT cue with no newline at the end
--   meson: Missing “android” choice in gl_winsys
--   video test: Keep BE test inline with LE test
--   id3tag: Correctly validate the year from v1 tags before passing to
-    GstDateTime
--   gl/wayland: Don’t prefix wl_shell struct field
--   eglimage: Add compatibility define for DRM_FORMAT_NV24
--   Add various Since: 1.16 markers
--   video-anc: Handle SD formats correctly
--   Docs: add GL_CFLAGS to GTK_DOC_CFLAGS
--   GL: using vaapi and showing on glimagesink on wayland loads one core
-    for 100% on 1.16
--   GL: external-oes shader places precision qualifier before #extension
-    (was: androidmedia amcviddec fail after 1.15.90 1.16.0 update)
-
-gst-plugins-good
-
--   alpha: Fix one_over_kc calculation on arm/aarch64
--   souphttpsrc: Fix incompatible type build warning
--   rtpjitterbuffer: limit max-dropout-time to maxint32
--   rtpjitterbuffer: Clear clock master before unreffing
--   qtdemux: Use empty-array safe way to cleanup GPtrArray
--   v4l2: Fix type compatibility issue with glibc 2.30
--   valgrind: suppress Cond error coming from gnutls and Ignore leaks
-    caused by shout/sethostent
--   rtpfunnel: forward correct segment when switching pad
--   gtkglsink: fix crash when widget is resized after element
-    destruction
--   jpegdec: Don’t dereference NULL input state if we have no caps in
-    TIME segments
--   rtp: opuspay: fix memory leak in gst_rtp_opus_pay_setcaps
--   v4l2videodec: return right type for drain.
--   rtpssrcdemux: Avoid taking streamlock out-of-band
--   Support v4l2src buffer orphaning
--   splitmuxsink: Only set running time on finalizing sink element when
-    in async-finalize mode
--   rtpsession: Always keep at least one NACK on early RTCP
--   rtspsrc: do not try to send EOS with invalid seqnum
--   rtpsession: Call on-new-ssrc earlier
--   rtprawdepay: Don’t get rid of the buffer pool on FLUSH_STOP
--   rtpbin: Free storage when freeing session
--   scaletempo: Advertise interleaved layout in caps templates
--   Support v4l2src buffer orphaning
-
-gst-plugins-bad
-
--   hls: Make crypto dependency optional when hls-crypto is auto
--   player: fix switching back and forth between forward and reverse
-    playback
--   decklinkaudiosink: Drop late buffers
--   srt: Add stats property, include sender-side statistics and fix a
-    crash
--   dshowsrcwrapper: fix regression on device selection
--   tsdemux: Limit the maximum PES payload size
--   wayland: Define libdrm_dep in meson.build to fix meson configure
-    error when kms is disabled
--   sctp: Fix crash on free() when using the MSVC binaries
--   webrtc: Fix signals documentation
--   h264parse: don’t critical on VUI parameters > 2^31
--   rtmp: Fix crash inside free() with MSVC on Windows
--   iqa: fix leak of map_meta.data
--   d3dvideosink: Fix crash on WinProc handler
--   amc: Fix crash when a sync_meta survives its sink
--   pitch: Fix race between putSamples() and setting soundtouch
-    parameters
--   webrtc: fix type of max-retransmits, make it work
--   mxfdemux: Also allow picture essence element type 0x05 for VC-3
--   wasapi: fix symbol redefinition build error
--   decklinkvideosrc: Retrieve mode of the ancillary data from the frame
--   decklinkaudiosrc/decklinkvideosrc: Do nothing in
-    BaseSrc::negotiate() and…
--   adaptivedemux: do not retry downloads during shutdown.
--   webrtcbin: fix GInetAddress leak
--   dtls: fix dtls connection object leak
--   siren: fix a global buffer overflow spotted by asan
--   kmssink: Fix implicit declaration build error
--   Fix -Werror=return-type error in configure.
--   aiff: Fix infinite loop in header parsing.
--   nvdec: Fix possible frame drop on EOS
--   srtserversrc: yields malformed rtp payloads
--   srtsink: Fix crash in case no URI
--   dtlsagent: Fix leaked dtlscertificate
--   meson: bluez: Early terminate configure on Windows
--   decklink: Correctly ensure >=16 byte alignment for the buffers we
-    allocate
--   webrtcbin: fix DTLS when receivebin is set to DROP
--   zbar: Include running-time, stream-time and duration in the messages
--   uvch264src: Make sure we set our segment
--   avwait: Allow start and end timecode to be set back to NULL
--   avwait: Don’t print warnings for every buffer passed
--   hls/meson: fix dependency logic
--   Waylandsink gnome shell workaround
--   avwait: Allow setting start timecode after end timecode; protect
-    propeties with mutex
--   wayland/wlbuffer: just return if used_by_compositor is true when
-    attach
--   proxy: Set SOURCE flag on the source and SINK flag on the sink
--   ivfparse: Check the data size against IVF_FRAME_HEADER_SIZE
--   webrtc: Add various Since markers to new types after 1.14.0
--   msdk: fix the typo in debug category
--   dtlsagent: Do not overwrite openssl locking callbacks
--   meson: Fix typo in gsm header file name
--   srt: handle races in state change
--   webrtc: Add g_autoptr() support for public types
--   openh264enc: Fix compilation with openh264 v2.0
--   meson: Allow CUDA_PATH fallback on linux
--   meson: fix build with opencv=enabled and opencv4. Fixes #964
--   meson: Add support for the colormanagement plugin
--   autotools: gstsctp: set LDFLAGS
--   nvenc/nvdec: Add NVIDIA SDK headers to noinst_HEADERS
--   h264parse: Fix typo when setting multiview mode and flags
--   Add various Since: 1.16 markers
--   opencv: allow compilation against 4.1.x
--   Backport of some minor srt commits without MR into 1.16
--   meson: fix build with opencv=enabled and opencv4
--   wasapisrc: fix segtotal value being always 2 due to an unused
-    variable
--   meson: colormanagement missing
--   androidmedia amcviddec fail after 1.15.90 1.16.0 update
-
-gst-plugins-ugly
-
--   meson: Always require the gmodule dependency
-
-gst-libav
-
--   docs: don’t include the type hierarchy, fixing build with gtk-doc
-    1.30
--   avvidenc: Correctly signal interlaced input to ffmpeg when the input
-    caps are interlaced
--   autotools: add bcrypt to win32 libs
--   gstav: Use libavcodec util function for version check
--   API documentation fails to build with gtk-doc 1.30
-
-gst-rtsp-server
-
--   rtsp-client: RTP Info must exist in PLAY response
--   onvif-media: fix “void function returning a value” compiler warning
--   Add various Since: 1.16 markers
-
-gstreamer-vaapi
-
--   fix egl context leak and display creation race
--   pluginutil: Remove Mesa from drivers white list
--   Classify vaapidecodebin as a hardware decoder
--   Fix two leak
--   vaapivideomemory: demote error message to info
--   encoder: vp8,vp9: reset frame_counter when input frame’s format
-    changes
--   encoder: mpeg2: No packed header for SPS and PPS
--   decoder: vp9: clear parser pointer after release
--   encoder: Fixes deadlock in change state function
--   encoder: h265: reset num_ref_idx_l1_active_minus1 when low delay B.
--   encoder: not call ensure_num_slices inside g_assert()
--   encoder: continue if roi meta is NULL
--   decoder: vp9: Set chroma_ ype by VP9 bit_depth
--   vaapipostproc: don’t do any color conversion when GL_TEXTURE_UPLOAD
--   libs: surface: fix double free when dmabuf export fails
--   h264 colors and artifacts upon upgrade to GStreamer Core Library
-    version 1.15.90
-
-gst-editing-services
-
--   element: Properly handle the fact that pasting can return NULL
--   Add various missing Since markers
--   launch: Fix caps restriction short names
--   python: Avoid warning about using deprecated methods
--   video-transition: When using non crossfade effect use ‘over’
-    operations
--   meson: Generate a pkgconfig file for the GES plugin
-
-gst-devtools
-
--   launcher: testsuites: skip systemclock stress tests
--   validate: fix build on macOS
-
-gst-build
-
--   Update win flex bison binaries
--   Update the flexmeson windows binary version
--   Don’t allow people to run meson inside the uninstalled env
-
-Cerbero build tool and packaging changes in 1.16.1
-
--   cerbero: Add enums for Fedora 30, Fedora 31 and Debian bullseye
--   gnutls.recipe: Fix crash when running on Android Q
--   recipes: Upgrade openssl to 1.1.1c
--   Fix some typos
--   add support for vs build tools 2019, fixes #183
--   android: Adjust gstreamer-1.0.mk for NDK r20
--   Fix license enums
--   bootstrap: Fix dnf usage on CentOS
--   Make _add_system_libs reentrant
--   meson.recipe: Fix setting of bitcode compiler options
--   cerbero: support Ubuntu disco dingo
--   cerbero: Set utf-8 to execution character set also on MSVC
--   git: simplify the reset of the source branch.
--   FORTIFY: %n not allowed on Android Q
--   Fails to build if there’s no license file for the given license
-    (GPL/LGPL without Plus, Proprietary, …)
-
-Contributors to 1.16.1
-
-Aaron Boxer, Adam Duskett, Alicia Boya García, Andoni Morales Alastruey,
-Antonio Ospite, Arun Raghavan, Askar Safin, A. Wilcox, Charlie Turner,
-Christoph Reiter, Damian Hobson-Garcia, Daniel Klamt, Danny Smith, David
-Gunzinger, David Ing, David Svensson Fors, Doug Nazar, Edward Hervey,
-Eike Hein, Fabrice Bellet, Fernando Herrrera, Georg Lippitsch, Göran
-Jönsson, Guillaume Desmottes, Haihao Xiang, Haihua Hu, Håvard Graff, Hou
-Qi, Ignacio Casal Quinteiro, Ilya Smelykh, Jan Schmidt, Javier Celaya,
-Jim Mason, Jonas Larsson, Jordan Petridis, Jose Antonio Santos Cadenas,
-Juan Navarro, Knut Andre Tidemann, Kristofer Björkström, Lucas Stach,
-Marco Felsch, Marcos Kintschner, Mark Nauwelaerts, Martin Liska, Martin
-Theriault, Mathieu Duponchelle, Matthew Waters, Michael Olbrich, Mike
-Gorse, Nicola Murino, Nicolas Dufresne, Niels De Graef, Niklas
-Hambüchen, Nirbheek Chauhan, Olivier Crête, Philippe Normand, Ross
-Burton, Sebastian Dröge, Seungha Yang, Song Bing, Thiago Santos,
-Thibault Saunier, Thomas Coldrick, Tim-Philipp Müller, Víctor Manuel
-Jáquez Leal, Vivia Nikolaidou, Xavier Claessens, Yeongjin Jeong,
-
-… and many others who have contributed bug reports, translations, sent
-suggestions or helped testing. Thank you all!
-
-List of merge requests and issues fixed in 1.16.1
-
--   List of Merge Requests applied in 1.16
--   List of Issues fixed in 1.16.1
-
-1.16.2
-
-The second 1.16 bug-fix release (1.16.2) was released on 03 December
-2019.
-
-This release only contains bugfixes and it _should_ be safe to update
-from 1.16.1.
-
-Highlighted bugfixes in 1.16.2
-
--   Interlaced video scaling fixes
--   CineForm video support in AVI
--   audioresample: avoid glitches due to rounding errors after changing
-    rate
--   Command line tool output printing improvements on Windows
--   various performance improvements, memory leak fixes and security
-    fixes
--   VP9 decoding fixes
--   avfvideosrc: Explicitly request video permission on macOS 10.14+
--   wasapi: bug fixes and stability improvements
--   webrtc-audio-processing: fix segmentation fault on 32-bit windows
--   tsdemux: improved handling of certain discontinuities
--   vaapi h265 decoder: wait for I-frame before trying to decode
-
-gstreamer
-
--   gst-launch: Fix ugly stdout on Windows
--   tee: Make sure to actually deactivate pads that are released
--   bin: Drop need-context messages without source instead of crashing
--   gst: Don’t pass miniobjects to GST_DEBUG_OBJECT() and similar macros
--   tracers: Don’t leak temporary GstStructure
-
-gst-plugins-base
-
--   xvimagepool: Update size, stride, and offset with allocated XvImage
--   video-converter: Fix RGB-XYZ-RGB conversion
--   audiorate: Update next_offset on rate change
--   audioringbuffer: Reset reorder flag before check
--   audio-buffer: Don’t fail to map buffers with zero samples
--   videorate: Fix max-duplication-time handling
--   gl/gbm: ensure we call the resize callback before attempting to draw
--   video-converter: Various fixes for interlaced scaling
--   gstrtspconnection: messages_bytes not decreased
--   check: Don’t use real audio devices for tests
--   riff: add CineForm mapping
--   glfilters: Don’t use static variables for storing per-element state
--   glupload: Add VideoMetas and GLSyncMeta to the raw uploaded buffers
--   streamsynchronizer: avoid pad release race during logging.
--   gst-play: Use gst_print* to avoid broken stdout string on Windows
-
-gst-plugins-good
-
--   vp9dec: Fix broken 4:4:4 8bits decoding
--   rtpsession: add locking for clear-pt-map
--   rtpL16depay: don’t crash if data is not modulo channels*width
--   wavparse: Fix push mode ignoring audio with a size smaller than
-    segment buffer
--   wavparse: Fix push mode ignoring last audio payload chunk
--   aacparse: fix wrong offset of the channel number in adts header
--   jpegdec: Fix incorrect logic in EOI tag detection
--   videocrop: Also update the coordinate when in-place
--   jpegdec: don’t overwrite the last valid line
--   vpx: Error out if enabled and no features found
--   v4l2videodec: ensure pool exists before orphaning it
--   v4l2videoenc: fix type conversion errors
--   v4l2bufferpool: Queue number of allocated buffers to capture
--   v4l2object: fix mpegversion number typo
--   v4l2object: Work around bad TRY_FMT colorimetry implementations
-
-gst-plugins-bad
-
--   avfvideosrc: Explicitly request video permission on macOS 10.14+
--   wasapi: Various fixes and a workaround for a specific driver bug
--   wasapi: Move to CoInitializeEx for COM initialization
--   wasapi: Fix runtime/build warnings
--   waylandsink: Commit the parent after creating subsurface
--   msdkdec: fix surface leak in msdkdec_handle_frame
--   tsmux: Fix copying of buffer region
--   tsdemux: Handle continuity mismatch in more cases
--   tsdemux: Always issue a DTS even when it’s equal to PTS
--   openexr: Fix build with OpenEXR 2.4 (and also OpenEXR 2.2 on Ubuntu
-    18.04)
--   ccextractor: Always forward all sticky events to the caption pad
--   pnmdec: Return early on ::finish() if we have no actual data to
-    parse
--   ass: avoid infinite unref loop with bad data
--   fluidsynth: add sf3 to soundfont search path
--   webrtcdsp/webrtcechoprobe segmentation fault on windows (1.16.0 x86)
-
-gst-libav
-
--   avvidenc: Fix error propagation
--   avdemux: Fix segmentation fault if long_name is NULL
--   avviddec: Fix huge leak caused by circular reference
--   avviddec: Enforce allocate new AVFrame per input frame
--   avdec_mpeg2video (and probably more): Huge memory leak in git master
-
-gst-rtsp-server
-
--   rtsp-media: Use lock in gst_rtsp_media_is_receive_only
--   rtsp-client: RTP Info when completed_sender
--   rtsp-client: fix location uri-format by getting uri directly from
-    context instead
-
-gstreamer-vaapi
-
--   meson build: halt configuration if no renderer API
--   libs: decoder: h265: skip all pictures prior the first I-frame
--   libs: window: x11: Avoid usage of deprecated API
-
-gst-editing-services
-
--   Initialize debug categories before usage
-
-gst-build
-
--   gst-env: Use locally built GStreamer utility programs
-
-Cerbero build tool and packaging changes in 1.16.2
-
-General
-
--   openssl: Update to 1.1.1d
--   Updated ffmpeg, expat, flac, freetype, croco, ogg, xml2, mpg123,
-    openjpeg, opus, pixman, speex, tiff recipes
--   Fix setting of git credentials in local source repos
-
-Windows
-
--   webrtc-audio-processing: fix segmentation fault on 32-bit windows
-    with webrtcdsp/webrtcechoprobe elemens
--   vpx plugin has no features when built with Visual Studio 2019
--   libvpx: Add support for Visual Studio 2019
--   mingw-runtime.recipe: Correctly package pkg-config in the MSI
--   GIO doesn’t load any modules on Windows with MSVC, which breaks TLS
-    support since glib-networking’s giognutls module isn’t loaded
--   Make the instructions for running Cerbero the same on all platforms
-
-macOS + iOS
-
--   Add support for macOS 10.15 Catalina
--   Updates for Xcode 11
--   macos/ios: expose objc++ compilers in env variables
--   srt.recipe: Fix crash in constructor on iOS
--   osx-framework.recipe: Dynamically generate the list of libraries and
-    ship pkg-config
--   macos: add -mmacosx-version-min for framework
--   gstreamer-1.0-osx-framework.recipe contains an outdated hard-coded
-    list of libraries
--   We need to ship pkg-config with macOS
-
-Linux
-
--   Fix filesprovider.find_shlib_regex when a lib_suffix is used in the
-    cerbero config file
-
-Contributors to 1.16.2
-
-Adam Nilsson, Amr Mahdi, Angus Ao, Charlie Turner, Edward Hervey, Fabian
-Greffrath, Fuwei Tang, Havard Graff, Hu Qian, James Cowgill, Jan
-Alexander Steffens (heftig), Jeffy Chen, Jeremy Lempereur, Joakim
-Johansson, Jochen Henneberg, Julien Isorce, Kevin Joly, Kristofer
-Bjorkstrom, Kyrylo Polezhaiev, Matthew Waters, Michael Olbrich, Muhammet
-Ilendemli, Nicolas Dufresne, Nirbheek Chauhan, Pablo Marcos Oltra, Roman
-Shpuntov, Ruben Gonzalez, Scott Kanowitz, Sebastian Dröge, Seungha Yang,
-Thibault Saunier, Tim-Philipp Müller, Víctor Manuel Jáquez Leal, Vivia
-Nikolaidou,
-
-… and many others who have contributed bug reports, translations, sent
-suggestions or helped testing. Thank you all!
-
-List of merge requests and issues fixed in 1.16.2
-
--   List of Merge Requests applied in 1.16
--   List of Issues fixed in 1.16.2
-
-
-Known Issues
-
--   possibly breaking/incompatible changes to properties of wrapped
-    FFmpeg decoders and encoders (see above).
+a bug-fix release usually. The 1.20.x bug-fix releases will be made from
+the git 1.20 branch, which will be a stable branch.
 
--   The way that GIO modules are named has changed due to upstream GLib
-    natively adding support for loading static GIO modules. This means
-    that any GStreamer application using gnutls for SSL/TLS on the
-    Android or iOS platforms (or any other setup using static libraries)
-    will fail to link looking for the g_io_module_gnutls_load_static()
-    function. The new function name is now
-    g_io_gnutls_load(gpointer data). See Android/iOS sections above for
-    further details.
+1.20.0
 
+1.20.0 is scheduled to be released around October/November 2021.
 
-Schedule for 1.18
+Schedule for 1.22
 
-Our next major feature release will be 1.18, and 1.17 will be the
-unstable development version leading up to the stable 1.18 release. The
-development of 1.17/1.18 will happen in the git master branch.
+Our next major feature release will be 1.22, and 1.21 will be the
+unstable development version leading up to the stable 1.22 release. The
+development of 1.21/1.22 will happen in the git main branch.
 
-The plan for the 1.18 development cycle is yet to be confirmed, but it
-is now expected that feature freeze will take place in December 2019,
-with the first 1.18 stable release ready in late January or February.
+The plan for the 1.22 development cycle is yet to be confirmed.
 
-1.18 will be backwards-compatible to the stable 1.16, 1.14, 1.12, 1.10,
-1.8, 1.6, 1.4, 1.2 and 1.0 release series.
+1.22 will be backwards-compatible to the stable 1.20, 1.18, 1.16, 1.14,
+1.12, 1.10, 1.8, 1.6, 1.4, 1.2 and 1.0 release series.
 
 ------------------------------------------------------------------------
 
-_These release notes have been prepared by Tim-Philipp Müller with_
-_contributions from Sebastian Dröge, Guillaume Desmottes, Matthew
-Waters, _ _Thibault Saunier, and Víctor Manuel Jáquez Leal._
+These release notes have been prepared by Tim-Philipp Müller with
+contributions from …
 
-_License: CC BY-SA 4.0_
+License: CC BY-SA 4.0
diff --git a/RELEASE b/RELEASE
index 82f23e6..e0eca80 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -1,15 +1,15 @@
-This is GStreamer gst-editing-services 1.16.2.
+This is GStreamer gst-editing-services 1.19.2.
 
-The GStreamer team is pleased to announce another bug-fix release in the
-stable 1.x API series of your favourite cross-platform multimedia framework!
+GStreamer 1.19 is the development branch leading up to the next major
+stable version which will be 1.20.
 
-The 1.16 release series adds new features on top of the 1.14 series and is
+The 1.19 development series adds new features on top of the 1.18 series and is
 part of the API and ABI-stable 1.x release series of the GStreamer multimedia
 framework.
 
 Full release notes will one day be found at:
 
-  https://gstreamer.freedesktop.org/releases/1.16/
+  https://gstreamer.freedesktop.org/releases/1.20/
 
 Binaries for Android, iOS, Mac OS X and Windows will usually be provided
 shortly after the release.
diff --git a/autogen.sh b/autogen.sh
deleted file mode 100755 (executable)
index cfd94c0..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/bin/sh
-#
-# gst-editing-services autogen.sh
-#
-# Run this to generate all the initial makefiles, etc.
-#
-# This file has been generated from common/autogen.sh.in via common/update-autogen
-
-
-test -n "$srcdir" || srcdir=`dirname "$0"`
-test -n "$srcdir" || srcdir=.
-
-olddir=`pwd`
-cd "$srcdir"
-
-package=gst-editing-services
-srcfile=gst-editing-services.doap
-
-# Make sure we have common
-if test ! -f common/gst-autogen.sh;
-then
-  echo "+ Setting up common submodule"
-  git submodule init
-fi
-git submodule update
-
-# source helper functions
-if test ! -f common/gst-autogen.sh;
-then
-  echo There is something wrong with your source tree.
-  echo You are missing common/gst-autogen.sh
-  exit 1
-fi
-. common/gst-autogen.sh
-
-# install pre-commit hook for doing clean commits
-if test ! \( -x .git/hooks/pre-commit -a -L .git/hooks/pre-commit \);
-then
-    rm -f .git/hooks/pre-commit
-    if ! ln -s ../../common/hooks/pre-commit.hook .git/hooks/pre-commit 2> /dev/null
-    then
-        echo "Failed to create commit hook symlink, copying instead ..."
-        cp common/hooks/pre-commit.hook .git/hooks/pre-commit
-    fi
-fi
-
-# GNU gettext automake support doesn't get along with git.
-# https://bugzilla.gnome.org/show_bug.cgi?id=661128
-if test -d po ; then
-  touch -t 200001010000 po/gst-editing-services-1.0.pot
-fi
-
-CONFIGURE_DEF_OPT='--enable-maintainer-mode --disable-gtk-doc'
-
-if test "x$package" = "xgstreamer"; then
-  CONFIGURE_DEF_OPT="$CONFIGURE_DEF_OPT --enable-failing-tests --enable-poisoning"
-elif test "x$package" = "xgst-plugins-bad"; then
-  CONFIGURE_DEF_OPT="$CONFIGURE_DEF_OPT --with-player-tests"
-fi
-
-autogen_options $@
-
-printf "+ check for build tools"
-if test -z "$NOCHECK"; then
-  echo
-
-  printf "  checking for autoreconf ... "
-  echo
-  which "autoreconf" 2>/dev/null || {
-    echo "not found! Please install the autoconf package."
-    exit 1
-  }
-
-  printf "  checking for pkg-config ... "
-  echo
-  which "pkg-config" 2>/dev/null || {
-    echo "not found! Please install pkg-config."
-    exit 1
-  }
-else
-  echo ": skipped version checks"
-fi
-
-# if no arguments specified then this will be printed
-if test -z "$*" && test -z "$NOCONFIGURE"; then
-  echo "+ checking for autogen.sh options"
-  echo "  This autogen script will automatically run ./configure as:"
-  echo "  ./configure $CONFIGURE_DEF_OPT"
-  echo "  To pass any additional options, please specify them on the $0"
-  echo "  command line."
-fi
-
-toplevel_check $srcfile
-
-# autopoint
-if test -d po && grep ^AM_GNU_GETTEXT_VERSION configure.ac >/dev/null ; then
-  tool_run "autopoint" "--force"
-fi
-
-# aclocal
-if test -f acinclude.m4; then rm acinclude.m4; fi
-
-autoreconf --force --install || exit 1
-
-test -n "$NOCONFIGURE" && {
-  echo "+ skipping configure stage for package $package, as requested."
-  echo "+ autogen.sh done."
-  exit 0
-}
-
-cd "$olddir"
-
-echo "+ running configure ... "
-test ! -z "$CONFIGURE_DEF_OPT" && echo "  default flags:  $CONFIGURE_DEF_OPT"
-test ! -z "$CONFIGURE_EXT_OPT" && echo "  external flags: $CONFIGURE_EXT_OPT"
-echo
-
-echo "$srcdir/configure" $CONFIGURE_DEF_OPT $CONFIGURE_EXT_OPT
-"$srcdir/configure" $CONFIGURE_DEF_OPT $CONFIGURE_EXT_OPT || {
-        echo "  configure failed"
-        exit 1
-}
-
-echo "Now type 'make' to compile $package."
diff --git a/bindings/Makefile.am b/bindings/Makefile.am
deleted file mode 100644 (file)
index be92781..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-SUBDIRS = 
-
-if WITH_PYTHON
-    SUBDIRS += python
-endif
-
diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am
deleted file mode 100644 (file)
index 14602de..0000000
+++ /dev/null
@@ -1 +0,0 @@
-SUBDIRS = gi
diff --git a/bindings/python/gi/Makefile.am b/bindings/python/gi/Makefile.am
deleted file mode 100644 (file)
index eeba093..0000000
+++ /dev/null
@@ -1 +0,0 @@
-SUBDIRS = overrides
index 9898408..1a9f1c4 100644 (file)
@@ -27,6 +27,7 @@
 import sys
 from ..overrides import override
 from ..importer import modules
+from gi.repository import GObject
 
 
 if sys.version_info >= (3, 0):
@@ -50,37 +51,45 @@ python module to use with GES 0.10"
 
     warnings.warn(warn_msg, RuntimeWarning)
 
+def __timeline_element__repr__(self):
+    return "%s [%s (%s) %s]" % (
+        self.props.name,
+        Gst.TIME_ARGS(self.props.start),
+        Gst.TIME_ARGS(self.props.in_point),
+        Gst.TIME_ARGS(self.props.duration),
+    )
+
+__prev_set_child_property = GES.TimelineElement.set_child_property
+def __timeline_element_set_child_property(self, prop_name, prop_value):
+    res, _, pspec = GES.TimelineElement.lookup_child(self, prop_name)
+    if not res:
+        return res
 
-class TrackElement(GES.TrackElement):
-    def set_child_property(self, prop_name, prop_value):
-        return TimelineElement.set_child_property(self, prop_name, prop_value)
-
-
-TrackElement = override(TrackElement)
-__all__.append('TrackElement')
+    v = GObject.Value()
+    v.init(pspec.value_type)
+    v.set_value(prop_value)
 
+    return __prev_set_child_property(self, prop_name, v)
 
-class TimelineElement(GES.TimelineElement):
-    def __repr__(self):
-        return "%s [%s (%s) %s]" % (
-            self.props.name,
-            Gst.TIME_ARGS(self.props.start),
-            Gst.TIME_ARGS(self.props.in_point),
-            Gst.TIME_ARGS(self.props.duration),
-        )
 
-    def set_child_property(self, prop_name, prop_value):
-        res, child, unused_pspec = GES.TimelineElement.lookup_child(self, prop_name)
-        if not res:
-            return res
+GES.TimelineElement.__repr__ = __timeline_element__repr__
+GES.TimelineElement.set_child_property = __timeline_element_set_child_property
+GES.TrackElement.set_child_property = GES.TimelineElement.set_child_property
+GES.Container.edit = GES.TimelineElement.edit
 
-        child.set_property(prop_name, prop_value)
-        return res
+__prev_asset_repr = GES.Asset.__repr__
+def __asset__repr__(self):
+    return "%s(%s)" % (__prev_asset_repr(self), self.props.id)
 
+GES.Asset.__repr__ = __asset__repr__
 
-TimelineElement = override(TimelineElement)
-__all__.append('TimelineElement')
+def __timeline_iter_clips(self):
+    """Iterate all clips in a timeline"""
+    for layer in self.get_layers():
+        for clip in layer.get_clips():
+            yield clip
 
+GES.Timeline.iter_clips = __timeline_iter_clips
 
 try:
     from gi.repository import Gst
diff --git a/bindings/python/gi/overrides/Makefile.am b/bindings/python/gi/overrides/Makefile.am
deleted file mode 100644 (file)
index c876b5e..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-pygesdir = $(pkgpyexecdir)
-pyges_PYTHON = GES.py
-
-EXTRA_DIST = GES.py
diff --git a/common b/common
deleted file mode 160000 (submodule)
index 59cb678..0000000
--- a/common
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 59cb678164719ff59dcf6c8b93df4617a1075d11
diff --git a/configure.ac b/configure.ac
deleted file mode 100644 (file)
index 1a7558a..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-AC_PREREQ(2.62)
-
-dnl initialize autoconf
-dnl when going to/from release please set the nano (fourth number) right !
-dnl releases only do Wall, cvs and prerelease does Werror too
-AC_INIT(GStreamer Editing Services, 1.16.2,
-    http://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer,
-    gstreamer-editing-services)
-
-AG_GST_INIT
-
-dnl initialize automake
-AM_INIT_AUTOMAKE([-Wno-portability 1.11 no-dist-gzip dist-xz tar-ustar subdir-objects])
-
-dnl define PACKAGE_VERSION_* variables
-AS_VERSION
-
-dnl check if this is a release version
-AS_NANO(GST_GIT="no", GST_GIT="yes")
-
-dnl can autoconf find the source ?
-AC_CONFIG_SRCDIR([ges/ges-timeline.c])
-
-dnl define the output header for config
-AC_CONFIG_HEADERS([config.h])
-
-dnl AM_MAINTAINER_MODE only provides the option to configure to enable it
-AM_MAINTAINER_MODE([enable])
-
-dnl sets host_* variables
-AC_CANONICAL_HOST
-
-dnl use pretty build output with automake >= 1.11
-m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])],
- [AM_DEFAULT_VERBOSITY=1
- AC_SUBST(AM_DEFAULT_VERBOSITY)])
-
-dnl GES versioning, this is mostly informational
-GES_VERSION_MAJOR=$PACKAGE_VERSION_MAJOR
-GES_VERSION_MINOR=$PACKAGE_VERSION_MINOR
-GES_VERSION_MICRO=$PACKAGE_VERSION_MICRO
-GES_VERSION_NANO=$PACKAGE_VERSION_NANO
-AC_SUBST(GES_VERSION_MAJOR)
-AC_SUBST(GES_VERSION_MINOR)
-AC_SUBST(GES_VERSION_MICRO)
-AC_SUBST(GES_VERSION_NANO)
-
-dnl our libraries and install dirs use major.minor as a version
-GST_API_VERSION=$GST_EDITING_SERVICES_VERSION_MAJOR.$GST_EDITING_SERVICES_VERSION_MINOR
-dnl we override it here if we need to for the release candidate of new series
-GST_API_VERSION=1.0
-AC_SUBST(GST_API_VERSION)
-
-AS_LIBTOOL(GST, 1602, 0, 1602)
-
-dnl *** required versions of GStreamer stuff ***
-GST_REQ=1.16.2
-GSTPB_REQ=1.16.2
-
-dnl *** autotools stuff ****
-
-dnl allow for different autotools
-AS_AUTOTOOLS_ALTERNATE
-
-dnl Add parameters for aclocal
-AC_SUBST(ACLOCAL_AMFLAGS, "-I m4 -I common/m4")
-AC_CONFIG_MACRO_DIR([m4])
-
-dnl *** check for arguments to configure ***
-
-AG_GST_ARG_DISABLE_FATAL_WARNINGS
-AG_GST_ARG_ENABLE_EXTRA_CHECKS
-
-AG_GST_ARG_DEBUG
-AG_GST_ARG_PROFILING
-AG_GST_ARG_VALGRIND
-AG_GST_ARG_GCOV
-
-AG_GST_ARG_EXAMPLES
-
-AG_GST_ARG_WITH_PKG_CONFIG_PATH
-AG_GST_ARG_WITH_PACKAGE_NAME
-AG_GST_ARG_WITH_PACKAGE_ORIGIN
-
-AG_GST_PKG_CONFIG_PATH
-
-AG_GST_FLEX_CHECK
-
-dnl *** checks for platform ***
-
-dnl * hardware/architecture *
-
-dnl common/m4/gst-arch.m4
-dnl check CPU type
-AG_GST_ARCH
-
-dnl Determine endianness
-AC_C_BIGENDIAN
-
-dnl *** checks for programs ***
-
-dnl find a compiler
-AC_PROG_CC
-
-dnl check if the compiler supports '-c' and '-o' options
-AM_PROG_CC_C_O
-
-dnl check if the compiler supports do while(0) macros
-AG_GST_CHECK_DOWHILE_MACROS
-
-AC_PATH_PROG(VALGRIND_PATH, valgrind, no)
-AM_CONDITIONAL(HAVE_VALGRIND, test ! "x$VALGRIND_PATH" = "xno")
-
-dnl check for gobject-introspection
-GOBJECT_INTROSPECTION_CHECK([0.9.6])
-
-dnl check for documentation tools
-AG_GST_DOCBOOK_CHECK
-GTK_DOC_CHECK([1.3])
-AS_PATH_PYTHON([2.1])
-AG_GST_PLUGIN_DOCS([1.3],[2.1])
-
-dnl check for pygobject
-AC_SUBST(PYGOBJECT_REQ, 3.0)
-PKG_CHECK_MODULES(PYGOBJECT, pygobject-3.0 >= $PYGOBJECT_REQ,
-  [
-    HAVE_PYGOBJECT=yes
-  ], HAVE_PYGOBJECT=no)
-
-AM_CONDITIONAL(WITH_PYTHON, [test "x$HAVE_PYGOBJECT" = "xyes"])
-
-dnl check for gst-validate
-PKG_CHECK_MODULES(GST_VALIDATE, gst-validate-1.0 >= 1.12.1,
-  [
-    HAVE_GST_VALIDATE=yes
-    AC_DEFINE(HAVE_GST_VALIDATE, 1, [Define if build with gst-validate support])
-  ], HAVE_GST_VALIDATE=no)
-
-AC_SUBST(GST_VALIDATE_CFLAGS)
-AC_SUBST(GST_VALIDATE_LIBS)
-AM_CONDITIONAL(HAVE_GST_VALIDATE, [test "x$HAVE_GST_VALIDATE" = "xyes"])
-
-dnl needed for scenarios definition files
-GST_PREFIX="`$PKG_CONFIG --variable=prefix gstreamer-$GST_API_VERSION`"
-AC_SUBST(GST_PREFIX)
-GST_DATADIR="$GST_PREFIX/share"
-AC_DEFINE_UNQUOTED(GST_DATADIR, "$GST_DATADIR", [system wide data directory])
-
-dnl check for bash completion
-AC_ARG_WITH([bash-completion-dir],
-    AS_HELP_STRING([--with-bash-completion-dir[=PATH]],
-        [Install the bash auto-completion script in this directory. @<:@default=no@:>@]),
-    [],
-    [with_bash_completion_dir=no])
-
-if test "x$with_bash_completion_dir" = "xyes"; then
-    PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
-        [BASH_COMPLETION_DIR="`pkg-config --variable=completionsdir bash-completion`"],
-        [BASH_COMPLETION_DIR="$datadir/bash-completion/completions"])
-else
-    BASH_COMPLETION_DIR="$with_bash_completion_dir"
-fi
-
-AC_SUBST([BASH_COMPLETION_DIR])
-AM_CONDITIONAL([ENABLE_BASH_COMPLETION],[test "x$with_bash_completion_dir" != "xno"])
-
-dnl *** checks for libraries ***
-
-dnl check for libm, for sin() etc.
-# LT_LIB_M
-# AC_SUBST(LIBM)
-
-dnl *** checks for header files ***
-
-AC_CHECK_HEADERS([unistd.h], HAVE_UNISTD_H=yes)
-AM_CONDITIONAL(HAVE_UNISTD_H, test "x$HAVE_UNISTD_H" = "xyes")
-
-if test "x$HAVE_UNISTD_H" != "xyes"; then
-  GST_PLUGINS_SELECTED=`echo $GST_PLUGINS_SELECTED | $SED -e s/festival//`
-fi
-
-dnl *** checks for gst-validate-launcher ***
-
-AC_CHECK_PROG(GST_VALIDATE_LAUNCHER, gst-validate-launcher, yes)
-AM_CONDITIONAL(HAVE_GST_VALIDATE_LAUNCHER, [test "x$GST_VALIDATE_LAUNCHER" = "xyes"])
-
-dnl *** checks for types/defines ***
-
-dnl *** checks for structures ***
-
-dnl *** checks for compiler characteristics ***
-
-dnl *** checks for library functions ***
-
-dnl *** checks for headers ***
-
-dnl *** checks for dependency libraries ***
-
-dnl GLib is required
-AG_GST_GLIB_CHECK([2.40.0])
-
-PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.16, HAVE_GIO=yes, HAVE_GIO=no)
-AC_SUBST(GIO_CFLAGS)
-AC_SUBST(GIO_LIBS)
-
-dnl checks for gstreamer
-dnl uninstalled is selected preferentially -- see pkg-config(1)
-AG_GST_CHECK_GST($GST_API_VERSION, [$GST_REQ], yes)
-AG_GST_CHECK_GST_BASE($GST_API_VERSION, [$GST_REQ], yes)
-#AG_GST_CHECK_GST_GDP($GST_API_VERSION, [$GST_REQ], yes)
-AG_GST_CHECK_GST_CHECK($GST_API_VERSION, [$GST_REQ], no)
-AG_GST_CHECK_GST_CONTROLLER($GST_API_VERSION, [$GST_REQ], yes)
-AG_GST_CHECK_GST_PLUGINS_BASE($GST_API_VERSION, [$GSTPB_REQ], yes)
-AG_GST_CHECK_GST_PLUGINS_BAD($GST_API_VERSION, [$GST_REQ], yes)
-AG_GST_CHECK_GST_PLUGINS_GOOD($GST_API_VERSION, [$GST_REQ], yes)
-AM_CONDITIONAL(HAVE_GST_CHECK, test "x$HAVE_GST_CHECK" = "xyes")
-
-AG_GST_ARG_WITH_PLUGINS
-AG_GST_CHECK_PLUGIN(plugins)
-
-dnl set location of plugin directory
-AG_GST_SET_PLUGINDIR
-
-GSTPB_PLUGINS_DIR=`$PKG_CONFIG gstreamer-plugins-base-$GST_API_VERSION --variable pluginsdir`
-AC_SUBST(GSTPB_PLUGINS_DIR)
-AC_MSG_NOTICE(Using GStreamer Base Plugins in $GSTPB_PLUGINS_DIR)
-
-dnl check for gstreamer-pbutils
-PKG_CHECK_MODULES(GST_PBUTILS, gstreamer-pbutils-$GST_API_VERSION, HAVE_GST_PBUTILS="yes", HAVE_GST_PBUTILS="no")
-if test "x$HAVE_GST_PBUTILS" != "xyes"; then
-  AC_ERROR([gst-pbutils is required for rendering support])
-fi
-AC_SUBST(GST_PBUTILS_LIBS)
-AC_SUBST(GST_PBUTILS_CFLAGS)
-
-dnl check for gst-controller
-PKG_CHECK_MODULES(GST_CONTROLLER, gstreamer-controller-$GST_API_VERSION, HAVE_GST_CONTROLLER="yes", HAVE_GST_CONROLLER="no")
-if test "x$HAVE_GST_CONTROLLER" != "xyes"; then
-  AC_ERROR([gst-controller is required for transition support])
-fi
-AC_SUBST(GST_CONTROLLER_LIBS)
-AC_SUBST(GST_CONTROLLER_CFLAGS)
-
-dnl check for gstvideo
-PKG_CHECK_MODULES(GST_VIDEO, gstreamer-video-$GST_API_VERSION, HAVE_GST_VIDEO="yes", HAVE_GST_CONROLLER="no")
-if test "x$HAVE_GST_VIDEO" != "xyes"; then
-  AC_ERROR([gst-video is required for transition support])
-fi
-AC_SUBST(GST_VIDEO_LIBS)
-AC_SUBST(GST_VIDEO_CFLAGS)
-
-dnl Check for documentation xrefs
-GLIB_PREFIX="`$PKG_CONFIG --variable=prefix glib-2.0`"
-GST_PREFIX="`$PKG_CONFIG --variable=prefix gstreamer-$GST_API_VERSION`"
-GSTPB_PREFIX="`$PKG_CONFIG --variable=prefix gstreamer-plugins-base-$GST_API_VERSION`"
-AC_SUBST(GLIB_PREFIX)
-AC_SUBST(GST_PREFIX)
-AC_SUBST(GSTPB_PREFIX)
-
-dnl pitivi formatter needs libxml
-PKG_CHECK_MODULES(XML, libxml-2.0, HAVE_LIBXML="yes", HAVE_LIBXML="no")
-if test "x$HAVE_LIBXML" != "xyes"; then
-  AC_ERROR([libxml2 is required])
-fi
-AC_SUBST(XML_LIBS)
-AC_SUBST(XML_CFLAGS)
-
-dnl GTK is optional and only used in examples
-HAVE_GTK=no
-HAVE_GTK_X11=no
-GTK_REQ=3.0.0
-if test "x$BUILD_EXAMPLES" = "xyes"; then
-  PKG_CHECK_MODULES(GTK, gtk+-3.0 >= $GTK_REQ, HAVE_GTK=yes, HAVE_GTK=no)
-  dnl some examples need gtk+-x11
-  PKG_CHECK_MODULES(GTK_X11, gtk+-x11-3.0 >= $GTK_REQ, HAVE_GTK_X11=yes, HAVE_GTK_X11=no)
-  AC_SUBST(GTK_LIBS)
-  AC_SUBST(GTK_CFLAGS)
-fi
-AM_CONDITIONAL(HAVE_GTK, test "x$HAVE_GTK" = "xyes")
-AM_CONDITIONAL(HAVE_GTK_X11, test "x$HAVE_GTK_X11" = "xyes")
-
-dnl Check for -Bsymbolic-functions linker flag used to avoid
-dnl intra-library PLT jumps, if available.
-AC_ARG_ENABLE(Bsymbolic,
-              [AS_HELP_STRING([--disable-Bsymbolic],[avoid linking with -Bsymbolic])],,
-              [SAVED_LDFLAGS="${LDFLAGS}" SAVED_LIBS="${LIBS}"
-               AC_MSG_CHECKING([for -Bsymbolic-functions linker flag])
-               LDFLAGS=-Wl,-Bsymbolic-functions
-               LIBS=
-               AC_TRY_LINK([], [return 0],
-                           AC_MSG_RESULT(yes)
-                           enable_Bsymbolic=yes,
-                           AC_MSG_RESULT(no)
-                           enable_Bsymbolic=no)
-               LDFLAGS="${SAVED_LDFLAGS}" LIBS="${SAVED_LIBS}"])
-
-dnl building of benchmarks
-AC_ARG_ENABLE(benchmarks,
-  AS_HELP_STRING([--disable-benchmarks],[disable building benchmarks apps]),
-  [
-    case "${enableval}" in
-      yes) BUILD_BENCHMARKS=yes ;;
-      no)  BUILD_BENCHMARKS=no ;;
-      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-benchmarks) ;;
-    esac
-  ],
-[BUILD_BENCHMARKS=yes]) dnl Default value
-AM_CONDITIONAL(BUILD_BENCHMARKS, test "x$BUILD_BENCHMARKS" = "xyes")
-
-dnl set license and copyright notice
-GST_LICENSE="LGPL"
-AC_DEFINE_UNQUOTED(GST_LICENSE, "$GST_LICENSE", [GStreamer license])
-AC_SUBST(GST_LICENSE)
-
-dnl define LIBDIR so we can inform people where we live
-AS_AC_EXPAND(LIBDIR, $libdir)
-AC_DEFINE_UNQUOTED(LIBDIR, "$LIBDIR", [library dir])
-
-dnl set location of plugin directory
-AG_GST_SET_PLUGINDIR
-
-dnl define an ERROR_CFLAGS Makefile variable
-AG_GST_SET_ERROR_CFLAGS($GST_GIT, [-Wmissing-declarations -Wmissing-prototypes -Wredundant-decls -Wundef \
-                                  -Wwrite-strings -Wformat-security -Wold-style-definition \
-                                  -Winit-self -Wmissing-include-dirs -Waddress -Waggregate-return -Wno-multichar \
-                                  -Wnested-externs])
-
-dnl define correct level for debugging messages
-AG_GST_SET_LEVEL_DEFAULT($GST_GIT)
-
-dnl *** finalize CFLAGS, LDFLAGS, LIBS
-
-dnl Overview:
-dnl GST_OPTION_CFLAGS:  common flags for profiling, debugging, errors, ...
-dnl GST_*:              flags shared by built objects to link against GStreamer
-dnl GST_ALL_LDFLAGS:    linker flags shared by all
-dnl GST_LIB_LDFLAGS:    additional linker flags for all libaries
-dnl GST_LT_LDFLAGS:     library versioning of our libraries
-dnl GST_PLUGIN_LDFLAGS: flags to be used for all plugins
-
-dnl GST_OPTION_CFLAGS
-if test "x$USE_DEBUG" = xyes; then
-   PROFILE_CFLAGS="-g"
-fi
-AC_SUBST(PROFILE_CFLAGS)
-
-if test "x$PACKAGE_VERSION_NANO" = "x1"; then
-  dnl Define _only_ during CVS (not pre-releases or releases)
-  DEPRECATED_CFLAGS="-DGST_DISABLE_DEPRECATED"
-else
-  DEPRECATED_CFLAGS=""
-fi
-AC_SUBST(DEPRECATED_CFLAGS)
-
-VISIBILITY_CFLAGS=""
-AS_COMPILER_FLAG([-fvisibility=hidden], [
-  VISIBILITY_CFLAGS="-fvisibility=hidden"
-  AC_DEFINE(GST_API_EXPORT, [extern __attribute__ ((visibility ("default")))], [public symbol export define])
-], [
-  VISIBILITY_CFLAGS=""
-  AC_DEFINE(GST_API_EXPORT, [extern], [public symbol export define])
-])
-AC_SUBST(VISIBILITY_CFLAGS)
-
-dnl disable strict aliasing
-AS_COMPILER_FLAG([-fno-strict-aliasing], [EXTRA_CFLAGS="-fno-strict-aliasing"])
-AC_SUBST(EXTRA_CFLAGS)
-
-dnl every flag in GST_OPTION_CFLAGS can be overridden
-dnl at make time with e.g. make ERROR_CFLAGS=""
-GST_OPTION_CFLAGS="\$(WARNING_CFLAGS) \$(DEBUG_CFLAGS) \$(PROFILE_CFLAGS) \$(GCOV_CFLAGS) \$(OPT_CFLAGS) \$(DEPRECATED_CFLAGS)"
-AC_SUBST(GST_OPTION_CFLAGS)
-
-dnl FIXME: do we want to rename to GST_ALL_* ?
-dnl prefer internal headers to already installed ones
-dnl also add builddir include for enumtypes and marshal
-dnl add GST_OPTION_CFLAGS, but overridable
-GST_CFLAGS="$GST_CFLAGS $EXTRA_CFLAGS \$(GST_OPTION_CFLAGS) \$(ERROR_CFLAGS) \$(VISIBILITY_CFLAGS) -DGST_USE_UNSTABLE_API"
-AC_SUBST(GST_CFLAGS)
-AC_SUBST(GST_LIBS)
-
-dnl LDFLAGS really should only contain flags, not libs - they get added before
-dnl whatevertarget_LIBS and -L flags here affect the rest of the linking
-GST_ALL_LDFLAGS="-no-undefined"
-if test "x${enable_Bsymbolic}" = "xyes"; then
-  GST_ALL_LDFLAGS="$GST_ALL_LDFLAGS -Wl,-Bsymbolic-functions"
-fi
-AC_SUBST(GST_ALL_LDFLAGS)
-
-dnl GST_LIB_LDFLAGS
-dnl linker flags shared by all libraries
-dnl LDFLAGS modifier defining exported symbols from built libraries
-GST_LIB_LDFLAGS=""
-AC_SUBST(GST_LIB_LDFLAGS)
-
-dnl GST_PLUGIN_LDFLAGS must only contain flags, not libs - they get added before
-dnl whatevertarget_LIBS and -L flags here affect the rest of the linking
-GST_PLUGIN_LDFLAGS="-module -avoid-version $GST_ALL_LDFLAGS"
-AC_SUBST(GST_PLUGIN_LDFLAGS)
-
-dnl *** output files ***
-
-dnl po/Makefile.in
-
-AC_CONFIG_FILES(
-Makefile
-ges/ges-version.h
-common/Makefile
-common/m4/Makefile
-m4/Makefile
-ges/Makefile
-tests/Makefile
-tests/check/Makefile
-tests/benchmarks/Makefile
-examples/Makefile
-examples/c/Makefile
-tests/validate/Makefile
-tests/validate/scenarios/Makefile
-tools/Makefile
-docs/Makefile
-docs/version.entities
-docs/libs/Makefile
-pkgconfig/Makefile
-pkgconfig/gst-editing-services.pc
-pkgconfig/gst-editing-services-uninstalled.pc
-plugins/Makefile
-plugins/ges/Makefile
-plugins/nle/Makefile
-bindings/Makefile
-bindings/python/Makefile
-bindings/python/gi/Makefile
-bindings/python/gi/overrides/Makefile
-)
-AC_OUTPUT
diff --git a/docs/Makefile.am b/docs/Makefile.am
deleted file mode 100644 (file)
index 40d622a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-if ENABLE_GTK_DOC
-DOCS_SUBDIRS = libs
-else
-DOCS_SUBDIRS = 
-endif
-
-SUBDIRS = $(DOCS_SUBDIRS)
-DIST_SUBDIRS = libs
-
-EXTRA_DIST = \
-       version.entities.in
-
-upload:
-       @if test "x$(SUBDIRS)" != x; then for a in $(SUBDIRS); do cd $$a; make upload; cd ..; done; fi
diff --git a/docs/base-classes.md b/docs/base-classes.md
new file mode 100644 (file)
index 0000000..8efcd96
--- /dev/null
@@ -0,0 +1 @@
+# Base classes
diff --git a/docs/deprecated.md b/docs/deprecated.md
new file mode 100644 (file)
index 0000000..6ac4a90
--- /dev/null
@@ -0,0 +1,3 @@
+# Deprecated APIS
+
+Those APIs have been deprecated and shouldn't be used in newly written code.
\ No newline at end of file
index f4c1d1d..1bce7ac 100644 (file)
@@ -87,7 +87,7 @@ B. Goals
   ...
 
   vsrcpad = gst_element_get_src_pad(source, "src1");
-  vsinkpad = gst_element_get_request_pad (encbin, "video_%d");
+  vsinkpad = gst_element_request_pad_simple (encbin, "video_%d");
   gst_pad_link(vsrcpad, vsinkpad);
 
   ...
diff --git a/docs/design/time_notes.md b/docs/design/time_notes.md
new file mode 100644 (file)
index 0000000..46180de
--- /dev/null
@@ -0,0 +1,412 @@
+# Time Notes
+
+Some notes on time coordinates and time effects in GES.
+
+## Time Coordinate Definitions
+
+A timeline will have a single time coordinate, which runs from `0` onwards in `GstClockTime`. Each track in the timeline will share the same time.
+
+For a given track, at any given timeline time `time`, we have a stack of `GESTrackElement`s whose interval `[start, start + duration]` contains `time`. The elements are linked in order or priority. Each element will have four time coordinates per *each* unique stack it is part of:
+
++ external sink coordinates: the coordinates used at the boundary between the upstream element and itself. This is the external source coordinates of the upstream element minus `(downstream-start - upstream-start)`. If it has no upstream element, these coordinates do not exist.
++ external source coordinates: the coordinates used at the boundary between the downstream element and itself. This is the external sink coordinates of the downstream element minus `(upstream-start - downstream-start)`. If it has no downstream element, these coordinates can be translated to the timeline coordinates by adding the `start` of the element.
++ internal sink coordinates: the coordinates used for the sink of the first internal `GstElement`. This is the external sink coordinates plus `in-point`.
++ internal source coordinates: the coordinates used at the source of the last internal `GstElement`. This will differ from the internal sink coordinates if one of the `GstElement`s applies a rate-changing effects. This is the external source coordinates plus `in-point`. Note that an element that changes the consumption rate should always have its in-point set to `0`. This is because nleghostpad is not able to 'undo' this shift by `in-point` at the opposite pad.
+
+The following diagram shows where these coordinates are used, and how they are transformed. Below we have a `GESSource`, followed by a `GESOperation` that does not perform any rate-changing effect, followed by a `GESEffect` that does apply a rate-changing effect (and so its `in-point` is `0`).
+
+```
+time                                        coordinate             coordinate
+coords                  object              transformation         transformation
+used                                        upwards                downwards
+______________________________________________________________________________________
+
+            |        source (1)         |
+int. src    |---------------------------|
+            |                           |   + in-point-1           - in-point-1
+ex. src     '==========================='
+                                            - start-1 + start-2    + start-1 - start-2
+ex. sink    .===========================.
+            |                           |   - in-point-2           + in-point-2
+int. sink   |---------------------------|
+            |       operation (2)       |   identity ()            identity ()
+int. src    |---------------------------|
+            |                           |   + in-point-2           - in-point-2
+ex. src     '==========================='
+                                            - start-2 + start-3    + start-2 - start-3
+ex. sink    .===========================.
+            |                           |   - 0                    + 0
+int. sink   |---------------------------|
+            |     time effect (3)       |   f ()                   f^-1 ()
+int. src    |---------------------------|
+            |                           |   + 0                    - 0
+ex. src     '==========================='
+                                            - start-3              + start-3
+timeline    +++++++++++++++++++++++++++++
+                      timeline
+```
+
+The given function `f` summarises how a seek will transform as it goes from from the source to the sink of the internal `GstElement`, and `f^-1` summarises how a segment stream time will transform.
+
+In particular, `f` will be a function
+
+```
+f: [0, MAX] -> [0, G_MAXUINT64 - 1]
+```
+
+where `MAX` is some `guint64`. For what follows, we will only fully support time effects whose function `f`:
+
++ is monotonically increasing. This would exclude time effects that play some later content, and then jump back to earlier content.
++ is 'continuously reversible'. We define `T_d` for the time `t` as the set of all the times `t'` such that `|f (t') - t| <= d`. This property requires that, for any `t` between `f`'s minimum and maximum values, we can choose a small `d` such that `T_d` is not empty, is small and has no gaps. The word "small" refers to an unnoticeable difference (the times are in nanoseconds).  This means that `f` can be approximately reversed at all points between its minimum and maximum, which means that `f^-1` can act as a close inverse of `f`. For a monotonically increasing function, this means that `f` is *steadily* increasing.
+
+ For example, if `f` simply doubles the time, then for time `t = 501`, we can choose `d=1`, and `T_d` would be `{250, 251}`.
+
+ This would exclude a time effect which has a large jump, because there would be a time `t` between this jump, whose `T_d` would be empty for all small `d`.
+
+ This would also exclude a time effect that creates a freeze-frame effect by always seeking to the same spot, because at the time `t` of this freeze-frame, `T_d` would be large for all `d`.
+
++ obeys `f (0) = 0`. This would exclude a time effect that introduces an initial shift in the requested source time.
++ has a `MAX` that is large enough. For example, 24 hours would be fine for a timeline. This would exclude a rate effect with a very large speedup.
++ does not depend on any property outside of the effect element, or on the data it receives. This would exclude a time effect that, say, goes faster if there is more red in the image.
+
+In what follows, a time effect that breaks one of these can still be used, but not all the features will work.
+
+### Translations Handled by `nleobject` Pads
+
+An `nleobject` source pad will translate outgoing times by applying
+
+```
+time-out = time-in + start - in-point
+```
+
+This will translate from the internal source coordinates to the timeline coordinates *if* it is the most downstream element. Similarly, an `nleobject` sink pad will translate incoming times by applying
+
+```
+time-out = time-in - start + in-point
+```
+
+If we have two `nle-object`s, `object-up` and `object-down`, that have their pads linked, then a time `time-up` from `object-up`'s internal `GstElement`, would be translated at the link to
+
+```
+time-down
+ = time-up + start-up - in-point-up - start-down + in-point-down
+```
+
+So the pads will overall translate from the internal source coordinates of the upstream element to the internal sink coordinates of the downstream element.
+
+### Undefined Translations
+
+Note that the coordinate transformation from the timeline time to an upstream time may be undefined depending on the configuration of elements in the timeline. For example, consider the earlier example stack, with the operation starting later than the time effect, such that
+
+```
+d = (start-2 - start-3) > 0
+```
+
+And we choose the `time`
+
+```
+time = start-2
+     = start-3 + d
+```
+
+Then, when `time` is transformed to the external source coordinates of the operation, we have
+
+```
+operation-source-time = f (time - start-3) - start-2 + start-3
+                      = f (d) - d
+```
+
+If the time effect slows down the consumption rate, then `f (d) < d`, which would make the time undefined in the external source coordinates (we can not have a negative `GstClockTime`). Basically, the effect is trying to access content that is before the operation.
+
+We can similarly have an effect that tries to access content that is later than the operation, but this wouldn't lead to an underflow of the time. It can however lead to a request for data that is outside the internal content of the operation.
+
+### Mismatched Coordinates
+
+The coordinates of an element are only defined relative to the stack that they are in. However, if we have no time effects, these coordinates will line up. Consider the following source and operation configuration.
+
+```
+            |        source (1)         |
+            |---------------------------|
+            |                           |
+            '==========================='
+
+.===========================.
+|                           |
+|---------------------------|
+|       operation (2)       |
+|---------------------------|
+|                           |
+'==========================='
+
++++++++++++++++++++++++++++++++++++++++++
+              timeline
+```
+
+This gives us three stacks
+
+```
+                          |      (1)      |            |    (1)    |
+                          |---------------|            |-----------|
+                          |               |            |           |
+                          '===============.            '==========='
+                          0           (s1-s2+d1)   (s1-s2+d1)      d2
+
+ 0        (s2-s1)      (s2-s1)           d1
+ .===========.            .===============.
+ |           |            |               |
+ |-----------|            |---------------|
+ |    (2)    |            |      (2)      |
+ |-----------|            |---------------|
+ |           |            |               |
+ '==========='            '==============='
+ 0        (s2-s1)      (s2-s1)           d1
+
+ +++++++++++++            +++++++++++++++++            +++++++++++++
+s1          s2           s2            (s1+d1)      (s1+d1)     (s2+d2)
+```
+
+where we have written in times in the external coordinates of the elements, where `s1` and `d1` are the `start` and `duration` of the source, and similarly for `s2` and `d2` for the operation. We can see that the edge times of all coordinates match up with their neighbours. Therefore, for both elements, there coordinates across each stack can be combined into a single coordinate system.
+
+Consider that instead of the operation we have a time effect, then we would have
+
+```
+                          |      (1)      |            |    (1)    |
+                          |---------------|            |-----------|
+                          |               |            |           |
+                          '===============.            '==========='
+                      f(s2-s1)          f(d1)      (s1-s2+d1)      d2
+                      -(s2-s1)         -(s2-s1)
+
+f(0)     f(s2-s1)     f(s2-s1)          f(d1)
+ .===========.            .===============.
+ |           |            |               |
+ |-----------|            |---------------|
+ |    (2)    |            |      (2)      |
+ |-----------|            |---------------|
+ |           |            |               |
+ '==========='            '==============='
+ 0        (s2-s1)      (s2-s1)           d1
+
+ +++++++++++++            +++++++++++++++++            +++++++++++++
+s1          s2           s2            (s1+d1)      (s1+d1)     (s2+d2)
+```
+
+We can see that the coordinates of the source now start at `f(s2-s1) - (s2-s1)`, rather than `0`. We can also see that the external source coordinates of the source jump by `(d1 - f(d1))` when the time effect ends. Therefore, most time effects will prevent the coordinates from different stacks from being combined. This can lead to counter-intuitive behaviour.
+
+A further example would be a rate effect with `rate=3` that covers two sources that are side by side. The rate effect will **not** treat this as playing the sources concatenated, at triple speed. Instead, it would play the first source at triple speed, and once it reaches the starting timeline time of the second source, it will start playing the second source instead, but starting from the internal source coordinates
+
+```
+3 * (source-start - rate-start) - (source-start - rate-start) + source-in-point
+ = 2 * (source-start - rate-start) + source-in-point
+```
+
+Note that if this was a slowed down rate, this would have been an undefined (negative) time, as we mentioned earlier.
+
+Therefore, in general, time effects should only be placed at a higher priority than elements that share the same `start` and `duration` as it. Note that it is fine to place an operation with a higher priority on top of a time effect with a different `start` or `duration` because this will not lead to a change in the coordinates.
+
+This is why only a `GESSourceClip` can have time effects added to it.
+
+There is a general exception to this: if a time effect obeys `f (0) = 0`, then it will not introduce mismatched coordinates downstream if it has a later `start` than all the elements it has a higher priority than, **and** its end timeline time matches all of theirs. Note that this is because the effect would only exist in a single stack, and starts by apply no change to the times it receives.
+
+## GESTimelineElement times
+
+The `start` and `duration` of an element use the timeline time coordinates. `in-point` and `max-duration` use the internal source coordinates. These last two should be `0` and `GST_CLOCK_TIME_NONE` respectively for time effects.
+
+## How to Translate Between Time Coordinates of a Clip
+
+Consider a `GESTrackElement` `element` in a `GESClip` `clip` in a timeline. It has `n` `active` elements with higher priority in the same `clip` and track, labelled by `i=1,...,n`, where element 1 has a higher priority than element 2, and so on. Each element has an associated function `f_i` that translates from its external source coordinates to its external sink coordinates. Note that for elements that apply no time effect, this will be an identity, regardless of their `in-point`. We can define the function `F`, such that
+
+```
+F(t) = f_n (f_n-1 ( ... f_1 (t)...))
+```
+
+Note that if each `f_i` has the desired properties, then so will `F`, with the exception that the maximum value it can translate may have become too small. For example, if several rate effects accumulate into a very large speedup.
+
+Given such an `F`, we can translate from the timeline time `t` to the internal source coordinate time of `element` using
+
+```
+F (t - start) + in-point
+```
+
+This is what is done in `ges_clip_get_internal_time_from_timeline_time`.
+
+Note that this works because all the elements in `clip` share the same `start`. Note that this would not work if there existed an overlapping higher priority time effect outside of the clip because the highest priority clip element would **not** be receiving a timeline time at its source pads. This is not a problem if there are non-time effects at higher priority because they will pass through a timeline time unchanged.
+
+If `F` has the desired properties, it will have a well defined inverse `F^-1`, based on the inverses of `f_i`, which we can use to reverse this translation:
+
+```
+F^-1 (t - in-point) + start
+```
+
+This is what is done in `ges_clip_get_timeline_time_from_internal_time`.
+
+## `duration-limit`
+
+The `duration-limit` is meant to be the largest value we can set the clip's `duration` to.
+
+It would be given by the minimum
+
+```
+ges_clip_get_timeline_time_from_internal_time (clip, child, child-max-duration) - start
+```
+
+we calculate amongst all its children that have a `max-duration`. Note that the implementation of `_calculate_duration_limit` does not use this method directly, but it should give the same result.
+
+Note that this would fail if `max-duration` is not reachable through a seek. E.g. if the corresponding function `F` of the time effects acted like
+
+```
+F (t) = t + max-duration + 1
+```
+
+then `F^-1 (t)` will be undefined for `t=max-duartion` because its domain will be `[max-duration + 1, inf)`. Note that this function `F` does not obey `F (0)=0`, so is not supported in GES.
+
+Note that `duration-limit` may not be *exactly* the largest end time possible. If the corresponding function `F` is monotonically increasing, then there is no source time below `max-duration` that could give a larger value, but there may be some times beyond `max-time` that would correspond to the *same* source time. However, these extra times will only differ from the `max-time` by a small amount if `F` is 'continuously reversible', and so `max-time` would be close enough. Otherwise, we would not have a simple way to know which is the actual largest `duration`.
+
+## Trimming a clip
+
+Normally, trimming is meant to keep the internal content in the same position relative to the timeline. If we are applying a non-constant rate effect, it may not be possible to keep all the internal content appearing in the timeline at the same time whilst changing the `start` and `duration`. However, we can keep the start or end frames/samples in the same timeline position.
+
+#### Trimming the start of a clip to a later time
+
+When trimming the start edge of a clip from timeline time `old-start` to `new-start`, where `old-start < new-start <= (old-start + duration)`, we set the `in-point` of the clip's children such that the internal content that appeared at `new-start` before the trim, still appears at `new-start` afterwards.
+
+This would require
+
+```
+new-in-point = old-in-point + F (new-start - old-start)
+```
+
+because this is the internal source time corresponding to `new-start`.
+
+Note that, after we have finished trimming, *assuming* the corresponding `F` has not changed and `F (0) = 0`,
+
+```
+ges_clip_get_internal_timeline_from_timeline_time (clip, child, new-start)
+ = F (new-start - new-start) + new-in-point
+ = new-in-point
+```
+
+So after trimming, `new-start` will correspond to the same source position as before. Note that this would not work if the time effects changed depending on the data they receive (such as a "go faster if we have more red" time effect) because the corresponding `F` would have changed after setting the `in-point`. However, we already stated earlier that these are not supported in GES.
+
+#### Trimming the start of a clip to an earlier time
+
+When trimming the start edge of a clip from timeline time `old-start` to `new-start`, where `new-start < old-start`, we set the `in-point` of the clip's children such that the internal content that appeared at `old-start` before the trim, still appears at `old-start` afterwards.
+
+```
+new-in-point = old-in-point - F (old-start - new-start)
+```
+
+Note that this will fail if the second argument is too big, which indicates that it would be before there is any internal content.
+
+In terms of the function `F` earlier, since this is calculated using the new `start` and old `in-point`, the `source-time` would be
+
+Note, after we have finished trimming, *assuming* the corresponding `F` has not changed,
+
+```
+ges_clip_get_internal_time_from_timeline_time (clip, child, old-start)
+ = F (old-start - new-start) + new-in-point
+ = F (old-start - new-start) + old-in-point - F (old-start - new-start)
+ = old-in-point
+```
+
+So after trimming, `old-start` will correspond to the same source time as before.
+
+Note that `ges_clip_get_internal_time_from_timeline_time` will perform this same calculation if it receives a timeline time before the `start` of the clip. So timeline-tree is simply able to call `ges_clip_get_internal_time_from_timeline_time (clip, child, new_start, error)` in both cases.
+
+#### Trimming the end of a clip
+
+This is as simple as changing the `duration` of the clip since everything will stay at the same timeline position anyway (assuming `F` does not change, as required by GES). It just cannot go above the clip's `duration-limit`.
+
+## Splitting a clip
+
+The `in-point` of the new clip is chosen to match the new `out-point` of the split clip. This won't work well if different core children of the clip will end up with very different `out-point`s. But if these differences are within half a source frame, GES will not complain. The same can happen when trimming a clip, since all the core children must share the same `in-point`.
+
+## Buffer Timestamps
+
+NOTE: As of 21 May, the recommended changes are not implemented in GES. This delves into why translations will be needed for non-linear time effects.
+
+Currently, the `nleobject` pads will leave the buffer times unchanged. Which means that an `nlesource` will send out buffer timestamped using its *internal* source coordinates.
+
+This is in contrast to a `pitch` within an `nleoperation`, which would translate the buffer times from its internal sink coordinates to its internal source coordinates.
+
+Since the internal source coordinates of the `nlesource` do **not** match the internal sink coordinates of the `nleoperation` (they will differ by `in-point`), this will result in buffer times that are not in **any** coordinates.
+
+This will make it difficult to use control bindings which are to be given in stream time, which is linked to the buffer timestamps.
+
+We can explore this in more detail. [According to the GStreamer design docs](https://gstreamer.freedesktop.org/documentation/additional/design/synchronisation.html#stream-time), the stream time is used for
+
++ report the POSITION query in the pipeline
++ the position used in seek events/queries
++ the position used to synchronize controller values
+
+Therefore, in our case, we can say that the stream time at a given position in an nle stack should match the corresponding seek time.
+
+If we have no applied rate, which shouldn't be the case for a normal uses of a timeline, the `stream-time` is given by
+
+```
+    stream-time = buffer.timestamp - seg.start + seg.time
+```
+
+Thus, the stream time is basically the internal source or sink coordinates. In `GESTrackElement` control sources are meant to be given in the internal source coordinates.
+
+We will now look at what these time values **currently** are set to in a stack of an `nlesource` and an `nleoperation` that share the same `start` and `duration`.
+
+Currently, the `nleobject` pads will only change the `seg.time` of the segments it receives by adding or subtracting `(start - in-point)`.
+
+We will assume that the `GstElement` that the `nleoperation` wraps is applying its time effect to `seg.time`, `seg.start` and `buffer.timestamp`, which is given by the function `g`. Note that this is what `pitch` currently does. Its not clear to me what `videorate` does to the `buffer.timestamp`, but it does transform `seg.start` the same way as `seg.time`.
+
+The following is a table of what the `seg.time`, `seg.start` and `buffer.timestamp` values are when *leaving* a pad. The "internal src pad" refers to the source pad of the internal `GstElement`. `s` is the `start` of the objects, and `i` is the `in-point` of the `nlesource`. Following these is what the corresponding stream time would be using these values. The final row is what the corresponding seek position would be coming *into* the pad, if were seeking to the same media time `T`.
+
+```
+           nlesrc       nlesrc       nleop        nleop        nleop
+           internal     external     external     internal     external
+           src pad      src pad      sink pad     src pad      src pad
+
+seg.time   i            s            0            g (0)        g (0) + s
+
+seg.start  i            i            i            g (i)        g (i)
+
+buffer.    T            T            T            g (T)        g (T)
+timestamp
+------------------------------------------------------------------------
+stream     T            T            T            g (T)        g (T)
+time                    - i          - i          - g (i)      - g (i)
+                        + s                       + g (0)      + g (0)
+                                                               + s
+------------------------------------------------------------------------
+seek       T            T            T            g (T - i)    g (T - i)
+time                    - i          - i                       + s
+                        + s
+```
+
+We can see that after the `nleoperation`, the seek time and stream time will generally be out of sync.
+
+Note that if `g` corresponds to a constant rate effect, then
+
+```
+g (t) = r * t
+```
+
+for some rate `r`. Then, at the `nleoperation` external source pad.
+
+```
+stream-time = r * T - r * i + r * 0 + s
+            = g (T - i) + s
+            = seek-time
+```
+
+so the two will match up under the current behaviour for this special case. However, if the rate varies, this will break down.
+
+If, instead, we translate `seg.start` and `buffer.timestamp` in the same way as `seg.time` on the `nleobject` pads, by adding or subtracting `(start - in-point)`, then we will always have
+
+```
+seg.start = seg.time
+```
+
+which means that we would also have
+
+```
+seek-time = stream-time = buffer.timestamp
+```
+
+Finally, it would be a good if the convention for a time effect was to use the *output* stream time in `gst_object_sync_values`, rather than the *input* stream time. This would make them compatible with GES's rule that control sources are given in the internal source coordinates. Luckily, it seems that `pitch` already uses the output stream time. `videorate` doesn't currently use `gst_object_sync_values`.
diff --git a/docs/gst_plugins_cache.json b/docs/gst_plugins_cache.json
new file mode 100644 (file)
index 0000000..b318645
--- /dev/null
@@ -0,0 +1,473 @@
+{
+    "ges": {
+        "description": "GStreamer Editing Services Plugin",
+        "elements": {
+            "gesdemux": {
+                "author": "Thibault Saunier <tsaunier@igalia.com",
+                "description": "Demuxer for complex timeline file formats using GES.",
+                "hierarchy": [
+                    "GESDemux",
+                    "GESBaseBin",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "klass": "Codec/Demux/Editing",
+                "long-name": "GStreamer Editing Services based 'demuxer'",
+                "pad-templates": {
+                    "audio_src": {
+                        "caps": "audio/x-raw(ANY):\n",
+                        "direction": "src",
+                        "presence": "sometimes"
+                    },
+                    "sink": {
+                        "caps": "application/xges:\ntext/x-xptv:\napplication/vnd.pixar.opentimelineio+json:\napplication/vnd.apple-xmeml+xml:\napplication/vnd.apple-fcp+xml:\n",
+                        "direction": "sink",
+                        "presence": "always"
+                    },
+                    "video_src": {
+                        "caps": "video/x-raw(ANY):\n",
+                        "direction": "src",
+                        "presence": "sometimes"
+                    }
+                },
+                "properties": {},
+                "rank": "primary",
+                "signals": {}
+            },
+            "gessrc": {
+                "author": "Thibault Saunier <tsaunier@igalia.com",
+                "description": "Source for GESTimeline.",
+                "hierarchy": [
+                    "GESSrc",
+                    "GESBaseBin",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy",
+                    "GstURIHandler"
+                ],
+                "klass": "Codec/Source/Editing",
+                "long-name": "GStreamer Editing Services based 'source'",
+                "pad-templates": {
+                    "audio_src": {
+                        "caps": "audio/x-raw(ANY):\n",
+                        "direction": "src",
+                        "presence": "sometimes"
+                    },
+                    "video_src": {
+                        "caps": "video/x-raw(ANY):\n",
+                        "direction": "src",
+                        "presence": "sometimes"
+                    }
+                },
+                "properties": {},
+                "rank": "none",
+                "signals": {}
+            }
+        },
+        "filename": "gstges",
+        "license": "LGPL",
+        "other-types": {
+            "GESBaseBin": {
+                "hierarchy": [
+                    "GESBaseBin",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "kind": "object",
+                "properties": {
+                    "timeline": {
+                        "blurb": "Timeline to use in this src.",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "GESTimeline",
+                        "writable": true
+                    }
+                }
+            }
+        },
+        "package": "GStreamer Editing Services",
+        "source": "gst-editing-services",
+        "tracers": {},
+        "url": "Unknown package origin"
+    },
+    "nle": {
+        "description": "GStreamer Non Linear Engine",
+        "elements": {
+            "nlecomposition": {
+                "author": "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>, Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>, Thibault Saunier <tsaunier@gnome.org>",
+                "description": "Combines NLE objects",
+                "hierarchy": [
+                    "NleComposition",
+                    "NleObject",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "klass": "Filter/Editor",
+                "long-name": "GNonLin Composition",
+                "pad-templates": {
+                    "src": {
+                        "caps": "ANY",
+                        "direction": "src",
+                        "presence": "always"
+                    }
+                },
+                "properties": {
+                    "drop-tags": {
+                        "blurb": "Whether the composition should drop tags from its children",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "true",
+                        "mutable": "playing",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": true
+                    },
+                    "id": {
+                        "blurb": "The stream-id of the composition",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "NULL",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gchararray",
+                        "writable": true
+                    }
+                },
+                "rank": "none",
+                "signals": {
+                    "commited": {
+                        "args": [
+                            {
+                                "name": "arg0",
+                                "type": "gboolean"
+                            }
+                        ],
+                        "return-type": "void",
+                        "when": "first"
+                    }
+                }
+            },
+            "nleoperation": {
+                "author": "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>",
+                "description": "Encapsulates filters/effects for use with NLE Objects",
+                "hierarchy": [
+                    "NleOperation",
+                    "NleObject",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "klass": "Filter/Editor",
+                "long-name": "GNonLin Operation",
+                "pad-templates": {
+                    "sink%%d": {
+                        "caps": "ANY",
+                        "direction": "sink",
+                        "presence": "request"
+                    },
+                    "src": {
+                        "caps": "ANY",
+                        "direction": "src",
+                        "presence": "always"
+                    }
+                },
+                "properties": {
+                    "sinks": {
+                        "blurb": "Number of input sinks (-1 for automatic handling)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "1",
+                        "max": "2147483647",
+                        "min": "-1",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gint",
+                        "writable": true
+                    }
+                },
+                "rank": "none",
+                "signals": {
+                    "input-priority-changed": {
+                        "args": [
+                            {
+                                "name": "arg0",
+                                "type": "GstPad"
+                            },
+                            {
+                                "name": "arg1",
+                                "type": "guint"
+                            }
+                        ],
+                        "return-type": "void",
+                        "when": "last"
+                    }
+                }
+            },
+            "nlesource": {
+                "author": "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>",
+                "description": "Manages source elements",
+                "hierarchy": [
+                    "NleSource",
+                    "NleObject",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "klass": "Filter/Editor",
+                "long-name": "GNonLin Source",
+                "pad-templates": {
+                    "src": {
+                        "caps": "ANY",
+                        "direction": "src",
+                        "presence": "always"
+                    }
+                },
+                "properties": {},
+                "rank": "none",
+                "signals": {}
+            },
+            "nleurisource": {
+                "author": "Edward Hervey <bilboed@bilboed.com>",
+                "description": "High-level URI Source element",
+                "hierarchy": [
+                    "NleURISource",
+                    "NleSource",
+                    "NleObject",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "klass": "Filter/Editor",
+                "long-name": "GNonLin URI Source",
+                "pad-templates": {
+                    "src": {
+                        "caps": "ANY",
+                        "direction": "src",
+                        "presence": "sometimes"
+                    }
+                },
+                "properties": {
+                    "uri": {
+                        "blurb": "Uri of the file to use",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "NULL",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gchararray",
+                        "writable": true
+                    }
+                },
+                "rank": "none",
+                "signals": {}
+            }
+        },
+        "filename": "gstnle",
+        "license": "LGPL",
+        "other-types": {
+            "NleObject": {
+                "hierarchy": [
+                    "NleObject",
+                    "GstBin",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstChildProxy"
+                ],
+                "kind": "object",
+                "properties": {
+                    "active": {
+                        "blurb": "Use this object in the NleComposition",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "true",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": true
+                    },
+                    "caps": {
+                        "blurb": "Caps used to filter/choose the output stream",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "ANY",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "GstCaps",
+                        "writable": true
+                    },
+                    "duration": {
+                        "blurb": "Outgoing duration (in nanoseconds)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "0",
+                        "max": "9223372036854775807",
+                        "min": "0",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gint64",
+                        "writable": true
+                    },
+                    "expandable": {
+                        "blurb": "Expand to the full duration of the container composition",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "false",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": true
+                    },
+                    "inpoint": {
+                        "blurb": "The media start position (in nanoseconds)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "18446744073709551615",
+                        "max": "18446744073709551615",
+                        "min": "0",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "guint64",
+                        "writable": true
+                    },
+                    "media-duration-factor": {
+                        "blurb": "The relative rate caused by this object",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "1",
+                        "max": "1.79769e+308",
+                        "min": "0.01",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gdouble",
+                        "writable": true
+                    },
+                    "priority": {
+                        "blurb": "The priority of the object (0 = highest priority)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "0",
+                        "max": "-1",
+                        "min": "0",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "guint",
+                        "writable": true
+                    },
+                    "start": {
+                        "blurb": "The start position relative to the parent (in nanoseconds)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "0",
+                        "max": "18446744073709551615",
+                        "min": "0",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "guint64",
+                        "writable": true
+                    },
+                    "stop": {
+                        "blurb": "The stop position relative to the parent (in nanoseconds)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "0",
+                        "max": "18446744073709551615",
+                        "min": "0",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "guint64",
+                        "writable": false
+                    }
+                },
+                "signals": {
+                    "commit": {
+                        "action": true,
+                        "args": [
+                            {
+                                "name": "arg0",
+                                "type": "gboolean"
+                            }
+                        ],
+                        "return-type": "gboolean",
+                        "when": "last"
+                    }
+                }
+            }
+        },
+        "package": "GStreamer Editing Services",
+        "source": "gst-editing-services",
+        "tracers": {},
+        "url": "Unknown package origin"
+    }
+}
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644 (file)
index 0000000..e2e39da
--- /dev/null
@@ -0,0 +1,68 @@
+---
+short-description:  GStreamer Editing Services API reference.
+...
+
+# GStreamer Editing Services
+
+The "GStreamer Editing Services" is a library to simplify the creation
+of multimedia editing applications. Based on the GStreamer multimedia framework
+and the GNonLin set of plugins, its goals are to suit all types of editing-related
+applications.
+
+The GStreamer Editing Services are cross-platform and work on most UNIX-like
+platform as well as Windows. It is released under the GNU Library General Public License
+(GNU LGPL).
+
+## Goals of GStreamer Editing Services
+
+The GStreamer multimedia framework and the accompanying GNonLin set of
+plugins for non-linear editing offer all the building blocks for:
+
+-   Decoding and encoding to a wide variety of formats, through all the
+    available GStreamer plugins.
+
+-   Easily choosing segments of streams and arranging them through time
+    through the GNonLin set of plugins.
+
+But all those building blocks only offer stream-level access, which
+results in developers who want to write non-linear editors to write a
+consequent amount of code to get to the level of *non-linear editing*
+notions which are closer and more meaningful for the end-user (and
+therefore the application).
+
+The GStreamer Editing Services (hereafter GES) aims to fill the gap
+between GStreamer/GNonLin and the application developer by offering a
+series of classes to simplify the creation of many kind of
+editing-related applications.
+
+## Architecture
+
+### Timeline and TimelinePipeline
+
+The most top-level object encapsulating every other object is the
+[GESTimeline](GESTimeline). It is the central object for any editing project.
+
+The `GESTimeline` is a `GstElement`. It can therefore be used in any
+GStreamer pipeline like any other object.
+
+### Tracks and Layers
+
+The GESTimeline can contain two types of objects (seen in
+"Layers and Tracks"):
+
+-   Layers - Corresponds to the user-visible arrangement of clips, and
+    what you primarily interact with as an application developer. A
+    minimalistic timeline would only have one layer, but a more complex
+    editing application could use as many as needed.
+
+-   Tracks - Corresponds to the output streams in GStreamer. A typical
+    GESTimeline, aimed at a video editing application, would have an
+    audio track and a video track. A GESTimeline for an audio editing
+    application would only require an audio track. Multiple layers can
+    be related to each track.
+
+![Layers and Tracks](images/layer_track_overview.png)
+
+In order to reduce even more the amount of GStreamer interaction the
+application developer has to deal with, a convenience GstPipeline has
+been made available specifically for Timelines : [GESPipeline](GESPipeline).
diff --git a/docs/libs/.gitignore b/docs/libs/.gitignore
deleted file mode 100644 (file)
index 65f8c76..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-ges-decl-list.txt
-ges-decl.txt
-ges-overrides.txt
-ges-undeclared.txt
-ges-undocumented.txt
-ges-unused.txt
-ges.args
-ges.hierarchy
-ges.interfaces
-ges.prerequisites
-ges.signals
-*.stamp
-
-html/
-tmpl/
-xml/
-doc-registry.xml
diff --git a/docs/libs/GESAudioTestSource-children-props.md b/docs/libs/GESAudioTestSource-children-props.md
new file mode 100644 (file)
index 0000000..382c19c
--- /dev/null
@@ -0,0 +1,32 @@
+#### `freq`
+
+Frequency of test signal. The sample rate needs to be at least 2 times higher.
+
+Value type: #gdouble
+
+See #audiotestsrc:freq
+
+#### `mute`
+
+mute channel
+
+Value type: #gboolean
+
+See #volume:mute
+
+#### `volume`
+
+volume factor, 1.0=100%
+
+Value type: #gdouble
+
+See #volume:volume
+
+#### `volume`
+
+Volume of test signal
+
+Value type: #gdouble
+
+See #audiotestsrc:volume
+
diff --git a/docs/libs/GESAudioUriSource-children-props.md b/docs/libs/GESAudioUriSource-children-props.md
new file mode 100644 (file)
index 0000000..0e39a34
--- /dev/null
@@ -0,0 +1,16 @@
+#### `mute`
+
+mute channel
+
+Value type: #gboolean
+
+See #volume:mute
+
+#### `volume`
+
+volume factor, 1.0=100%
+
+Value type: #gdouble
+
+See #volume:volume
+
diff --git a/docs/libs/GESTimeOverlayClip-children-props.md b/docs/libs/GESTimeOverlayClip-children-props.md
new file mode 100644 (file)
index 0000000..999fe81
--- /dev/null
@@ -0,0 +1,201 @@
+#### `alpha`
+
+alpha of the stream
+
+Value type: #gdouble
+
+#### `background-color`
+
+Background color to use (big-endian ARGB)
+
+Value type: #guint
+
+See #videotestsrc:background-color
+
+#### `font-desc`
+
+Pango font description of font to be used for rendering. See documentation of
+pango_font_description_from_string for syntax.
+
+Value type: #gchararray
+
+See #GstBaseTextOverlay:font-desc
+
+#### `foreground-color`
+
+Foreground color to use (big-endian ARGB)
+
+Value type: #guint
+
+See #videotestsrc:foreground-color
+
+#### `freq`
+
+Frequency of test signal. The sample rate needs to be at least 2 times higher.
+
+Value type: #gdouble
+
+See #audiotestsrc:freq
+
+#### `halignment`
+
+Horizontal alignment of the text
+
+Valid values:
+  - **left** (0) – left
+  - **center** (1) – center
+  - **right** (2) – right
+  - **position** (4) – Absolute position clamped to canvas
+  - **absolute** (5) – Absolute position
+
+See #GstBaseTextOverlay:halignment
+
+#### `height`
+
+height of the source
+
+Value type: #gint
+
+#### `mute`
+
+mute channel
+
+Value type: #gboolean
+
+See #volume:mute
+
+#### `pattern`
+
+Type of test pattern to generate
+
+Valid values:
+  - **SMPTE 100% color bars** (0) – smpte
+  - **Random (television snow)** (1) – snow
+  - **100% Black** (2) – black
+  - **100% White** (3) – white
+  - **Red** (4) – red
+  - **Green** (5) – green
+  - **Blue** (6) – blue
+  - **Checkers 1px** (7) – checkers-1
+  - **Checkers 2px** (8) – checkers-2
+  - **Checkers 4px** (9) – checkers-4
+  - **Checkers 8px** (10) – checkers-8
+  - **Circular** (11) – circular
+  - **Blink** (12) – blink
+  - **SMPTE 75% color bars** (13) – smpte75
+  - **Zone plate** (14) – zone-plate
+  - **Gamut checkers** (15) – gamut
+  - **Chroma zone plate** (16) – chroma-zone-plate
+  - **Solid color** (17) – solid-color
+  - **Moving ball** (18) – ball
+  - **SMPTE 100% color bars** (19) – smpte100
+  - **Bar** (20) – bar
+  - **Pinwheel** (21) – pinwheel
+  - **Spokes** (22) – spokes
+  - **Gradient** (23) – gradient
+  - **Colors** (24) – colors
+
+See #videotestsrc:pattern
+
+#### `posx`
+
+x position of the stream
+
+Value type: #gint
+
+#### `posy`
+
+y position of the stream
+
+Value type: #gint
+
+#### `text-width`
+
+Resulting width of font rendering
+
+Value type: #guint
+
+See #GstBaseTextOverlay:text-width
+
+#### `text-x`
+
+Resulting X position of font rendering.
+
+Value type: #gint
+
+See #GstBaseTextOverlay:text-x
+
+#### `text-y`
+
+Resulting X position of font rendering.
+
+Value type: #gint
+
+See #GstBaseTextOverlay:text-y
+
+#### `time-mode`
+
+What time to show
+
+Valid values:
+  - **buffer-time** (0) – buffer-time
+  - **stream-time** (1) – stream-time
+  - **running-time** (2) – running-time
+  - **time-code** (3) – time-code
+
+See #timeoverlay:time-mode
+
+#### `valignment`
+
+Vertical alignment of the text
+
+Valid values:
+  - **baseline** (0) – baseline
+  - **bottom** (1) – bottom
+  - **top** (2) – top
+  - **position** (3) – Absolute position clamped to canvas
+  - **center** (4) – center
+  - **absolute** (5) – Absolute position
+
+See #GstBaseTextOverlay:valignment
+
+#### `video-direction`
+
+Video direction: rotation and flipping
+
+Valid values:
+  - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity
+  - **GST_VIDEO_ORIENTATION_90R** (1) – 90r
+  - **GST_VIDEO_ORIENTATION_180** (2) – 180
+  - **GST_VIDEO_ORIENTATION_90L** (3) – 90l
+  - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz
+  - **GST_VIDEO_ORIENTATION_VERT** (5) – vert
+  - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr
+  - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll
+  - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto
+  - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom
+
+See #GstVideoDirection:video-direction
+
+#### `volume`
+
+Volume of test signal
+
+Value type: #gdouble
+
+See #audiotestsrc:volume
+
+#### `volume`
+
+volume factor, 1.0=100%
+
+Value type: #gdouble
+
+See #volume:volume
+
+#### `width`
+
+width of the source
+
+Value type: #gint
+
diff --git a/docs/libs/GESTitleSource-children-props.md b/docs/libs/GESTitleSource-children-props.md
new file mode 100644 (file)
index 0000000..999b092
--- /dev/null
@@ -0,0 +1,221 @@
+#### `alpha`
+
+alpha of the stream
+
+Value type: #gdouble
+
+#### `color`
+
+Color to use for text (big-endian ARGB).
+
+Value type: #guint
+
+See #GstBaseTextOverlay:color
+
+#### `font-desc`
+
+Pango font description of font to be used for rendering. See documentation of
+pango_font_description_from_string for syntax.
+
+Value type: #gchararray
+
+See #GstBaseTextOverlay:font-desc
+
+#### `foreground-color`
+
+Foreground color to use (big-endian ARGB)
+
+Value type: #guint
+
+See #videotestsrc:foreground-color
+
+#### `halignment`
+
+Horizontal alignment of the text
+
+Valid values:
+  - **left** (0) – left
+  - **center** (1) – center
+  - **right** (2) – right
+  - **position** (4) – Absolute position clamped to canvas
+  - **absolute** (5) – Absolute position
+
+See #GstBaseTextOverlay:halignment
+
+#### `height`
+
+height of the source
+
+Value type: #gint
+
+#### `outline-color`
+
+Color to use for outline the text (big-endian ARGB).
+
+Value type: #guint
+
+See #GstBaseTextOverlay:outline-color
+
+#### `pattern`
+
+Type of test pattern to generate
+
+Valid values:
+  - **SMPTE 100% color bars** (0) – smpte
+  - **Random (television snow)** (1) – snow
+  - **100% Black** (2) – black
+  - **100% White** (3) – white
+  - **Red** (4) – red
+  - **Green** (5) – green
+  - **Blue** (6) – blue
+  - **Checkers 1px** (7) – checkers-1
+  - **Checkers 2px** (8) – checkers-2
+  - **Checkers 4px** (9) – checkers-4
+  - **Checkers 8px** (10) – checkers-8
+  - **Circular** (11) – circular
+  - **Blink** (12) – blink
+  - **SMPTE 75% color bars** (13) – smpte75
+  - **Zone plate** (14) – zone-plate
+  - **Gamut checkers** (15) – gamut
+  - **Chroma zone plate** (16) – chroma-zone-plate
+  - **Solid color** (17) – solid-color
+  - **Moving ball** (18) – ball
+  - **SMPTE 100% color bars** (19) – smpte100
+  - **Bar** (20) – bar
+  - **Pinwheel** (21) – pinwheel
+  - **Spokes** (22) – spokes
+  - **Gradient** (23) – gradient
+  - **Colors** (24) – colors
+
+See #videotestsrc:pattern
+
+#### `posx`
+
+x position of the stream
+
+Value type: #gint
+
+#### `posy`
+
+y position of the stream
+
+Value type: #gint
+
+#### `shaded-background`
+
+Whether to shade the background under the text area
+
+Value type: #gboolean
+
+See #GstBaseTextOverlay:shaded-background
+
+#### `text`
+
+Text to be display.
+
+Value type: #gchararray
+
+See #GstBaseTextOverlay:text
+
+#### `text-height`
+
+Resulting height of font rendering
+
+Value type: #guint
+
+See #GstBaseTextOverlay:text-height
+
+#### `text-width`
+
+Resulting width of font rendering
+
+Value type: #guint
+
+See #GstBaseTextOverlay:text-width
+
+#### `text-x`
+
+Resulting X position of font rendering.
+
+Value type: #gint
+
+See #GstBaseTextOverlay:text-x
+
+#### `text-y`
+
+Resulting X position of font rendering.
+
+Value type: #gint
+
+See #GstBaseTextOverlay:text-y
+
+#### `valignment`
+
+Vertical alignment of the text
+
+Valid values:
+  - **baseline** (0) – baseline
+  - **bottom** (1) – bottom
+  - **top** (2) – top
+  - **position** (3) – Absolute position clamped to canvas
+  - **center** (4) – center
+  - **absolute** (5) – Absolute position
+
+See #GstBaseTextOverlay:valignment
+
+#### `video-direction`
+
+Video direction: rotation and flipping
+
+Valid values:
+  - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity
+  - **GST_VIDEO_ORIENTATION_90R** (1) – 90r
+  - **GST_VIDEO_ORIENTATION_180** (2) – 180
+  - **GST_VIDEO_ORIENTATION_90L** (3) – 90l
+  - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz
+  - **GST_VIDEO_ORIENTATION_VERT** (5) – vert
+  - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr
+  - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll
+  - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto
+  - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom
+
+See #GstVideoDirection:video-direction
+
+#### `width`
+
+width of the source
+
+Value type: #gint
+
+#### `x-absolute`
+
+Horizontal position when using absolute alignment
+
+Value type: #gdouble
+
+See #GstBaseTextOverlay:x-absolute
+
+#### `xpos`
+
+Horizontal position when using clamped position alignment
+
+Value type: #gdouble
+
+See #GstBaseTextOverlay:xpos
+
+#### `y-absolute`
+
+Vertical position when using absolute alignment
+
+Value type: #gdouble
+
+See #GstBaseTextOverlay:y-absolute
+
+#### `ypos`
+
+Vertical position when using clamped position alignment
+
+Value type: #gdouble
+
+See #GstBaseTextOverlay:ypos
+
diff --git a/docs/libs/GESTransitionClip-children-props.md b/docs/libs/GESTransitionClip-children-props.md
new file mode 100644 (file)
index 0000000..44507fb
--- /dev/null
@@ -0,0 +1,16 @@
+#### `border`
+
+The border width
+
+Value type: #guint
+
+See #GESVideoTransition:border
+
+#### `invert`
+
+Whether the transition is inverted
+
+Value type: #gboolean
+
+See #GESVideoTransition:invert
+
diff --git a/docs/libs/GESVideoTestSource-children-props.md b/docs/libs/GESVideoTestSource-children-props.md
new file mode 100644 (file)
index 0000000..8d99b05
--- /dev/null
@@ -0,0 +1,97 @@
+#### `alpha`
+
+alpha of the stream
+
+Value type: #gdouble
+
+#### `background-color`
+
+Background color to use (big-endian ARGB)
+
+Value type: #guint
+
+See #videotestsrc:background-color
+
+#### `foreground-color`
+
+Foreground color to use (big-endian ARGB)
+
+Value type: #guint
+
+See #videotestsrc:foreground-color
+
+#### `height`
+
+height of the source
+
+Value type: #gint
+
+#### `pattern`
+
+Type of test pattern to generate
+
+Valid values:
+  - **SMPTE 100% color bars** (0) – smpte
+  - **Random (television snow)** (1) – snow
+  - **100% Black** (2) – black
+  - **100% White** (3) – white
+  - **Red** (4) – red
+  - **Green** (5) – green
+  - **Blue** (6) – blue
+  - **Checkers 1px** (7) – checkers-1
+  - **Checkers 2px** (8) – checkers-2
+  - **Checkers 4px** (9) – checkers-4
+  - **Checkers 8px** (10) – checkers-8
+  - **Circular** (11) – circular
+  - **Blink** (12) – blink
+  - **SMPTE 75% color bars** (13) – smpte75
+  - **Zone plate** (14) – zone-plate
+  - **Gamut checkers** (15) – gamut
+  - **Chroma zone plate** (16) – chroma-zone-plate
+  - **Solid color** (17) – solid-color
+  - **Moving ball** (18) – ball
+  - **SMPTE 100% color bars** (19) – smpte100
+  - **Bar** (20) – bar
+  - **Pinwheel** (21) – pinwheel
+  - **Spokes** (22) – spokes
+  - **Gradient** (23) – gradient
+  - **Colors** (24) – colors
+
+See #videotestsrc:pattern
+
+#### `posx`
+
+x position of the stream
+
+Value type: #gint
+
+#### `posy`
+
+y position of the stream
+
+Value type: #gint
+
+#### `video-direction`
+
+Video direction: rotation and flipping
+
+Valid values:
+  - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity
+  - **GST_VIDEO_ORIENTATION_90R** (1) – 90r
+  - **GST_VIDEO_ORIENTATION_180** (2) – 180
+  - **GST_VIDEO_ORIENTATION_90L** (3) – 90l
+  - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz
+  - **GST_VIDEO_ORIENTATION_VERT** (5) – vert
+  - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr
+  - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll
+  - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto
+  - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom
+
+See #GstVideoDirection:video-direction
+
+#### `width`
+
+width of the source
+
+Value type: #gint
+
diff --git a/docs/libs/GESVideoUriSource-children-props.md b/docs/libs/GESVideoUriSource-children-props.md
new file mode 100644 (file)
index 0000000..10791ae
--- /dev/null
@@ -0,0 +1,83 @@
+#### `alpha`
+
+alpha of the stream
+
+Value type: #gdouble
+
+#### `fields`
+
+Fields to use for deinterlacing
+
+Valid values:
+  - **All fields** (0) – all
+  - **Top fields only** (1) – top
+  - **Bottom fields only** (2) – bottom
+  - **Automatically detect** (3) – auto
+
+See #deinterlace:fields
+
+#### `height`
+
+height of the source
+
+Value type: #gint
+
+#### `mode`
+
+Deinterlace Mode
+
+Valid values:
+  - **Auto detection (best effort)** (0) – auto
+  - **Force deinterlacing** (1) – interlaced
+  - **Run in passthrough mode** (2) – disabled
+  - **Auto detection (strict)** (3) – auto-strict
+
+See #deinterlace:mode
+
+#### `posx`
+
+x position of the stream
+
+Value type: #gint
+
+#### `posy`
+
+y position of the stream
+
+Value type: #gint
+
+#### `tff`
+
+Deinterlace top field first
+
+Valid values:
+  - **Auto detection** (0) – auto
+  - **Top field first** (1) – tff
+  - **Bottom field first** (2) – bff
+
+See #deinterlace:tff
+
+#### `video-direction`
+
+Video direction: rotation and flipping
+
+Valid values:
+  - **GST_VIDEO_ORIENTATION_IDENTITY** (0) – identity
+  - **GST_VIDEO_ORIENTATION_90R** (1) – 90r
+  - **GST_VIDEO_ORIENTATION_180** (2) – 180
+  - **GST_VIDEO_ORIENTATION_90L** (3) – 90l
+  - **GST_VIDEO_ORIENTATION_HORIZ** (4) – horiz
+  - **GST_VIDEO_ORIENTATION_VERT** (5) – vert
+  - **GST_VIDEO_ORIENTATION_UL_LR** (6) – ul-lr
+  - **GST_VIDEO_ORIENTATION_UR_LL** (7) – ur-ll
+  - **GST_VIDEO_ORIENTATION_AUTO** (8) – auto
+  - **GST_VIDEO_ORIENTATION_CUSTOM** (9) – custom
+
+See #GstVideoDirection:video-direction
+
+#### `width`
+
+width of the source
+
+Value type: #gint
+
diff --git a/docs/libs/Makefile.am b/docs/libs/Makefile.am
deleted file mode 100644 (file)
index d4362f0..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-GST_DOC_SCANOBJ = $(top_srcdir)/common/gstdoc-scangobj
-
-## Process this file with automake to produce Makefile.in
-
-# The name of the module, e.g. 'glib'.
-MODULE=ges
-DOC_MODULE=$(MODULE)
-
-# for upload-doc.mak
-DOC=gstreamer-editing-services
-FORMATS=html
-html: html-build.stamp
-include $(top_srcdir)/common/upload-doc.mak
-
-# The top-level SGML file. Change it if you want.
-DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
-
-# The directory containing the source code.
-# gtk-doc will search all .c & .h files beneath here for inline comments
-# documenting functions and macros.
-DOC_SOURCE_DIR = $(top_srcdir)/ges
-
-SCAN_OPTIONS= 
-
-# Extra options to supply to gtkdoc-mkdb.
-MKDB_OPTIONS=--sgml-mode --source-suffixes=c,h,cc,m
-
-# Extra options to supply to gtkdoc-fixref.
-FIXXREF_OPTIONS=--extra-dir=$(GLIB_PREFIX)/share/gtk-doc/html \
-       --extra-dir=$(GST_PREFIX)/share/gtk-doc/html \
-       --extra-dir=$(GSTPB_PREFIX)/share/gtk-doc/html
-
-# Used for dependencies.
-HFILE_GLOB=$(top_srcdir)/ges/ges-*.h
-CFILE_GLOB=$(top_srcdir)/ges/ges-*.c
-
-# Extra options to supply to gtkdoc-scan.
-SCANOBJ_OPTIONS=--type-init-func="g_type_init();gst_init(&argc,&argv)"
-
-# Header files to ignore when scanning.
-IGNORE_HFILES = \
-       gesmarshal.h \
-       ges-internal.h \
-       ges-auto-transition.h \
-       ges-structured-interface.h \
-       ges-structure-parser.h \
-       ges-smart-video-mixer.h \
-       gstframepositioner.h
-IGNORE_CFILES =
-
-# we add all .h files of elements that have signals/args we want
-# sadly this also pulls in the private methods - maybe we should
-# move those around in the source ?
-# also, we should add some stuff here conditionally based on whether
-# or not the plugin will actually build
-# but I'm not sure about that - it might be this Just Works given that
-# the registry won't have the element
-
-EXTRA_HFILES = \
-       $(top_srcdir)/ges/ges-types.h
-
-# Images to copy into HTML directory.
-HTML_IMAGES =  layer_track_overview.png
-
-# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
-content_files = architecture.xml 
-
-# Other files to distribute.
-extra_files =
-
-# CFLAGS and LDFLAGS for compiling scan program. Only needed if your app/lib
-# contains GtkObjects/GObjects and you want to document signals and properties.
-GTKDOC_CFLAGS = -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) $(GST_BASE_CFLAGS) \
-       $(GST_CFLAGS)  $(GIO_CFLAGS) $(GCOV_CFLAGS)
-GTKDOC_LIBS =  \
-       $(top_builddir)/ges/libges-@GST_API_VERSION@.la \
-       $(GST_BASE_LIBS) $(GST_LIBS) $(GIO_LIBS) $(GCOV_LIBS)
-
-GTKDOC_CC=$(LIBTOOL) --tag=CC --mode=compile $(CC)
-GTKDOC_LD=$(LIBTOOL) --tag=CC --mode=link $(CC)
-
-# If you need to override some of the declarations, place them in this file
-# and uncomment this line.
-DOC_OVERRIDES = $(DOC_MODULE)-overrides.txt
-
-include $(top_srcdir)/common/gtk-doc.mak
diff --git a/docs/libs/architecture.xml b/docs/libs/architecture.xml
deleted file mode 100644 (file)
index f345eb9..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
-"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
-<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
-]>
-<refentry id="ges-architecture" revision="25 mar 2009">
-  <refmeta>
-    <refentrytitle>Overview and architecture</refentrytitle>
-
-    <manvolnum>1</manvolnum>
-
-    <refmiscinfo>GStreamer Editing Services</refmiscinfo>
-  </refmeta>
-
-  <!-- <refnamediv> -->
-
-  <!-- <refname>Overview</refname> -->
-
-  <!-- <refpurpose> -->
-
-  <!-- Goals of the GStreamer Editing Services. -->
-
-  <!-- </refpurpose> -->
-
-  <!-- </refnamediv> -->
-
-  <refsect1>
-    <title>Goals of GStreamer Editing Services</title>
-
-    <para>The GStreamer multimedia framework and the accompanying GNonLin set
-    of plugins for non-linear editing offer all the building blocks for:
-    <itemizedlist>
-        <listitem>
-          <para>Decoding and encoding to a wide variety of formats, through
-          all the available GStreamer plugins.</para>
-        </listitem>
-
-        <listitem>
-          <para>Easily choosing segments of streams and arranging them through
-          time through the GNonLin set of plugins.</para>
-        </listitem>
-      </itemizedlist></para>
-
-    <para>But all those building blocks only offer stream-level access, which
-    results in developers who want to write non-linear editors to write a
-    consequent amount of code to get to the level of <emphasis>non-linear
-    editing</emphasis> notions which are closer and more meaningful for the
-    end-user (and therefore the application).</para>
-
-    <para>The GStreamer Editing Services <remark>(hereafter GES)</remark> aims
-    to fill the gap between GStreamer/GNonLin and the application developer by
-    offering a series of classes to simplify the creation of many kind of
-    editing-related applications.</para>
-  </refsect1>
-
-  <refsect1>
-    <title>Architecture</title>
-
-    <refsect2>
-      <title>Timeline and TimelinePipeline</title>
-
-      <para>The most top-level object encapsulating every other object is the
-      <link linkend="GESTimeline">GESTimeline</link>. It is the central object
-      for any editing project.</para>
-
-      <para>The <classname>GESTimeline</classname> is a
-      <classname>GstElement</classname>. It can therefore be used in any
-      GStreamer pipeline like any other object.</para>
-    </refsect2>
-
-
-    <refsect2>
-      <title>Tracks and Layers</title>
-
-      <para>The GESTimeline can contain two types of objects (seen in <xref
-      linkend="layer_tracks_diagram" />): <itemizedlist>
-          <listitem>
-            <para>Layers - Corresponds to the user-visible arrangement of clips,
-            and what you primarily interact with as an application developer.
-            A minimalistic timeline would only have one layer,
-            but a more complex editing application could use as many as needed.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>Tracks - Corresponds to the output streams in GStreamer.
-            A typical GESTimeline, aimed at a video editing application, would
-            have an audio track and a video track.
-            A GESTimeline for an audio editing application would only require
-            an audio track. Multiple layers can be related to each track.</para>
-          </listitem>
-        </itemizedlist></para>
-
-      <figure float="0" id="layer_tracks_diagram">
-        <title>Layers and Tracks</title>
-
-        <mediaobject>
-          <imageobject>
-            <imagedata fileref="layer_track_overview.png" scale="75" />
-          </imageobject>
-        </mediaobject>
-      </figure>
-
-      <para>In order to reduce even more the amount of GStreamer interaction
-      the application developer has to deal with, a convenience GstPipeline
-      has been made available specifically for Timelines : <link
-      linkend="GESPipeline">GESPipeline</link>.</para>
-    </refsect2>
-
-  </refsect1>
-</refentry>
diff --git a/docs/libs/document-children-props.py b/docs/libs/document-children-props.py
new file mode 100644 (file)
index 0000000..5c5e246
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+
+"""
+Simple script to update the children properties information for
+GESTrackElement-s that add children properties all the time
+"""
+
+import gi
+import os
+import sys
+import textwrap
+
+gi.require_version("Gst", "1.0")
+gi.require_version("GObject", "2.0")
+gi.require_version("GES", "1.0")
+
+from gi.repository import Gst, GES, GObject
+
+overrides = {
+    "GstFramePositioner": False,
+    "GstBaseTextOverlay": "timeoverlay",
+    "GstVideoDirection": "videoflip",
+    "GESVideoTestSource": "GESVideoTestSource",
+    "GESVideoTransition": "GESVideoTransition",
+}
+
+if __name__ == "__main__":
+    Gst.init(None)
+    GES.init()
+
+    os.chdir(os.path.realpath(os.path.dirname(__file__)))
+    tl = GES.Timeline.new_audio_video()
+    layer = tl.append_layer()
+
+    elements = []
+    def add_clip(c, add=True, override_name=None):
+        c.props.duration = Gst.SECOND
+        c.props.start = layer.get_duration()
+        layer.add_clip(c)
+        if add:
+            elements.extend(c.children)
+        else:
+            if override_name:
+                elements.append((c, override_name))
+            else:
+                elements.append(c)
+
+    add_clip(GES.UriClipAsset.request_sync(Gst.filename_to_uri(os.path.join("../../", "tests/check/assets/audio_video.ogg"))).extract())
+    add_clip(GES.TestClip.new())
+    add_clip(GES.TitleClip.new())
+
+    add_clip(GES.SourceClip.new_time_overlay(), False, "GESTimeOverlaySourceClip")
+    add_clip(GES.TransitionClip.new_for_nick("crossfade"), False)
+
+    for element in elements:
+        if isinstance(element, tuple):
+            element, gtype = element
+        else:
+            gtype = element.__gtype__.name
+        print(gtype)
+        with open(gtype + '-children-props.md', 'w') as f:
+            for prop in GES.TimelineElement.list_children_properties(element):
+                prefix = '#### `%s`\n\n' % (prop.name)
+
+                prefix_len = len(prefix)
+                lines = textwrap.wrap(prop.blurb, width=80)
+
+                doc = prefix + lines[0]
+
+                if GObject.type_is_a(prop, GObject.ParamSpecEnum.__gtype__):
+                    lines += ["", "Valid values:"]
+                    for value  in prop.enum_class.__enum_values__.values():
+                        lines.append("  - **%s** (%d) – %s" % (value.value_name,
+                            int(value), value.value_nick))
+                else:
+                    lines += ["", "Value type: #" + prop.value_type.name]
+
+                typename = overrides.get(prop.owner_type.name, None)
+                if typename is not False:
+                    if typename is None:
+                        if GObject.type_is_a(prop.owner_type, Gst.Element):
+                            typename = GObject.new(prop.owner_type).get_factory().get_name()
+                    lines += ["", "See #%s:%s" % (typename, prop.name)]
+
+                if len(lines) > 1:
+                    doc += '\n'
+                    doc += '\n'.join(lines[1:])
+
+
+                print(doc + "\n", file=f)
diff --git a/docs/libs/ges-docs.sgml b/docs/libs/ges-docs.sgml
deleted file mode 100644 (file)
index edb9586..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
-               "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
-<!ENTITY % version-entities SYSTEM "version.entities">
-%version-entities;
-]>
-
-<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
-  <bookinfo>
-    <title>GStreamer Editing Services &GES_VERSION; Reference Manual</title>
-    <releaseinfo>
-      for GStreamer Editing Services &GST_API_VERSION; (&GES_VERSION;)
-    </releaseinfo>
-  </bookinfo>
-
-  <chapter>
-    <title>GStreamer Editing Services Overview</title>
-    <para>
-      The "GStreamer Editing Services" is a library to simplify the creation
-of multimedia editing applications. Based on the GStreamer multimedia framework
-and the GNonLin set of plugins, its goals are to suit all types of editing-related
-applications.
-    </para>
-
-    <para>
-      The GStreamer Editing Services are cross-platform and work on most UNIX-like
-platform as well as Windows. It is released under the GNU Library General Public License
-(GNU LGPL).
-    </para>
-  <xi:include href="architecture.xml"/>
-  <xi:include href="xml/ges-common.xml"/>
-  <xi:include href="xml/ges-enums.xml"/>
-  <xi:include href="xml/ges-gerror.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Base Classes</title>
-    <xi:include href="xml/gestimeline.xml"/>
-    <xi:include href="xml/geslayer.xml"/>
-    <xi:include href="xml/gestimelineelement.xml"/>
-    <xi:include href="xml/gescontainer.xml"/>
-    <xi:include href="xml/gesclip.xml"/>
-    <xi:include href="xml/gessourceclip.xml"/>
-    <xi:include href="xml/gesoperationclip.xml"/>
-    <xi:include href="xml/gesoverlayclip.xml"/>
-    <xi:include href="xml/gesbaseeffectclip.xml"/>
-    <xi:include href="xml/gestrack.xml"/>
-    <xi:include href="xml/gestrackelement.xml"/>
-    <xi:include href="xml/gessource.xml"/>
-    <xi:include href="xml/gesvideosource.xml"/>
-    <xi:include href="xml/gesaudiosource.xml"/>
-    <xi:include href="xml/gesbaseeffect.xml"/>
-    <xi:include href="xml/gesoperation.xml"/>
-    <xi:include href="xml/gesbasetransitionclip.xml"/>
-    <xi:include href="xml/gesasset.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Timeline objects</title>
-    <xi:include href="xml/gesuriclip.xml"/>
-    <xi:include href="xml/gestitleclip.xml"/>
-    <xi:include href="xml/gestestclip.xml"/>
-    <xi:include href="xml/gestextoverlayclip.xml"/>
-    <xi:include href="xml/gestransitionclip.xml"/>
-    <xi:include href="xml/geseffectclip.xml"/>
-    <xi:include href="xml/gesgroup.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Track objects</title>
-    <xi:include href="xml/gesaudiourisource.xml"/>
-    <xi:include href="xml/gesvideourisource.xml"/>
-    <xi:include href="xml/gestitlesource.xml"/>
-    <xi:include href="xml/gesaudiotestsource.xml"/>
-    <xi:include href="xml/gesvideotestsource.xml"/>
-    <xi:include href="xml/gestextoverlay.xml"/>
-    <xi:include href="xml/gestransition.xml"/>
-    <xi:include href="xml/gesvideotransition.xml"/>
-    <xi:include href="xml/gesaudiotransition.xml"/>
-    <xi:include href="xml/gesimagesource.xml"/>
-    <xi:include href="xml/gesmultifilesource.xml"/>
-    <xi:include href="xml/geseffect.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Convenience classes</title>
-    <xi:include href="xml/gespipeline.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Serialization Classes</title>
-    <xi:include href="xml/gesformatter.xml"/>
-    <xi:include href="xml/gespitiviformatter.xml"/>
-    <xi:include href="xml/gesbasexmlformatter.xml"/>
-    <xi:include href="xml/gesxmlformatter.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Interfaces</title>
-    <xi:include href="xml/gesmetacontainer.xml"/>
-    <xi:include href="xml/gesextractable.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Assets</title>
-    <xi:include href="xml/gesclipasset.xml"/>
-    <xi:include href="xml/gestrackelementasset.xml"/>
-    <xi:include href="xml/gesuriclipasset.xml"/>
-    <xi:include href="xml/gesurisourceasset.xml"/>
-    <xi:include href="xml/gesproject.xml"/>
-  </chapter>
-
-  <chapter>
-    <title>Tracks</title>
-    <xi:include href="xml/gesvideotrack.xml"/>
-    <xi:include href="xml/gesaudiotrack.xml"/>
-  </chapter>
-
-  <chapter id="ges-hierarchy">
-    <title>Object Hierarchy</title>
-    <xi:include href="xml/tree_index.sgml"/>
-  </chapter>
-
-  <index id="api-index-full">
-    <title>API Index</title>
-    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
-  </index>
-
-  <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
-</book>
diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt
deleted file mode 100644 (file)
index aa862b9..0000000
+++ /dev/null
@@ -1,1258 +0,0 @@
-<INCLUDE>ges/ges.h</INCLUDE>
-
-<SECTION>
-<FILE>ges-common</FILE>
-<TITLE>Initialization</TITLE>
-ges_init
-ges_is_initialized
-ges_init_check
-ges_deinit
-ges_version
-ges_init_get_option_group
-GES_VERSION_MAJOR
-GES_VERSION_MICRO
-GES_VERSION_MINOR
-GES_VERSION_NANO
-<SUBSECTION Standard>
-GES_PADDING
-GES_PADDING_LARGE
-GESAssetLoadingReturn
-</SECTION>
-
-<SECTION>
-<FILE>ges-utils</FILE>
-<TITLE>Utilities</TITLE>
-ges_add_missing_uri_relocation_uri
-
-ges_pspec_equal
-ges_pspec_hash
-
-</SECTION>
-
-<SECTION>
-<FILE>ges-gerror</FILE>
-<TITLE>GES GErrors</TITLE>
-GES_ERROR
-GESError
-</SECTION>
-
-<SECTION>
-<FILE>ges-enums</FILE>
-<TITLE>GES Enums</TITLE>
-GESTrackType
-GESVideoStandardTransitionType
-GESTextHAlign
-DEFAULT_HALIGNMENT
-GESTextVAlign
-DEFAULT_VALIGNMENT
-GESVideoTestPattern
-GESPipelineFlags
-GESEdge
-GESEditMode
-GESMetaFlag
-<SUBSECTION Standard>
-GES_TYPE_TRACK_TYPE
-ges_track_type_get_type
-GES_META_FLAG_TYPE
-ges_meta_flag_get_type
-GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE
-ges_video_standard_transition_type_get_type
-GES_TEXT_HALIGN_TYPE
-ges_text_halign_get_type
-GES_TEXT_VALIGN_TYPE
-ges_text_valign_get_type
-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_type_name
-</SECTION>
-
-<SECTION>
-<FILE>gestrack</FILE>
-<TITLE>GESTrack</TITLE>
-GESTrack
-GESCreateElementForGapFunc
-ges_track_new
-ges_track_add_element
-ges_track_set_restriction_caps
-ges_track_update_restriction_caps
-ges_track_remove_element
-ges_track_set_caps
-ges_track_get_caps
-ges_track_enable_update
-ges_track_get_elements
-ges_track_is_updating
-ges_track_commit
-ges_track_get_mixing
-ges_track_set_mixing
-<SUBSECTION Standard>
-GESTrackClass
-GESTrackPrivate
-ges_track_set_timeline
-ges_track_get_timeline
-ges_track_get_type
-GES_IS_TRACK
-GES_IS_TRACK_CLASS
-GES_TRACK
-GES_TRACK_CLASS
-GES_TRACK_GET_CLASS
-GES_TYPE_TRACK
-</SECTION>
-
-<SECTION>
-<FILE>gesaudiotrack</FILE>
-<TITLE>GESAudioTrack</TITLE>
-GESAudioTrack
-ges_audio_track_new
-<SUBSECTION Standard>
-GESAudioTrackClass
-GESAudioTrackPrivate
-GES_IS_AUDIO_TRACK
-GES_IS_AUDIO_TRACK_CLASS
-GES_AUDIO_TRACK
-GES_AUDIO_TRACK_CLASS
-GES_AUDIO_TRACK_GET_CLASS
-GES_TYPE_AUDIO_TRACK
-ges_audio_track_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesvideotrack</FILE>
-<TITLE>GESVideoTrack</TITLE>
-GESVideoTrack
-ges_video_track_new
-<SUBSECTION Standard>
-GESVideoTrackClass
-GESVideoTrackPrivate
-GES_IS_VIDEO_TRACK
-GES_IS_VIDEO_TRACK_CLASS
-GES_VIDEO_TRACK
-GES_VIDEO_TRACK_CLASS
-GES_VIDEO_TRACK_GET_CLASS
-GES_TYPE_VIDEO_TRACK
-ges_video_track_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gestrackelement</FILE>
-<TITLE>GESTrackElement</TITLE>
-GESTrackElement
-GESTrackElementClass
-ges_track_element_set_active
-ges_track_element_get_track
-ges_track_element_get_nleobject
-ges_track_element_get_gnlobject
-ges_track_element_get_element
-ges_track_element_is_active
-ges_track_element_lookup_child
-ges_track_element_list_children_properties
-ges_track_element_set_child_property
-ges_track_element_set_child_properties
-ges_track_element_set_child_property_valist
-ges_track_element_set_child_property_by_pspec
-ges_track_element_get_child_property
-ges_track_element_get_child_properties
-ges_track_element_get_child_property_valist
-ges_track_element_get_child_property_by_pspec
-ges_track_element_edit
-ges_track_element_set_control_source
-ges_track_element_get_control_binding
-ges_track_element_get_all_control_bindings
-ges_track_element_remove_control_binding
-<SUBSECTION Standard>
-GESTrackElementPrivate
-ges_track_element_set_track
-ges_track_element_get_type
-GES_IS_TRACK_ELEMENT
-GES_IS_TRACK_ELEMENT_CLASS
-GES_TRACK_ELEMENT
-GES_TRACK_ELEMENT_CLASS
-GES_TRACK_ELEMENT_GET_CLASS
-GES_TYPE_TRACK_ELEMENT
-</SECTION>
-
-<SECTION>
-<FILE>gessource</FILE>
-<TITLE>GESSource</TITLE>
-GESSource
-GESSourceClass
-<SUBSECTION Standard>
-GESSourcePrivate
-GES_SOURCE
-GES_SOURCE_CLASS
-GES_SOURCE_GET_CLASS
-GES_TYPE_SOURCE
-GES_IS_SOURCE
-GES_IS_SOURCE_CLASS
-ges_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesoperation</FILE>
-<TITLE>GESOperation</TITLE>
-GESOperation
-GESOperationClass
-<SUBSECTION Standard>
-GESOperationPrivate
-ges_operation_get_type
-GES_IS_OPERATION
-GES_IS_OPERATION_CLASS
-GES_OPERATION
-GES_OPERATION_CLASS
-GES_OPERATION_GET_CLASS
-GES_TYPE_OPERATION
-</SECTION>
-
-<SECTION>
-<FILE>gesvideosource</FILE>
-<TITLE>GESVideoSource</TITLE>
-GESVideoSource
-<SUBSECTION Standard>
-GESVideoSourceClass
-GESVideoSourcePrivate
-GES_VIDEO_SOURCE
-GES_VIDEO_SOURCE_CLASS
-GES_VIDEO_SOURCE_GET_CLASS
-GES_TYPE_VIDEO_SOURCE
-GES_IS_VIDEO_SOURCE
-GES_IS_VIDEO_SOURCE_CLASS
-ges_video_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesaudiosource</FILE>
-<TITLE>GESAudioSource</TITLE>
-GESAudioSource
-<SUBSECTION Standard>
-GESAudioSourceClass
-GESAudioSourcePrivate
-GES_AUDIO_SOURCE
-GES_AUDIO_SOURCE_CLASS
-GES_AUDIO_SOURCE_GET_CLASS
-GES_TYPE_AUDIO_SOURCE
-GES_IS_AUDIO_SOURCE
-GES_IS_AUDIO_SOURCE_CLASS
-ges_audio_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesvideourisource</FILE>
-<TITLE>GESVideoUriSource</TITLE>
-GESVideoUriSource
-<SUBSECTION Standard>
-GESVideoUriSourceClass
-GESVideoUriSourcePrivate
-GES_VIDEO_URI_SOURCE
-GES_VIDEO_URI_SOURCE_CLASS
-GES_VIDEO_URI_SOURCE_GET_CLASS
-GES_TYPE_VIDEO_URI_SOURCE
-GES_IS_VIDEO_URI_SOURCE
-GES_IS_VIDEO_URI_SOURCE_CLASS
-ges_video_uri_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesaudiourisource</FILE>
-<TITLE>GESAudioUriSource</TITLE>
-GESAudioUriSource
-<SUBSECTION Standard>
-GESAudioUriSourceClass
-GESAudioUriSourcePrivate
-GES_AUDIO_URI_SOURCE
-GES_AUDIO_URI_SOURCE_CLASS
-GES_AUDIO_URI_SOURCE_GET_CLASS
-GES_TYPE_AUDIO_URI_SOURCE
-GES_IS_AUDIO_URI_SOURCE
-GES_IS_AUDIO_URI_SOURCE_CLASS
-ges_audio_uri_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesimagesource</FILE>
-<TITLE>GESImageSource</TITLE>
-GESImageSource
-<SUBSECTION Standard>
-GESImageSourcePrivate
-GES_IS_IMAGE_SOURCE
-GES_IS_IMAGE_SOURCE_CLASS
-GES_IMAGE_SOURCE
-GES_IMAGE_SOURCE_CLASS
-GES_IMAGE_SOURCE_GET_CLASS
-GES_TYPE_IMAGE_SOURCE
-GESImageSourceClass
-ges_image_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesmultifilesource</FILE>
-<TITLE>GESMultiFileSource</TITLE>
-GESMultiFileSource
-ges_multi_file_source_new
-<SUBSECTION Standard>
-GESMultiFileSourcePrivate
-GES_IS_MULTI_FILE_SOURCE
-GES_IS_MULTI_FILE_SOURCE_CLASS
-GES_MULTI_FILE_SOURCE
-GES_MULTI_FILE_SOURCE_CLASS
-GES_MULTI_FILE_SOURCE_GET_CLASS
-GES_TYPE_MULTI_FILE_SOURCE
-GESMultiFileSourceClass
-ges_multi_file_source_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gestransition</FILE>
-<TITLE>GESTransition</TITLE>
-GESTransition
-GESTransitionClass
-<SUBSECTION Standard>
-GESTransitionPrivate
-GES_IS_TRANSITION
-GES_IS_TRANSITION_CLASS
-GES_TRANSITION
-GES_TRANSITION_CLASS
-GES_TRANSITION_GET_CLASS
-GES_TYPE_TRANSITION
-ges_transition_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesaudiotransition</FILE>
-<TITLE>GESAudioTransition</TITLE>
-GESAudioTransition
-ges_audio_transition_new
-<SUBSECTION Standard>
-GESAudioTransitionClass
-GESAudioTransitionPrivate
-GES_IS_AUDIO_TRANSITION
-ges_audio_transition_get_type
-GES_IS_AUDIO_TRANSITION_CLASS
-GES_AUDIO_TRANSITION
-GES_AUDIO_TRANSITION_CLASS
-GES_AUDIO_TRANSITION_GET_CLASS
-GES_TYPE_AUDIO_TRANSITION
-</SECTION>
-
-<SECTION>
-<FILE>gesvideotransition</FILE>
-<TITLE>GESVideoTransition</TITLE>
-GESVideoTransition
-ges_video_transition_new
-ges_video_transition_set_transition_type
-ges_video_transition_get_transition_type
-ges_video_transition_set_border
-ges_video_transition_get_border
-ges_video_transition_set_inverted
-ges_video_transition_is_inverted
-<SUBSECTION Standard>
-GESVideoTransitionClass
-GESVideoTransitionPrivate
-ges_video_transition_get_type
-GES_IS_VIDEO_TRANSITION
-GES_IS_VIDEO_TRANSITION_CLASS
-GES_VIDEO_TRANSITION
-GES_VIDEO_TRANSITION_CLASS
-GES_VIDEO_TRANSITION_GET_CLASS
-GES_TYPE_VIDEO_TRANSITION
-</SECTION>
-
-<SECTION>
-<FILE>gestimeline</FILE>
-<TITLE>GESTimeline</TITLE>
-GESTimeline
-ges_timeline_new
-ges_timeline_new_audio_video
-ges_timeline_new_from_uri
-ges_timeline_add_layer
-ges_timeline_append_layer
-ges_timeline_remove_layer
-ges_timeline_add_track
-ges_timeline_remove_track
-ges_timeline_load_from_uri
-ges_timeline_save_to_uri
-ges_timeline_enable_update
-ges_timeline_is_updating
-ges_timeline_commit
-ges_timeline_commit_sync
-ges_timeline_move_layer
-<SUBSECTION usage>
-ges_timeline_get_tracks
-ges_timeline_get_layer
-ges_timeline_get_layers
-ges_timeline_get_track_for_pad
-ges_timeline_get_pad_for_track
-ges_timeline_get_duration
-ges_timeline_get_project
-ges_timeline_get_auto_transition
-ges_timeline_set_auto_transition
-ges_timeline_get_snapping_distance
-ges_timeline_set_snapping_distance
-ges_timeline_get_element
-ges_timeline_is_empty
-GES_TIMELINE_GET_LAYERS
-GES_TIMELINE_GET_TRACKS
-<SUBSECTION Standard>
-GESTimelinePrivate
-GESTimelineClass
-ges_timeline_get_type
-GES_IS_TIMELINE
-GES_IS_TIMELINE_CLASS
-GES_TIMELINE
-GES_TIMELINE_CLASS
-GES_TIMELINE_GET_CLASS
-GES_TYPE_TIMELINE
-</SECTION>
-
-
-<SECTION>
-<FILE>geslayer</FILE>
-<TITLE>GESLayer</TITLE>
-GESLayer
-GESLayerClass
-ges_layer_add_clip
-ges_layer_add_asset
-ges_layer_new
-ges_layer_remove_clip
-ges_layer_set_priority
-ges_layer_get_priority
-ges_layer_get_clips
-ges_layer_get_timeline
-ges_layer_get_auto_transition
-ges_layer_set_auto_transition
-ges_layer_is_empty
-ges_layer_get_duration
-<SUBSECTION Standard>
-GESLayerPrivate
-ges_layer_set_timeline
-ges_layer_get_type
-GES_IS_LAYER
-GES_IS_LAYER_CLASS
-GES_LAYER
-GES_LAYER_CLASS
-GES_LAYER_GET_CLASS
-GES_TYPE_LAYER
-</SECTION>
-
-<SECTION>
-<FILE>gestimelineelement</FILE>
-<TITLE>GESTimelineElement</TITLE>
-GESTimelineElement
-GESTimelineElementClass
-ges_timeline_element_set_parent
-ges_timeline_element_get_parent
-ges_timeline_element_set_timeline
-ges_timeline_element_get_timeline
-ges_timeline_element_set_start
-ges_timeline_element_set_inpoint
-ges_timeline_element_set_duration
-ges_timeline_element_set_max_duration
-ges_timeline_element_set_priority
-ges_timeline_element_get_start
-ges_timeline_element_get_inpoint
-ges_timeline_element_get_duration
-ges_timeline_element_get_max_duration
-ges_timeline_element_get_priority
-ges_timeline_element_ripple
-ges_timeline_element_ripple_end
-ges_timeline_element_roll_start
-ges_timeline_element_roll_end
-ges_timeline_element_trim
-ges_timeline_element_get_toplevel_parent
-ges_timeline_element_copy
-ges_timeline_element_paste
-ges_timeline_element_get_name
-ges_timeline_element_set_name
-ges_timeline_element_list_children_properties
-ges_timeline_element_lookup_child
-ges_timeline_element_get_child_property_by_pspec
-ges_timeline_element_get_child_property_valist
-ges_timeline_element_get_child_properties
-ges_timeline_element_set_child_property_valist
-ges_timeline_element_set_child_property_by_pspec
-ges_timeline_element_set_child_properties
-ges_timeline_element_set_child_property
-ges_timeline_element_get_child_property
-ges_timeline_element_add_child_property
-ges_timeline_element_remove_child_property
-ges_timeline_element_get_track_types
-GES_TIMELINE_ELEMENT_PARENT
-GES_TIMELINE_ELEMENT_TIMELINE
-GES_TIMELINE_ELEMENT_START
-GES_TIMELINE_ELEMENT_END
-GES_TIMELINE_ELEMENT_INPOINT
-GES_TIMELINE_ELEMENT_DURATION
-GES_TIMELINE_ELEMENT_MAX_DURATION
-GES_TIMELINE_ELEMENT_PRIORITY
-GES_TIMELINE_ELEMENT_NAME
-<SUBSECTION Standard>
-GESTimelineElementPrivate
-ges_timeline_element_get_type
-GES_TYPE_TIMELINE_ELEMENT
-GES_TIMELINE_ELEMENT
-GES_TIMELINE_ELEMENT_CLASS
-GES_IS_TIMELINE_ELEMENT
-GES_IS_TIMELINE_ELEMENT_CLASS
-GES_TIMELINE_ELEMENT_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gescontainer</FILE>
-<TITLE>GESContainer</TITLE>
-GESContainer
-GESContainerClass
-GES_CONTAINER_CHILDREN
-GES_CONTAINER_HEIGHT
-ges_container_get_children
-ges_container_add
-ges_container_remove
-ges_container_ungroup
-ges_container_group
-ges_container_edit
-<SUBSECTION Standard>
-GESContainerPrivate
-ges_container_get_type
-GES_TYPE_CONTAINER
-GES_CONTAINER
-GES_CONTAINER_CLASS
-GES_IS_CONTAINER
-GES_IS_CONTAINER_CLASS
-GES_CONTAINER_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesclip</FILE>
-<TITLE>GESClip</TITLE>
-GESClip
-GESClipClass
-GESCreateTrackElementFunc
-GESCreateTrackElementsFunc
-GESFillTrackElementFunc
-ges_clip_get_layer
-ges_clip_find_track_element
-ges_clip_find_track_elements
-ges_clip_add_asset
-ges_clip_get_top_effects
-ges_clip_get_top_effect_index
-ges_clip_set_top_effect_index
-ges_clip_get_top_effect_position
-ges_clip_set_top_effect_priority
-ges_clip_move_to_layer
-ges_clip_set_supported_formats
-ges_clip_get_supported_formats
-ges_clip_split
-ges_clip_edit
-GES_CLIP_HEIGHT
-<SUBSECTION Standard>
-GESClipPrivate
-GES_IS_CLIP
-GES_IS_CLIP_CLASS
-GES_CLIP
-GES_CLIP_CLASS
-GES_CLIP_GET_CLASS
-GES_TYPE_CLIP
-ges_clip_get_type
-</SECTION>
-
-
-<SECTION>
-<FILE>gespipeline</FILE>
-<TITLE>GESPipeline</TITLE>
-GESPipeline
-ges_pipeline_new
-ges_pipeline_set_timeline
-ges_pipeline_set_mode
-ges_pipeline_set_render_settings
-ges_pipeline_preview_get_audio_sink
-ges_pipeline_preview_get_video_sink
-ges_pipeline_preview_set_audio_sink
-ges_pipeline_preview_set_video_sink
-ges_pipeline_get_mode
-ges_pipeline_get_thumbnail
-ges_pipeline_get_thumbnail_rgb24
-ges_pipeline_save_thumbnail
-<SUBSECTION Standard>
-GESPipelineClass
-GESPipelinePrivate
-ges_play_sink_convert_frame
-ges_pipeline_get_type
-GES_PIPELINE
-GES_PIPELINE_CLASS
-GES_PIPELINE_GET_CLASS
-GES_IS_PIPELINE
-GES_IS_PIPELINE_CLASS
-GES_TYPE_PIPELINE
-</SECTION>
-
-
-<SECTION>
-<FILE>gessourceclip</FILE>
-<TITLE>GESSourceClip</TITLE>
-GESSourceClip
-GESSourceClipClass
-<SUBSECTION Standard>
-GESSourceClipPrivate
-ges_source_clip_get_type
-GES_IS_SOURCE_CLIP
-GES_IS_SOURCE_CLIP_CLASS
-GES_SOURCE_CLIP
-GES_SOURCE_CLIP_CLASS
-GES_SOURCE_CLIP_GET_CLASS
-GES_TYPE_SOURCE_CLIP
-</SECTION>
-
-<SECTION>
-<FILE>gesuriclip</FILE>
-<TITLE>GESUriClip</TITLE>
-GESUriClip
-ges_uri_clip_new
-ges_uri_clip_get_uri
-ges_uri_clip_is_image
-ges_uri_clip_is_muted
-ges_uri_clip_set_is_image
-ges_uri_clip_set_mute
-<SUBSECTION Standard>
-GESUriClipClass
-GESUriClipPrivate
-ges_uri_clip_get_type
-GES_IS_URI_CLIP
-GES_IS_URI_CLIP_CLASS
-GES_URI_CLIP
-GES_URI_CLIP_CLASS
-GES_URI_CLIP_GET_CLASS
-GES_TYPE_URI_CLIP
-</SECTION>
-
-<SECTION>
-<FILE>gesoperationclip</FILE>
-<TITLE>GESOperationClip</TITLE>
-GESOperationClip
-<SUBSECTION Standard>
-GESOperationClipClass
-GESOperationClipPrivate
-GES_OPERATION_CLIP
-GES_IS_OPERATION_CLIP
-GES_TYPE_OPERATION_CLIP
-ges_operation_clip_get_type
-GES_OPERATION_CLIP_CLASS
-GES_IS_OPERATION_CLIP_CLASS
-GES_OPERATION_CLIP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesoverlayclip</FILE>
-<TITLE>GESOverlayClip</TITLE>
-GESOverlayClip
-GESOverlayClipClass
-<SUBSECTION Standard>
-GESOverlayClipPrivate
-ges_overlay_clip_get_type
-GES_IS_OVERLAY_CLIP
-GES_IS_OVERLAY_CLIP_CLASS
-GES_OVERLAY_CLIP
-GES_OVERLAY_CLIP_CLASS
-GES_OVERLAY_CLIP_GET_CLASS
-GES_TYPE_OVERLAY_CLIP
-</SECTION>
-
-<SECTION>
-<FILE>gesbasetransitionclip</FILE>
-<TITLE>GESBaseTransitionClip</TITLE>
-GESBaseTransitionClip
-<SUBSECTION Standard>
-GESBaseTransitionClipClass
-GESBaseTransitionClipPrivate
-GES_BASE_TRANSITION_CLIP
-GES_IS_BASE_TRANSITION_CLIP
-GES_TYPE_BASE_TRANSITION_CLIP
-ges_base_transition_clip_get_type
-GES_BASE_TRANSITION_CLIP_CLASS
-GES_IS_BASE_TRANSITION_CLIP_CLASS
-GES_BASE_TRANSITION_CLIP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesbaseeffectclip</FILE>
-<TITLE>GESBaseEffectClip</TITLE>
-GESBaseEffectClip
-<SUBSECTION Standard>
-GESBaseEffectClipClass
-GESBaseEffectClipPrivate
-GES_BASE_EFFECT_CLIP
-GES_IS_BASE_EFFECT_CLIP
-GES_TYPE_BASE_EFFECT_CLIP
-ges_base_effect_clip_get_type
-GES_BASE_EFFECT_CLIP_CLASS
-GES_IS_BASE_EFFECT_CLIP_CLASS
-GES_BASE_EFFECT_CLIP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>geseffectclip</FILE>
-<TITLE>GESEffectClip</TITLE>
-GESEffectClip
-ges_effect_clip_new
-<SUBSECTION Standard>
-GESEffectClipClass
-GESEffectClipPrivate
-GES_EFFECT_CLIP_CLASS
-GES_EFFECT_CLIP
-GES_IS_EFFECT_CLIP
-GES_TYPE_EFFECT_CLIP
-ges_effect_clip_get_type
-GES_EFFECT_CLIP_CLASS_
-GES_IS_EFFECT_CLIP_CLASS
-GES_EFFECT_CLIP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gestransitionclip</FILE>
-<TITLE>GESTransitionClip</TITLE>
-GESTransitionClip
-ges_transition_clip_new
-ges_transition_clip_new_for_nick
-<SUBSECTION Standard>
-GESTransitionClipClass
-GESTransitionClipPrivate
-GES_IS_TRANSITION_CLIP
-GES_IS_TRANSITION_CLIP_CLASS
-GES_TRANSITION_CLIP
-GES_TRANSITION_CLIP_CLASS
-GES_TRANSITION_CLIP_GET_CLASS
-GES_TYPE_TRANSITION_CLIP
-ges_transition_clip_get_type
-</SECTION>
-
-
-<SECTION>
-<FILE>gestestclip</FILE>
-<TITLE>GESTestClip</TITLE>
-GESTestClip
-GESTestClipClass
-ges_test_clip_new
-ges_test_clip_new_for_nick
-ges_test_clip_get_vpattern
-ges_test_clip_get_frequency
-ges_test_clip_get_volume
-ges_test_clip_is_muted
-ges_test_clip_set_vpattern
-ges_test_clip_set_frequency
-ges_test_clip_set_mute
-ges_test_clip_set_volume
-<SUBSECTION Standard>
-GESTestClipPrivate
-ges_test_clip_get_type
-GES_TYPE_TEST_CLIP
-GES_IS_TEST_CLIP
-GES_IS_TEST_CLIP_CLASS
-GES_TEST_CLIP
-GES_TEST_CLIP_CLASS
-GES_TEST_CLIP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gestitleclip</FILE>
-<TITLE>GESTitleClip</TITLE>
-GESTitleClip
-ges_title_clip_new
-ges_title_clip_set_text
-ges_title_clip_set_font_desc
-ges_title_clip_set_halignment
-ges_title_clip_set_valignment
-ges_title_clip_set_color
-ges_title_clip_set_background
-ges_title_clip_set_xpos
-ges_title_clip_set_ypos
-ges_title_clip_get_text
-ges_title_clip_get_font_desc
-ges_title_clip_get_valignment
-ges_title_clip_get_halignment
-ges_title_clip_get_text_color
-ges_title_clip_get_background_color
-ges_title_clip_get_xpos
-ges_title_clip_get_ypos
-<SUBSECTION Standard>
-GESTitleClipClass
-GESTitleClipPrivate
-ges_title_clip_get_type
-GES_IS_TITLE_CLIP
-GES_IS_TITLE_CLIP_CLASS
-GES_TITLE_CLIP
-GES_TITLE_CLIP_CLASS
-GES_TITLE_CLIP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gestextoverlayclip</FILE>
-<TITLE>GESTextOverlayClip</TITLE>
-GESTextOverlayClip
-ges_text_overlay_clip_new
-ges_text_overlay_clip_set_text
-ges_text_overlay_clip_set_font_desc
-ges_text_overlay_clip_set_valign
-ges_text_overlay_clip_set_halign
-ges_text_overlay_clip_set_color
-ges_text_overlay_clip_set_xpos
-ges_text_overlay_clip_set_ypos
-ges_text_overlay_clip_get_text
-ges_text_overlay_clip_get_font_desc
-ges_text_overlay_clip_get_valignment
-ges_text_overlay_clip_get_halignment
-ges_text_overlay_clip_get_color
-ges_text_overlay_clip_get_xpos
-ges_text_overlay_clip_get_ypos
-<SUBSECTION Standard>
-GESTextOverlayClipClass
-GESTextOverlayClipPrivate
-ges_text_overlay_clip_get_type
-GES_IS_OVERLAY_TEXT_CLIP
-GES_IS_OVERLAY_TEXT_CLIP_CLASS
-GES_OVERLAY_TEXT_CLIP
-GES_OVERLAY_TEXT_CLIP_CLASS
-GES_OVERLAY_TEXT_CLIP_GET_CLASS
-GES_TYPE_OVERLAY_TEXT_CLIP
-</SECTION>
-
-<SECTION>
-<FILE>gesvideotestsource</FILE>
-<TITLE>GESVideoTestSource</TITLE>
-GESVideoTestSource
-ges_video_test_source_set_pattern
-ges_video_test_source_get_pattern
-<SUBSECTION Standard>
-GESVideoTestSourceClass
-GESVideoTestSourcePrivate
-ges_video_test_source_get_type
-GES_IS_VIDEO_TEST_SOURCE
-GES_IS_VIDEO_TEST_SOURCE_CLASS
-GES_VIDEO_TEST_SOURCE
-GES_VIDEO_TEST_SOURCE_CLASS
-GES_VIDEO_TEST_SOURCE_GET_CLASS
-GES_TYPE_VIDEO_TEST_SOURCE
-</SECTION>
-
-<SECTION>
-<FILE>gesaudiotestsource</FILE>
-<TITLE>GESAudioTestSource</TITLE>
-GESAudioTestSource
-ges_audio_test_source_set_freq
-ges_audio_test_source_set_volume
-ges_audio_test_source_get_freq
-ges_audio_test_source_get_volume
-<SUBSECTION Standard>
-GESAudioTestSourceClass
-GESAudioTestSourcePrivate
-ges_audio_test_source_get_type
-GES_AUDIO_TEST_SOURCE
-GES_AUDIO_TEST_SOURCE_CLASS
-GES_AUDIO_TEST_SOURCE_GET_CLASS
-GES_TYPE_AUDIO_TEST_SOURCE
-GES_IS_AUDIO_TEST_SOURCE
-GES_IS_AUDIO_TEST_SOURCE_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gestitlesource</FILE>
-<TITLE>GESTitleSource</TITLE>
-GESTitleSource
-ges_title_source_set_text
-ges_title_source_set_font_desc
-ges_title_source_set_halignment
-ges_title_source_set_valignment
-ges_title_source_set_text_color
-ges_title_source_set_background_color
-ges_title_source_set_xpos
-ges_title_source_set_ypos
-ges_title_source_get_text
-ges_title_source_get_font_desc
-ges_title_source_get_halignment
-ges_title_source_get_valignment
-ges_title_source_get_text_color
-ges_title_source_get_background_color
-ges_title_source_get_xpos
-ges_title_source_get_ypos
-<SUBSECTION Standard>
-GESTitleSourceClass
-GESTitleSourcePrivate
-ges_title_source_get_type
-GES_TITLE_SOURCE
-GES_TITLE_SOURCE_CLASS
-GES_TITLE_SOURCE_GET_CLASS
-GES_TYPE_TITLE_CLIP
-GES_TYPE_TITLE_SOURCE
-GES_IS_TITLE_SOURCE
-GES_IS_TITLE_SOURCE_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gestextoverlay</FILE>
-<TITLE>GESTextOverlay</TITLE>
-GESTextOverlay
-ges_text_overlay_new
-ges_text_overlay_set_text
-ges_text_overlay_set_font_desc
-ges_text_overlay_set_halignment
-ges_text_overlay_set_valignment
-ges_text_overlay_set_color
-ges_text_overlay_set_xpos
-ges_text_overlay_set_ypos
-ges_text_overlay_get_text
-ges_text_overlay_get_font_desc
-ges_text_overlay_get_halignment
-ges_text_overlay_get_valignment
-ges_text_overlay_get_color
-ges_text_overlay_get_xpos
-ges_text_overlay_get_ypos
-<SUBSECTION Standard>
-GESTextOverlayClass
-GESTextOverlayPrivate
-ges_text_overlay_get_type
-GES_IS_TEXT_OVERLAY
-GES_IS_TEXT_OVERLAY_CLASS
-GES_TEXT_OVERLAY
-GES_TEXT_OVERLAY_CLASS
-GES_TEXT_OVERLAY_GET_CLASS
-GES_TYPE_TEXT_OVERLAY
-</SECTION>
-
-<SECTION>
-<FILE>gesformatter</FILE>
-<TITLE>GESFormatter</TITLE>
-GESFormatter
-GESFormatterClass
-
-GESFormatterLoadFromURIMethod
-GESFormatterSaveToURIMethod
-GESFormatterCanLoadURIMethod
-GESFormatterCanSaveURIMethod
-
-ges_formatter_class_register_metas
-
-ges_formatter_load_from_uri
-ges_formatter_save_to_uri
-ges_formatter_can_load_uri
-ges_formatter_can_save_uri
-ges_formatter_get_default
-
-<SUBSECTION Standard>
-GES_FORMATTER
-GES_FORMATTER_CLASS
-GES_FORMATTER_GET_CLASS
-GES_IS_FORMATTER
-GES_IS_FORMATTER_CLASS
-GES_TYPE_FORMATTER
-<SUBSECTION Private>
-ges_formatter_get_type
-GESFormatterPrivate
-</SECTION>
-
-<SECTION>
-<FILE>gespitiviformatter</FILE>
-<TITLE>GESPitiviFormatter</TITLE>
-GESPitiviFormatter
-ges_pitivi_formatter_new
-<SUBSECTION Standard>
-GESPitiviFormatterClass
-GESPitiviFormatterPrivate
-GES_TYPE_PITIVI_FORMATTER
-GES_IS_PITIVI_FORMATTER
-GES_IS_PITIVI_FORMATTER_CLASS
-GES_PITIVI_FORMATTER
-GES_PITIVI_FORMATTER_CLASS
-GES_PITIVI_FORMATTER_GET_CLASS
-GES_TYPE_PITIVIFORMATTER
-ges_pitivi_formatter_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesbaseeffect</FILE>
-<TITLE>GESBaseEffect</TITLE>
-GESBaseEffect
-<SUBSECTION Standard>
-GESBaseEffectClass
-GESBaseEffectPrivate
-GES_IS_BASE_EFFECT
-GES_IS_BASE_EFFECT_CLASS
-GES_BASE_EFFECT
-GES_BASE_EFFECT_CLASS
-GES_BASE_EFFECT_GET_CLASS
-GES_TYPE_BASE_EFFECT
-ges_base_effect_get_type
-</SECTION>
-
-<SECTION>
-<FILE>geseffect</FILE>
-<TITLE>GESEffect</TITLE>
-GESEffect
-ges_effect_new
-<SUBSECTION Standard>
-GESEffectClass
-GESEffectPrivate
-GES_IS_EFFECT
-GES_IS_EFFECT_CLASS
-GES_EFFECT
-GES_EFFECT_CLASS
-GES_EFFECT_GET_CLASS
-GES_TYPE_EFFECT
-ges_effect_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesmetacontainer</FILE>
-<TITLE>GESMetaContainer</TITLE>
-GESMetaContainer
-GESMetaForeachFunc
-ges_meta_container_foreach
-ges_meta_container_get_meta
-ges_meta_container_get_boolean
-ges_meta_container_get_date
-ges_meta_container_get_date_time
-ges_meta_container_get_double
-ges_meta_container_get_float
-ges_meta_container_get_int
-ges_meta_container_get_int64
-ges_meta_container_get_string
-ges_meta_container_get_uint
-ges_meta_container_get_uint64
-ges_meta_container_set_boolean
-ges_meta_container_set_date
-ges_meta_container_set_date_time
-ges_meta_container_set_double
-ges_meta_container_set_float
-ges_meta_container_set_int
-ges_meta_container_set_int64
-ges_meta_container_set_string
-ges_meta_container_set_uint
-ges_meta_container_set_uint64
-ges_meta_container_set_meta
-ges_meta_container_register_meta_boolean
-ges_meta_container_register_meta_int
-ges_meta_container_register_meta_uint
-ges_meta_container_register_meta_int64
-ges_meta_container_register_meta_uint64
-ges_meta_container_register_meta_float
-ges_meta_container_register_meta_double
-ges_meta_container_register_meta_date
-ges_meta_container_register_meta_date_time
-ges_meta_container_register_meta_string
-ges_meta_container_register_meta
-ges_meta_container_metas_to_string
-ges_meta_container_add_metas_from_string
-ges_meta_container_get_type
-ges_meta_container_check_meta_registered
-
-GES_META_FORMATTER_NAME
-GES_META_FORMATTER_MIMETYPE
-GES_META_FORMATTER_EXTENSION
-GES_META_FORMATTER_VERSION
-GES_META_FORMATTER_RANK
-GES_META_DESCRIPTION
-
-GES_META_FORMAT_VERSION
-
-<SUBSECTION Standard>
-GESMetaContainerInterface
-GES_IS_META_CONTAINER
-GES_META_CONTAINER
-GES_META_CONTAINER_GET_INTERFACE
-GES_TYPE_META_CONTAINER
-ges_meta_container_get_ype
-</SECTION>
-
-<SECTION>
-<FILE>gesextractable</FILE>
-<TITLE>GESExtractableInterface</TITLE>
-GESExtractable
-GESExtractableInterface
-GESExtractableCheckId
-ges_extractable_get_asset
-ges_extractable_set_asset
-ges_extractable_get_id
-<SUBSECTION Standard>
-GES_IS_EXTRACTABLE
-GES_EXTRACTABLE
-GES_EXTRACTABLE_GET_INTERFACE
-GES_TYPE_EXTRACTABLE
-ges_extractable_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesasset</FILE>
-<TITLE>GESAsset</TITLE>
-GESAsset
-ges_asset_get_extractable_type
-ges_asset_get_id
-ges_asset_request
-ges_asset_request_async
-ges_asset_request_finish
-ges_asset_extract
-ges_asset_get_error
-ges_list_assets
-ges_asset_set_proxy
-ges_asset_list_proxies
-ges_asset_get_proxy_target
-ges_asset_get_proxy
-ges_asset_needs_reload
-<SUBSECTION Standard>
-GESAssetPrivate
-GES_ASSET
-GES_TYPE_ASSET
-GES_ASSET_CLASS
-GES_IS_ASSET
-GES_IS_ASSET_CLASS
-GES_ASSET_GET_CLASS
-ges_asset_get_type
-</SECTION>
-
-<SECTION>
-<FILE>gesclipasset</FILE>
-<TITLE>GESClipAsset</TITLE>
-GESClipAsset
-ges_clip_asset_get_type
-ges_clip_asset_set_supported_formats
-ges_clip_asset_get_supported_formats
-<SUBSECTION Standard>
-GESClipAssetPrivate
-GES_CLIP_ASSET
-GES_TYPE_CLIP_ASSET
-GES_CLIP_ASSET_CLASS
-GES_IS_CLIP_ASSET
-GES_IS_CLIP_ASSET_CLASS
-GES_CLIP_ASSET_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gestrackelementasset</FILE>
-<TITLE>GESTrackElementAsset</TITLE>
-GESTrackElementAsset
-ges_track_element_asset_get_type
-ges_track_element_asset_get_track_type
-ges_track_element_asset_set_track_type
-ges_track_element_add_children_props
-<SUBSECTION Standard>
-GESTrackElementAssetPrivate
-GES_TRACK_ELEMENT_ASSET
-GES_TYPE_TRACK_ELEMENT_ASSET
-GES_TRACK_ELEMENT_ASSET_CLASS
-GES_IS_TRACK_ELEMENT_ASSET
-GES_IS_TRACK_ELEMENT_ASSET_CLASS
-GES_TRACK_ELEMENT_ASSET_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesuriclipasset</FILE>
-<TITLE>GESUriClipAsset</TITLE>
-GESUriClipAsset
-ges_uri_clip_asset_get_type
-ges_uri_clip_asset_get_duration
-ges_uri_clip_asset_is_image
-ges_uri_clip_asset_get_info
-ges_uri_clip_asset_new
-ges_uri_clip_asset_request_sync
-ges_uri_clip_asset_get_stream_assets
-ges_uri_clip_asset_class_set_timeout
-<SUBSECTION Standard>
-GESUriClipAssetPrivate
-GES_URI_CLIP_ASSET
-GES_TYPE_URI_CLIP_ASSET
-GES_URI_CLIP_ASSET_CLASS
-GES_IS_URI_CLIP_ASSET
-GES_IS_URI_CLIP_ASSET_CLASS
-GES_URI_CLIP_ASSET_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesgroup</FILE>
-<TITLE>GESGroup</TITLE>
-GESGroup
-ges_group_new
-<SUBSECTION Standard>
-GESGroupClass
-GESGroupPrivate
-GES_GROUP
-GES_IS_GROUP
-GES_TYPE_GROUP
-ges_group_get_type
-GES_GROUP_CLASS
-GES_IS_GROUP_CLASS
-GES_GROUP_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesurisourceasset</FILE>
-<TITLE>GESUriSourceAsset</TITLE>
-GESUriSourceAsset
-ges_uri_source_asset_get_type
-ges_uri_source_asset_get_filesource_asset
-ges_uri_source_asset_get_stream_info
-ges_uri_source_asset_get_stream_uri
-<SUBSECTION Standard>
-GESUriSourceAssetPrivate
-GES_URI_SOURCE_ASSET
-GES_TYPE_URI_SOURCE_ASSET
-GES_URI_SOURCE_ASSET_CLASS
-GES_IS_URI_SOURCE_ASSET
-GES_IS_URI_SOURCE_ASSET_CLASS
-GES_URI_SOURCE_ASSET_GET_CLASS
-</SECTION>
-
-<SECTION>
-<FILE>gesproject</FILE>
-<TITLE>GESProject</TITLE>
-GESProject
-ges_project_load
-ges_project_add_asset
-ges_project_remove_asset
-ges_project_list_assets
-ges_project_get_asset
-ges_project_save
-ges_project_create_asset
-ges_project_create_asset_sync
-ges_project_get_type
-ges_project_get_uri
-ges_project_new
-ges_project_add_encoding_profile
-ges_project_list_encoding_profiles
-ges_project_get_loading_assets
-<SUBSECTION Standard>
-GESProjectPrivate
-GES_PROJECT
-GES_PROJECT_CLASS
-GES_IS_PROJECT
-GES_IS_PROJECT_CLASS
-GES_PROJECT_GET_CLASS
-GES_TYPE_PROJECT
-</SECTION>
-
-<SECTION>
-<FILE>gesbasexmlformatter</FILE>
-<TITLE>GESBaseXmlFormatter</TITLE>
-GESBaseXmlFormatter
-ges_base_xml_formatter_get_type
-<SUBSECTION Standard>
-GESBaseXmlFormatterPrivate
-GES_BASE_XML_FORMATTER
-GES_BASE_XML_FORMATTER_CLASS
-GES_IS_BASE_XML_FORMATTER
-GES_IS_BASE_XML_FORMATTER_CLASS
-GES_BASE_XML_FORMATTER_GET_CLASS
-GES_TYPE_BASE_XML_FORMATTER
-</SECTION>
-
-<SECTION>
-<FILE>gesxmlformatter</FILE>
-<TITLE>GESXmlFormatter</TITLE>
-ges_xml_formatter_get_type
-<SUBSECTION Standard>
-GESXmlFormatterPrivate
-GES_XML_FORMATTER
-GES_TYPE_XML_FORMATTER
-GES_XML_FORMATTER_CLASS
-GES_XML_FORMATTER_GET_CLASS
-GES_IS_XML_FORMATTER
-GES_IS_XML_FORMATTER_CLASS
-</SECTION>
diff --git a/docs/libs/ges.types b/docs/libs/ges.types
deleted file mode 100644 (file)
index 0dd84aa..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-#include <gst/gst.h>
-#include <ges/ges.h>
-
-ges_formatter_get_type
-%ges_text_halign_get_type
-%ges_text_valign_get_type
-ges_timeline_get_type
-ges_layer_get_type
-ges_clip_get_type
-ges_operation_clip_get_type
-ges_overlay_clip_get_type
-ges_pipeline_get_type
-ges_source_clip_get_type
-ges_test_clip_get_type
-ges_base_transition_clip_get_type
-ges_transition_clip_get_type
-ges_base_effect_clip_get_type
-ges_effect_clip_get_type
-ges_uri_clip_get_type
-ges_text_overlay_clip_get_type
-ges_title_clip_get_type
-ges_audio_test_source_get_type
-ges_audio_transition_get_type
-ges_video_uri_source_get_type
-ges_audio_uri_source_get_type
-ges_track_get_type
-ges_image_source_get_type
-ges_multi_file_source_get_type
-ges_track_element_get_type
-ges_base_effect_get_type
-ges_effect_get_type
-ges_operation_get_type
-ges_source_get_type
-ges_text_overlay_get_type
-ges_title_source_get_type
-ges_transition_get_type
-%ges_track_type_get_type
-ges_video_test_source_get_type
-ges_video_transition_get_type
-ges_project_get_type
-%ges_video_test_pattern_get_type
-%ges_video_standard_transition_type_get_type
-ges_meta_container_get_type
diff --git a/docs/libs/meson.build b/docs/libs/meson.build
deleted file mode 100644 (file)
index eb27c85..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-types = configure_file(input : 'ges.types',
-  output : 'ges.types',
-  copy: true)
-
-doc_deps_names = ['glib-2.0',
-                  'gstreamer-@0@'.format(apiversion),
-                  'gstreamer-plugins-base-@0@'.format(apiversion)]
-
-doc_deps = []
-foreach doc_dep : doc_deps_names
-    # TODO: Use get_pkgconfig_variable()
-    runcmd = run_command('pkg-config', '--variable=prefix', doc_dep)
-    if runcmd.returncode() == 0
-        tmp = '--extra-dir=' + runcmd.stdout().strip() + '/share/gtk-doc/html/'
-        tmp.strip()
-        doc_deps = doc_deps + [tmp]
-    endif
-endforeach
-
-gnome.gtkdoc('ges',
-  main_sgml : 'ges-docs.sgml',
-  src_dir : '@0@/../../ges'.format(meson.current_source_dir()),
-  scan_args : ['--deprecated-guards=GST_DISABLE_DEPRECATED',
-            '--ignore-decorators=GES_API',
-            '--ignore-headers=gesmarshal.h ges-internal.h ges-auto-transition.h ges-structured-interface.h ges-structure-parser.h ges-smart-video-mixer.h gstframepositioner.h'
-            ],
-  scanobjs_args : ['--type-init-func="gst_init(NULL,NULL)"'],
-  gobject_typesfile : types,
-  dependencies : [ges_dep],
-  fixxref_args: doc_deps + ['--html-dir=' + get_option('prefix') + '/share/gtk-doc/html/'],
-  content_files : ['architecture.xml', 'ges-sections.txt', version_entities],
-  html_assets : 'layer_track_overview.png',
-  install : true,
-  install_dir : 'gstreamer-editing-services',
-)
diff --git a/docs/low_level.md b/docs/low_level.md
new file mode 100644 (file)
index 0000000..fa1f8b5
--- /dev/null
@@ -0,0 +1,5 @@
+# Low level APIs
+
+Those APIs should usually not be used unless you know
+what you are doing, check other parts of the documentation
+before deciding you should use one of those.
index 49f7967..c3c91ef 100644 (file)
-docconf = configuration_data()
+build_hotdoc = false
 
-docconf.set('GST_API_VERSION', apiversion)
-docconf.set('VERSION', gst_version)
-docconf.set('PLUGINDIR', '@0@/lib/gstreamer-1.0'.format(get_option('prefix')))
+if meson.is_cross_build()
+    if get_option('doc').enabled()
+        error('Documentation enabled but building the doc while cross building is not supported yet.')
+    endif
 
-version_entities = configure_file(input : 'version.entities.in',
-  output : 'version.entities',
-  configuration : docconf)
+    message('Documentation not built as building it while cross building is not supported yet.')
+    subdir_done()
+endif
 
-subdir('libs')
+required_hotdoc_extensions = ['gi-extension', 'gst-extension']
+if gst_dep.type_name() == 'internal'
+    gst_proj = subproject('gstreamer')
+    plugins_cache_generator = gst_proj.get_variable('plugins_cache_generator')
+else
+    plugins_cache_generator = find_program(join_paths(gst_dep.get_pkgconfig_variable('libexecdir'), 'gstreamer-' + apiversion, 'gst-plugins-doc-cache-generator'), required: false)
+endif
+
+plugins_cache = join_paths(meson.current_source_dir(), 'gst_plugins_cache.json')
+
+if plugins_cache_generator.found()
+    plugins_doc_dep = custom_target('editing-services-doc-cache',
+        command: [plugins_cache_generator, plugins_cache, '@OUTPUT@', '@INPUT@'],
+        input: plugins,
+        output: 'gst_plugins_cache.json',
+        build_always_stale: true,
+    )
+else
+    warning('GStreamer plugin inspector for documentation not found, can\'t update the cache')
+endif
+
+hotdoc_p = find_program('hotdoc', required: get_option('doc'))
+if not hotdoc_p.found()
+    message('Hotdoc not found, not building the documentation')
+    subdir_done()
+endif
+
+hotdoc_req = '>= 0.11.0'
+hotdoc_version = run_command(hotdoc_p, '--version').stdout()
+if not hotdoc_version.version_compare(hotdoc_req)
+    if get_option('doc').enabled()
+        error('Hotdoc version @0@ not found, got @1@'.format(hotdoc_req, hotdoc_version))
+    else
+        message('Hotdoc version @0@ not found, got @1@, not building documentation'.format(hotdoc_req, hotdoc_version))
+        subdir_done()
+    endif
+endif
+
+hotdoc = import('hotdoc')
+foreach extension: required_hotdoc_extensions
+    if not hotdoc.has_extensions(extension)
+        if get_option('doc').enabled()
+            error('Documentation enabled but gi-extension missing')
+        endif
+
+        message('@0@ extensions not found, not building documentation requiring it'.format(extension))
+    endif
+endforeach
+
+if not build_gir
+    if get_option('doc').enabled()
+        error('Documentation enabled but introspection not built.')
+    endif
+
+    message('Introspection not built, can\'t build the documentation')
+    subdir_done()
+endif
+
+build_hotdoc = true
+ges_excludes = []
+foreach f: ['gesmarshal.*',
+            'ges-internal.*',
+            'ges-auto-transition.*',
+            'ges-structured-interface.*',
+            'ges-structure-parser.*',
+            'ges-version.h',
+            'ges-smart-*',
+            'ges-command-line-formatter.*',
+            'ges-base-xml-formatter.h',
+            'gstframepositioner.*',
+            'lex.priv_ges_parse_yy.c',
+            'ges-parse-lex.[c]']
+    ges_excludes += [join_paths(meson.current_source_dir(), '..', '..', 'ges', f)]
+endforeach
+
+hotdoc = import('hotdoc')
+libs_doc = [hotdoc.generate_doc('gst-editing-services',
+    project_version: apiversion,
+    extra_assets: [join_paths(meson.current_source_dir(), 'images')],
+    gi_c_sources: ges_sources + ges_headers,
+    gi_c_source_roots: [join_paths(meson.current_source_dir(), '../ges/')],
+    gi_sources: [ges_gir[0].full_path()],
+    gi_c_source_filters: ges_excludes,
+    sitemap: 'sitemap.txt',
+    index: 'index.md',
+    gi_index: 'index.md',
+    gi_smart_index: true,
+    gi_order_generated_subpages: true,
+    dependencies: [ges_dep],
+    disable_incremental_build: true,
+)]
+
+plugins_doc = []
+list_plugin_res = run_command(python3, '-c',
+'''
+import sys
+import json
+
+with open("@0@") as f:
+    print(':'.join(json.load(f).keys()), end='')
+'''.format(plugins_cache))
+
+assert(list_plugin_res.returncode() == 0,
+  'Could not list plugins from @0@\n@1@\n@1@'.format(plugins_cache, list_plugin_res.stdout(), list_plugin_res.stderr()))
+
+
+foreach plugin_name: list_plugin_res.stdout().split(':')
+    plugins_doc += [hotdoc.generate_doc(plugin_name,
+        project_version: apiversion,
+        sitemap: 'plugins/sitemap.txt',
+        index: 'plugins/index.md',
+        gst_index: 'plugins/index.md',
+        gst_smart_index: true,
+        gst_c_sources: ['../plugins/*/*.[ch]',],
+        dependencies: [gst_dep, plugins],
+        gst_order_generated_subpages: true,
+        gst_cache_file: plugins_cache,
+        gst_plugin_name: plugin_name,
+    )]
+endforeach
similarity index 100%
rename from m4/Makefile.am
rename to docs/plugins/index.md
diff --git a/docs/plugins/nle.md b/docs/plugins/nle.md
new file mode 100644 (file)
index 0000000..97b8968
--- /dev/null
@@ -0,0 +1,7 @@
+---
+short-description: Non Linear Engine
+...
+
+# Non Linear Engine
+
+NLE is a set of elements to implement Non Linear multimedia editing.
diff --git a/docs/plugins/sitemap.txt b/docs/plugins/sitemap.txt
new file mode 100644 (file)
index 0000000..d82d519
--- /dev/null
@@ -0,0 +1 @@
+gst-index
\ No newline at end of file
diff --git a/docs/sitemap.txt b/docs/sitemap.txt
new file mode 100644 (file)
index 0000000..c68b6ec
--- /dev/null
@@ -0,0 +1,65 @@
+gi-index
+       ges.h
+       ges-timeline.h
+       ges-layer.h
+       ges-clip.h
+               ges-uri-clip.h
+               ges-title-clip.h
+               ges-test-clip.h
+               ges-time-overlay-clip.h
+               ges-effect-clip.h
+               ges-transition-clip.h
+       ges-pipeline.h
+       ges-project.h
+       base-classes.md
+               ges-timeline-element.h
+               ges-container.h
+       ges-track.h
+               ges-audio-track.h
+               ges-video-track.h
+       ges-asset.h
+               ges-uri-asset.h
+               ges-clip-asset.h
+               ges-effect-asset.h
+               ges-track-element-asset.h
+               ges-source-clip-asset.h
+       ges-effect.h
+       ges-extractable.h
+       ges-group.h
+       ges-meta-container.h
+               ges-marker-list.h
+       ges-formatter.h
+               ges-xml-formatter.h
+       ges-track-element.h
+               ges-video-source.h
+               ges-audio-source.h
+               ges-audio-test-source.h
+               ges-audio-uri-source.h
+               ges-video-uri-source.h
+               ges-video-test-source.h
+               ges-title-source.h
+               ges-text-overlay.h
+       ges-gerror.h
+       ges-types.h
+       ges-enums.h
+       ges-utils.h
+       low_level.md
+               ges-base-xml-formatter.h
+               ges-command-line-formatter.h
+               ges-audio-transition.h
+               ges-base-effect-clip.h
+               ges-base-effect.h
+               ges-base-transition-clip.h
+               ges-operation-clip.h
+               ges-operation.h
+               ges-overlay-clip.h
+               ges-source-clip.h
+               ges-source.h
+               ges-text-overlay-clip.h
+               ges-transition.h
+               ges-video-transition.h
+               ges-prelude.h
+       deprecated.md
+               ges-pitivi-formatter.h
+               ges-image-source.h
+               ges-multi-file-source.h
\ No newline at end of file
diff --git a/examples/.gitignore b/examples/.gitignore
deleted file mode 100644 (file)
index c533fa6..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-assets
-concatenate
-ges-ui
-play_timeline_with_one_clip
-test1
-test2
-test3
-test4
-simple1
-text_properties
-transition
-thumbnails
-overlays
-multifilesrc
-c/gessrc
-
diff --git a/examples/Makefile.am b/examples/Makefile.am
deleted file mode 100644 (file)
index a328175..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-SUBDIRS = c
-
-AM_CFLAGS =  -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) $(GST_CFLAGS) $(GTK_CFLAGS)
-AM_LDFLAGS = -export-dynamic
-LDADD = $(top_builddir)/ges/libges-@GST_API_VERSION@.la $(GST_PBUTILS_LIBS) $(GST_LIBS) $(GTK_LIBS)
diff --git a/examples/c/Makefile.am b/examples/c/Makefile.am
deleted file mode 100644 (file)
index 1efb505..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-if HAVE_GTK_X11
-graphical=ges-ui
-else
-graphical=
-endif
-
-noinst_PROGRAMS =      \
-       concatenate     \
-       gessrc          \
-       simple1         \
-       test1           \
-       test2           \
-       test3           \
-       test4           \
-       transition      \
-       thumbnails      \
-       overlays        \
-       text_properties \
-       assets \
-       multifilesrc \
-       play_timeline_with_one_clip \
-       $(graphical)
-
-ERROR_CFLAGS=
-
-AM_CFLAGS =  -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) $(GST_CFLAGS) $(GTK_CFLAGS)
-AM_LDFLAGS = -export-dynamic
-LDADD = $(top_builddir)/ges/libges-@GST_API_VERSION@.la $(GST_PBUTILS_LIBS) $(GST_LIBS) $(GTK_LIBS)
-
index f84dea1..b543cc9 100644 (file)
@@ -27,7 +27,9 @@ bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop);
 
 static GstEncodingProfile *make_profile_from_info (GstDiscovererInfo * info);
 
+GESLayer *layer = NULL;
 GESPipeline *pipeline = NULL;
+GESTimeline *timeline = NULL;
 gchar *output_uri = NULL;
 guint assetsCount = 0;
 guint assetsLoaded = 0;
@@ -37,16 +39,23 @@ asset_loaded_cb (GObject * source_object, GAsyncResult * res,
     GMainLoop * mainloop)
 {
   GError *error = NULL;
+  guint64 duration = 0;
 
   GESUriClipAsset *mfs =
       GES_URI_CLIP_ASSET (ges_asset_request_finish (res, &error));
 
   if (error) {
-    GST_WARNING ("error creating asseti %s", error->message);
+    GST_WARNING ("error creating asset %s", error->message);
 
     return;
   }
 
+  duration = ges_uri_clip_asset_get_duration (mfs);
+  ges_layer_add_asset (layer,
+      GES_ASSET (source_object),
+      ges_timeline_get_duration (timeline),
+      0, duration, ges_clip_asset_get_supported_formats (GES_CLIP_ASSET (mfs)));
+
   assetsLoaded++;
   /*
    * Check if we have loaded last asset and trigger concatenating
@@ -71,13 +80,12 @@ main (int argc, char **argv)
 {
   GMainLoop *mainloop = NULL;
   GESTimeline *timeline;
-  GESLayer *layer = NULL;
   GstBus *bus = NULL;
   guint i;
 
 
   if (argc < 3) {
-    g_print ("Usage: %s <output uri> <list of files>\n", argv[0]);
+    gst_print ("Usage: %s <output uri> <list of files>\n", argv[0]);
     return -1;
   }
 
@@ -123,11 +131,11 @@ bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop)
 {
   switch (GST_MESSAGE_TYPE (message)) {
     case GST_MESSAGE_ERROR:
-      g_print ("ERROR\n");
+      gst_print ("ERROR\n");
       g_main_loop_quit (mainloop);
       break;
     case GST_MESSAGE_EOS:
-      g_print ("Done\n");
+      gst_print ("Done\n");
       g_main_loop_quit (mainloop);
       break;
     default:
index 45694a6..5f7d2d5 100644 (file)
@@ -380,7 +380,7 @@ layer_object_removed_cb (GESLayer * layer, GESClip * clip, App * app)
   GST_INFO ("layer clip removed cb %p %p %p", layer, clip, app);
 
   if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, clip)) {
-    g_print ("clip deleted but we don't own it");
+    gst_print ("clip deleted but we don't own it");
     return;
   }
   app->n_objects--;
@@ -446,11 +446,11 @@ project_bus_message_cb (GstBus * bus, GstMessage * message,
 {
   switch (GST_MESSAGE_TYPE (message)) {
     case GST_MESSAGE_ERROR:
-      g_printerr ("ERROR\n");
+      gst_printerr ("ERROR\n");
       g_main_loop_quit (mainloop);
       break;
     case GST_MESSAGE_EOS:
-      g_printerr ("Done\n");
+      gst_printerr ("Done\n");
       g_main_loop_quit (mainloop);
       break;
     default:
@@ -466,7 +466,7 @@ bus_message_cb (GstBus * bus, GstMessage * message, App * app)
 
   switch (GST_MESSAGE_TYPE (message)) {
     case GST_MESSAGE_ERROR:
-      g_print ("ERROR\n");
+      gst_print ("ERROR\n");
       break;
     case GST_MESSAGE_EOS:
       gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
index 8e52078..40917f1 100644 (file)
@@ -28,11 +28,11 @@ bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop)
 {
   switch (GST_MESSAGE_TYPE (message)) {
     case GST_MESSAGE_ERROR:
-      g_printerr ("Got error message on the bus\n");
+      gst_printerr ("Got error message on the bus\n");
       g_main_loop_quit (mainloop);
       break;
     case GST_MESSAGE_EOS:
-      g_print ("Done\n");
+      gst_print ("Done\n");
       g_main_loop_quit (mainloop);
       break;
     default:
@@ -60,7 +60,7 @@ main (int argc, char **argv)
   GstClockTime start = 0;
 
   if (argc < 2) {
-    g_print ("Usage: %s <list of files>\n", argv[0]);
+    gst_print ("Usage: %s <list of files>\n", argv[0]);
     return -1;
   }
 
@@ -85,7 +85,7 @@ main (int argc, char **argv)
     clip = GES_CLIP (ges_uri_clip_new (uri));
 
     if (!clip) {
-      g_printerr ("Could not create clip for file: %s\n", argv[i]);
+      gst_printerr ("Could not create clip for file: %s\n", argv[i]);
       g_free (uri);
       goto err;
     }
index 05a4c29..ac57ff9 100644 (file)
@@ -50,14 +50,14 @@ main (int argc, gchar ** argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_print ("Error initializing %s\n", err->message);
+    gst_print ("Error initializing %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     exit (1);
   }
 
   if (filepattern == NULL) {
-    g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
+    gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     exit (0);
   }
   g_option_context_free (ctx);
index d0edc3b..331504f 100644 (file)
@@ -148,14 +148,14 @@ main (int argc, char **argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_print ("Error initializing %s\n", err->message);
+    gst_print ("Error initializing %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     exit (1);
   }
 
   if (argc > 1) {
-    g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
+    gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     exit (0);
   }
 
index cfb8481..53ae235 100644 (file)
@@ -10,7 +10,7 @@ main (int argc, char **argv)
   GESTimeline *timeline;
 
   if (argc == 1) {
-    g_printerr ("Usage: play_timeline_with_one_clip file:///clip/uri\n");
+    gst_printerr ("Usage: play_timeline_with_one_clip file:///clip/uri\n");
 
     return 1;
   }
@@ -26,7 +26,8 @@ main (int argc, char **argv)
     GESClip *clip = GES_CLIP (ges_uri_clip_new (argv[1]));
 
     if (clip == NULL) {
-      g_printerr ("%s can not be used, make sure it is a supported media file",
+      gst_printerr
+          ("%s can not be used, make sure it is a supported media file",
           argv[1]);
 
       return 1;
index bc008e6..0423968 100644 (file)
@@ -28,9 +28,9 @@ main (int argc, gchar ** argv)
   GOptionContext *ctx;
   GESPipeline *pipeline;
   GESTimeline *timeline;
-  GESTrack *tracka, *trackv;
-  GESLayer *layer1, *layer2;
+  GESLayer *layer1;
   GESUriClip *src;
+  gchar *uri;
   GMainLoop *mainloop;
 
   gint inpoint = 0, duration = 10;
@@ -56,14 +56,14 @@ main (int argc, gchar ** argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_print ("Error initializing %s\n", err->message);
+    gst_print ("Error initializing %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     exit (1);
   }
 
   if (argc == 1) {
-    g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
+    gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     exit (0);
   }
   g_option_context_free (ctx);
@@ -72,33 +72,22 @@ main (int argc, gchar ** argv)
 
   /* Create an Audio/Video pipeline with two layers */
   pipeline = ges_pipeline_new ();
+  timeline = ges_timeline_new_audio_video ();
+  layer1 = ges_timeline_append_layer (timeline);
 
-  timeline = ges_timeline_new ();
-
-  tracka = GES_TRACK (ges_audio_track_new ());
-  trackv = GES_TRACK (ges_video_track_new ());
-
-  layer1 = ges_layer_new ();
-  layer2 = ges_layer_new ();
-  g_object_set (layer2, "priority", 1, NULL);
-
-  if (!ges_timeline_add_layer (timeline, layer1) ||
-      !ges_timeline_add_layer (timeline, layer2) ||
-      !ges_timeline_add_track (timeline, tracka) ||
-      !ges_timeline_add_track (timeline, trackv) ||
-      !ges_pipeline_set_timeline (pipeline, timeline))
+  if (!ges_pipeline_set_timeline (pipeline, timeline)) {
+    g_error ("Could not set timeline to pipeline");
     return -1;
-
-  if (1) {
-    gchar *uri = gst_filename_to_uri (argv[1], NULL);
-    /* Add the main audio/video file */
-    src = ges_uri_clip_new (uri);
-    g_free (uri);
-    g_object_set (src, "start", 0, "in-point", inpoint * GST_SECOND,
-        "duration", duration * GST_SECOND, "mute", mute, NULL);
-    ges_layer_add_clip (layer1, GES_CLIP (src));
   }
 
+  uri = gst_filename_to_uri (argv[1], NULL);
+  /* Add the main audio/video file */
+  src = ges_uri_clip_new (uri);
+  ges_layer_add_clip (layer1, GES_CLIP (src));
+  g_free (uri);
+  g_object_set (src, "start", 0, "in-point", inpoint * GST_SECOND,
+      "duration", duration * GST_SECOND, "mute", mute, NULL);
+
   /* Play the pipeline */
   gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
   mainloop = g_main_loop_new (NULL, FALSE);
index c0bf994..86229aa 100644 (file)
@@ -31,7 +31,7 @@ main (int argc, gchar ** argv)
   guint i;
 
   if (argc < 2) {
-    g_print ("Usage: %s <list of audio files>\n", argv[0]);
+    gst_print ("Usage: %s <list of audio files>\n", argv[0]);
     return -1;
   }
 
@@ -58,7 +58,7 @@ main (int argc, gchar ** argv)
   if (!ges_timeline_add_track (timeline, tracka))
     return -1;
 
-  /* Here we've finished initializing our timeline, we're 
+  /* Here we've finished initializing our timeline, we're
    * ready to start using it... by solely working with the layer ! */
 
   for (i = 1; i < argc; i++, offset += GST_SECOND) {
index d458c97..51a6e31 100644 (file)
@@ -30,7 +30,7 @@ main (int argc, gchar ** argv)
   guint i;
 
   if (argc < 2) {
-    g_print ("Usage: %s <list of audio files>\n", argv[0]);
+    gst_print ("Usage: %s <list of audio files>\n", argv[0]);
     return -1;
   }
 
@@ -57,7 +57,7 @@ main (int argc, gchar ** argv)
   if (!ges_timeline_add_track (timeline, tracka))
     return -1;
 
-  /* Here we've finished initializing our timeline, we're 
+  /* Here we've finished initializing our timeline, we're
    * ready to start using it... by solely working with the layer ! */
 
   for (i = 1; i < argc; i++) {
index 070b869..1cec66b 100644 (file)
@@ -23,7 +23,7 @@
 GstEncodingProfile *make_encoding_profile (gchar * audio, gchar * container);
 
 /* This example will take a series of files and create a audio-only timeline
- * containing the first second of each file and render it to the output uri 
+ * containing the first second of each file and render it to the output uri
  * using ogg/vorbis */
 
 /* make_encoding_profile
@@ -79,7 +79,7 @@ main (int argc, gchar ** argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_printerr ("Error initializing: %s\n", err->message);
+    gst_printerr ("Error initializing: %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     return -1;
@@ -87,7 +87,7 @@ main (int argc, gchar ** argv)
   g_option_context_free (ctx);
 
   if (argc < 3) {
-    g_print ("Usage: %s <output uri> <list of audio files>\n", argv[0]);
+    gst_print ("Usage: %s <output uri> <list of audio files>\n", argv[0]);
     return -1;
   }
 
@@ -114,7 +114,7 @@ main (int argc, gchar ** argv)
   if (!ges_timeline_add_track (timeline, tracka))
     return -1;
 
-  /* Here we've finished initializing our timeline, we're 
+  /* Here we've finished initializing our timeline, we're
    * ready to start using it... by solely working with the layer ! */
 
   for (i = 2; i < argc; i++) {
@@ -147,7 +147,7 @@ main (int argc, gchar ** argv)
   } else if (g_file_test (argv[1], G_FILE_TEST_EXISTS)) {
     output_uri = gst_filename_to_uri (argv[1], NULL);
   } else {
-    g_printerr ("Unrecognised command line argument '%s'.\n"
+    gst_printerr ("Unrecognised command line argument '%s'.\n"
         "Please pass an URI or file as argument!\n", argv[1]);
     return -1;
   }
index 3dc6e06..2252587 100644 (file)
@@ -111,14 +111,14 @@ main (int argc, char **argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_print ("Error initializing %s\n", err->message);
+    gst_print ("Error initializing %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     exit (1);
   }
 
   if (argc > 1) {
-    g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
+    gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     exit (0);
   }
 
index 9211bc7..0047cb5 100644 (file)
@@ -112,19 +112,19 @@ bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop)
 {
   switch (GST_MESSAGE_TYPE (message)) {
     case GST_MESSAGE_ERROR:
-      g_print ("ERROR\n");
+      gst_print ("ERROR\n");
       g_main_loop_quit (mainloop);
       break;
     case GST_MESSAGE_EOS:
       if (repeat > 0) {
-        g_print ("Looping again\n");
+        gst_print ("Looping again\n");
         /* No need to change state before */
         gst_element_seek_simple (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
             GST_SEEK_FLAG_FLUSH, 0);
         gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
         repeat -= 1;
       } else {
-        g_print ("Done\n");
+        gst_print ("Done\n");
         g_main_loop_quit (mainloop);
       }
       break;
@@ -150,7 +150,7 @@ main (int argc, gchar ** argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_print ("Error initializing: %s\n", err->message);
+    gst_print ("Error initializing: %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     exit (1);
@@ -170,7 +170,7 @@ main (int argc, gchar ** argv)
   /* Play the pipeline */
   mainloop = g_main_loop_new (NULL, FALSE);
 
-  g_print ("thumbnailing every 1 seconds\n");
+  gst_print ("thumbnailing every 1 seconds\n");
   g_timeout_add (1000, thumbnail_cb, pipeline);
 
   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
@@ -179,7 +179,7 @@ main (int argc, gchar ** argv)
 
   if (gst_element_set_state (GST_ELEMENT (pipeline),
           GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
-    g_print ("Failed to start the encoding\n");
+    gst_print ("Failed to start the encoding\n");
     return 1;
   }
   g_main_loop_run (mainloop);
index 7992e2a..8a38549 100644 (file)
@@ -76,7 +76,7 @@ print_transition_data (GESClip * tr)
 
   g_object_get (nleobj, "start", &start, "duration", &duration,
       "priority", &priority, "name", &name, NULL);
-  g_print ("nleobject for %s: %f %f %d\n", name,
+  gst_print ("nleobject for %s: %f %f %d\n", name,
       ((gfloat) start) / GST_SECOND,
       ((gfloat) duration) / GST_SECOND, priority);
 
@@ -128,7 +128,7 @@ make_timeline (gchar * nick, gdouble tdur, gchar * patha, gfloat adur,
   g_timeout_add_seconds (1, (GSourceFunc) print_transition_data, srcb);
 
   if (tduration != 0) {
-    g_print ("creating transition at %" GST_TIME_FORMAT " of %f duration (%"
+    gst_print ("creating transition at %" GST_TIME_FORMAT " of %f duration (%"
         GST_TIME_FORMAT ")\n", GST_TIME_ARGS (tstart), tdur,
         GST_TIME_ARGS (tduration));
     if (!(tr = ges_transition_clip_new_for_nick (nick)))
@@ -172,14 +172,14 @@ main (int argc, char **argv)
   g_option_context_add_group (ctx, gst_init_get_option_group ());
 
   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
-    g_print ("Error initializing %s\n", err->message);
+    gst_print ("Error initializing %s\n", err->message);
     g_option_context_free (ctx);
     g_clear_error (&err);
     exit (1);
   }
 
   if (argc < 4) {
-    g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
+    gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     exit (0);
   }
 
index 7cb2ae5..51ba739 100755 (executable)
@@ -1,3 +1,4 @@
+#!/usr/bin/python3
 import sys
 import gi
 
index 1de9f6e..7874e97 100755 (executable)
@@ -31,22 +31,17 @@ from gi.repository import Gst, GES, GLib  # noqa
 class Simple:
     def __init__(self, uri):
         timeline = GES.Timeline.new_audio_video()
-        self.project = timeline.get_asset()
-
-        self.project.connect("asset-added", self._asset_added_cb)
-        self.project.connect("error-loading-asset", self._error_loading_asset_cb)
-        self.project.create_asset(uri, GES.UriClip)
-        self.layer = timeline.append_layer()
-        self._create_pipeline(timeline)
-        self.loop = GLib.MainLoop()
-
-    def _create_pipeline(self, timeline):
-        self.pipeline = GES.Pipeline()
-        self.pipeline.set_timeline(timeline)
-        bus = self.pipeline.get_bus()
+        layer = timeline.append_layer()
+        layer.add_clip(GES.UriClip.new(uri))
+        self.pipeline = pipeline = GES.Pipeline()
+        pipeline.set_timeline(timeline)
+        pipeline.set_state(Gst.State.PLAYING)
+        bus = pipeline.get_bus()
         bus.add_signal_watch()
         bus.connect("message", self.bus_message_cb)
 
+        self.loop = GLib.MainLoop()
+
     def bus_message_cb(self, unused_bus, message):
         if message.type == Gst.MessageType.EOS:
             print("eos")
@@ -59,14 +54,6 @@ class Simple:
     def start(self):
         self.loop.run()
 
-    def _asset_added_cb(self, project, asset):
-        self.layer.add_asset(asset, 0, 0, Gst.SECOND * 5, GES.TrackType.UNKNOWN)
-        self.pipeline.set_state(Gst.State.PLAYING)
-
-    def _error_loading_asset_cb(self, project, error, asset_id, type):
-        print("Could not load asset %s: %s" % (asset_id, error))
-        self.loop.quit()
-
 if __name__ == "__main__":
     if len(os.sys.argv) != 2:
         print("You must specify a file URI")
diff --git a/ges/.gitignore b/ges/.gitignore
deleted file mode 100644 (file)
index 5947602..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-*.gir
-*.typelib
diff --git a/ges/Makefile.am b/ges/Makefile.am
deleted file mode 100644 (file)
index a79a73a..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-built_header_make =
-built_source_make =
-
-lib_LTLIBRARIES = libges-@GST_API_VERSION@.la
-
-
-EXTRA_libges_@GST_API_VERSION@_la_SOURCES = gesmarshal.list
-
-EXTRA_DIST=parse.l
-
-CLEANFILES = $(BUILT_SOURCES) $(built_header_make) $(built_source_make) *.gcno *.gcda *.gcov *.gcov.out
-
-nodist_libges_@GST_API_VERSION@_la_SOURCES = lex.priv_ges_parse_yy.c ges-parse-lex.h
-
-
-libges_@GST_API_VERSION@_la_SOURCES =          \
-       $(built_source_make)                    \
-       ges.c                                   \
-       ges-enums.c                             \
-       ges-meta-container.c        \
-       ges-timeline.c                          \
-       ges-layer.c                     \
-       ges-clip.c                      \
-       ges-pipeline.c                  \
-       ges-source-clip.c                       \
-       ges-base-effect-clip.c          \
-       ges-effect-clip.c               \
-       ges-uri-clip.c          \
-       ges-operation-clip.c            \
-       ges-base-transition-clip.c              \
-       ges-transition-clip.c   \
-       ges-test-clip.c         \
-       ges-title-clip.c                \
-       ges-overlay-clip.c                      \
-       ges-text-overlay-clip.c         \
-       ges-track.c                             \
-       ges-audio-track.c \
-       ges-video-track.c \
-       ges-track-element.c                     \
-       ges-source.c                    \
-       ges-operation.c                 \
-       ges-video-source.c \
-       ges-audio-source.c \
-       ges-video-uri-source.c                  \
-       ges-audio-uri-source.c  \
-       ges-image-source.c              \
-       ges-multi-file-source.c         \
-       ges-transition.c                        \
-       ges-audio-transition.c          \
-       ges-video-transition.c          \
-       ges-video-test-source.c         \
-       ges-audio-test-source.c         \
-       ges-title-source.c              \
-       ges-text-overlay.c              \
-       ges-base-effect.c               \
-       ges-effect.c            \
-       ges-screenshot.c                        \
-       ges-formatter.c                         \
-       ges-pitivi-formatter.c                  \
-       ges-asset.c \
-       ges-uri-asset.c \
-       ges-clip-asset.c \
-       ges-track-element-asset.c \
-       ges-extractable.c \
-       ges-project.c \
-       ges-base-xml-formatter.c \
-       ges-xml-formatter.c \
-       ges-command-line-formatter.c \
-       ges-auto-transition.c \
-       ges-timeline-element.c \
-       ges-timeline-tree.c \
-       ges-container.c \
-       ges-effect-asset.c \
-       ges-smart-adder.c \
-       ges-smart-video-mixer.c \
-       ges-utils.c \
-       ges-group.c \
-       ges-validate.c \
-       ges-structured-interface.c \
-       ges-structure-parser.c \
-       gstframepositioner.c
-
-libges_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/ges/
-libges_@GST_API_VERSION@include_HEADERS =      \
-       $(built_header_make)                    \
-       ges-types.h                             \
-       ges.h                                   \
-       ges-prelude.h                           \
-       ges-enums.h                             \
-       ges-gerror.h                            \
-       ges-meta-container.h        \
-       ges-timeline.h                          \
-       ges-layer.h                     \
-       ges-clip.h                      \
-       ges-pipeline.h                  \
-       ges-source-clip.h                       \
-       ges-uri-clip.h          \
-       ges-base-effect-clip.h          \
-       ges-effect-clip.h               \
-       ges-operation-clip.h            \
-       ges-base-transition-clip.h              \
-       ges-transition-clip.h   \
-       ges-test-clip.h         \
-       ges-title-clip.h                \
-       ges-overlay-clip.h                      \
-       ges-text-overlay-clip.h         \
-       ges-base-effect.h               \
-       ges-effect.h            \
-       ges-track.h                             \
-       ges-audio-track.h \
-       ges-video-track.h \
-       ges-track-element.h                     \
-       ges-source.h                    \
-       ges-operation.h                 \
-       ges-video-source.h \
-       ges-audio-source.h \
-       ges-video-uri-source.h                  \
-       ges-audio-uri-source.h                  \
-       ges-image-source.h              \
-       ges-multi-file-source.h         \
-       ges-transition.h                        \
-       ges-audio-transition.h          \
-       ges-video-transition.h          \
-       ges-video-test-source.h         \
-       ges-audio-test-source.h         \
-       ges-title-source.h              \
-       ges-text-overlay.h              \
-       ges-screenshot.h                        \
-       ges-formatter.h                         \
-       ges-pitivi-formatter.h                  \
-       ges-asset.h \
-       ges-uri-asset.h \
-       ges-clip-asset.h \
-       ges-track-element-asset.h \
-       ges-extractable.h \
-       ges-project.h \
-       ges-base-xml-formatter.h \
-       ges-xml-formatter.h \
-       ges-command-line-formatter.h \
-       ges-timeline-element.h \
-       ges-container.h \
-       ges-effect-asset.h \
-       ges-utils.h \
-       ges-group.h \
-       ges-version.h
-
-noinst_HEADERS = \
-       ges-internal.h \
-       ges-auto-transition.h \
-       ges-structured-interface.h \
-       ges-structure-parser.h \
-       ges-smart-video-mixer.h \
-       ges-smart-adder.h \
-       ges-timeline-tree.h \
-       gstframepositioner.h
-
-libges_@GST_API_VERSION@_la_CFLAGS = -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) \
-               $(GST_VIDEO_CFLAGS) $(GST_CONTROLLER_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \
-               $(GST_CFLAGS) $(XML_CFLAGS) $(GIO_CFLAGS) $(GST_VALIDATE_CFLAGS) \
-               -DG_LOG_DOMAIN=\"GES\" -DBUILDING_GES
-libges_@GST_API_VERSION@_la_LIBADD = $(GST_PBUTILS_LIBS) \
-               $(GST_VIDEO_LIBS) $(GST_CONTROLLER_LIBS) $(GST_PLUGINS_BASE_LIBS) \
-               $(GST_BASE_LIBS) $(GST_LIBS) $(XML_LIBS) $(GIO_LIBS) $(GST_VALIDATE_LIBS)
-libges_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) \
-               $(GST_LT_LDFLAGS) $(GIO_CFLAGS) $(GST_VALIDATE_CFLAGS)
-
-DISTCLEANFILE = $(CLEANFILES)
-
-#files built on make all/check/instal
-BUILT_SOURCES =                        \
-       $(built_header_make)    \
-       $(built_source_make)    \
-       lex.priv_ges_parse_yy.c \
-       ges-parse-lex.h
-
-include $(top_srcdir)/common/gst-glib-gen.mak
-
-if HAVE_INTROSPECTION
-BUILT_GIRSOURCES = GES-@GST_API_VERSION@.gir
-
-gir_headers_temp=$(patsubst %,$(srcdir)/%, $(libges_@GST_API_VERSION@include_HEADERS))
-gir_headers=$(subst $(srcdir)/ges-version.h,$(builddir)/ges-version.h,$(gir_headers_temp))
-gir_sources=$(patsubst %,$(srcdir)/%, $(libges_@GST_API_VERSION@_la_SOURCES))
-
-GES-@GST_API_VERSION@.gir: $(INTROSPECTION_SCANNER) libges-@GST_API_VERSION@.la
-       $(AM_V_GEN)PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" \
-               CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" CC="$(CC)" PKG_CONFIG="$(PKG_CONFIG)" DLLTOOL="$(DLLTOOL)" \
-               $(INTROSPECTION_SCANNER) -v --namespace GES \
-               --nsversion=@GST_API_VERSION@ \
-               --identifier-prefix=GES \
-               --symbol-prefix=ges \
-               --warn-all \
-               --c-include='ges/ges.h' \
-               -I$(top_srcdir) \
-               -I$(top_builddir) \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-@GST_API_VERSION@` \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-pbutils-@GST_API_VERSION@` \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-audio-@GST_API_VERSION@` \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-video-@GST_API_VERSION@` \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-tag-@GST_API_VERSION@` \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-base-@GST_API_VERSION@` \
-               --add-include-path=`$(PKG_CONFIG) --variable=girdir gio-2.0` \
-               --library=libges-@GST_API_VERSION@.la \
-               --include=Gst-@GST_API_VERSION@ \
-               --include=GstVideo-@GST_API_VERSION@ \
-               --include=GstPbutils-@GST_API_VERSION@ \
-               --include=Gio-2.0 \
-               --libtool="$(top_builddir)/libtool" \
-               --pkg gstreamer-@GST_API_VERSION@ \
-               --pkg gstreamer-pbutils-@GST_API_VERSION@ \
-               --pkg gstreamer-controller-@GST_API_VERSION@ \
-               --pkg gio-2.0 \
-               --pkg-export gst-editing-services-@GST_API_VERSION@ \
-               --add-init-section="$(INTROSPECTION_INIT)" \
-               --add-init-section="extern gboolean ges_init(void); ges_init();" \
-               --output $@ \
-               $(gir_headers) \
-               $(gir_sources)
-
-# INTROSPECTION_GIRDIR/INTROSPECTION_TYPELIBDIR aren't the right place to
-# install anything - we need to install inside our prefix.
-girdir = $(datadir)/gir-1.0
-gir_DATA = $(BUILT_GIRSOURCES)
-
-typelibsdir = $(libdir)/girepository-1.0/
-
-typelibs_DATA = $(BUILT_GIRSOURCES:.gir=.typelib)
-
-%.typelib: %.gir $(INTROSPECTION_COMPILER)
-       $(AM_V_GEN)PKG_CONFIG_PATH="$(GST_PKG_CONFIG_PATH)" \
-               $(INTROSPECTION_COMPILER) \
-               --includedir=$(srcdir) \
-               --includedir=$(srcdir)/../video \
-               --includedir=$(builddir) \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-pbutils-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-audio-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-video-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-tag-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-base-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-controller-@GST_API_VERSION@` \
-               --includedir=`$(PKG_CONFIG) --variable=girdir gio-2.0` \
-               $(INTROSPECTION_COMPILER_OPTS) $< -o $(@F)
-
-CLEANFILES += $(BUILT_GIRSOURCES) $(typelibs_DATA)
-endif
-
-%.c.gcov: .libs/libges_@GST_API_VERSION@_la-%.gcda %.c
-       $(GCOV) -b -f -o $^ > $@.out
-
-gcov: $(libges_@GST_API_VERSION@_la_SOURCES:=.gcov)
-
-lex.priv_ges_parse_yy.c ges-parse-lex.h: parse.l
-       $(AM_V_GEN)$(FLEX_PATH) --header-file=ges-parse-lex.h -Ppriv_ges_parse_yy $^
index 0bd1d33..7a4aa85 100644 (file)
 /**
  * SECTION: gesasset
  * @title: GESAsset
- * @short_description: Represents usable resources inside the GStreamer Editing Services
+ * @short_description: Represents usable resources inside the GStreamer
+ * Editing Services
  *
- * The Assets in the GStreamer Editing Services represent the resources
- * that can be used. You can create assets for any type that implements the #GESExtractable
- * interface, for example #GESClips, #GESFormatter, and #GESTrackElement do implement it.
- * This means that assets will represent for example a #GESUriClips, #GESBaseEffect etc,
- * and then you can extract objects of those types with the appropriate parameters from the asset
- * using the #ges_asset_extract method:
+ * A #GESAsset in the GStreamer Editing Services represents a resources
+ * that can be used. In particular, any class that implements the
+ * #GESExtractable interface may have some associated assets with a
+ * corresponding #GESAsset:extractable-type, from which its objects can be
+ * extracted using ges_asset_extract(). Some examples would be
+ * #GESClip, #GESFormatter and #GESTrackElement.
  *
- * |[
+ * All assets that are created within GES are stored in a cache; one per
+ * each #GESAsset:id and #GESAsset:extractable-type pair. These assets can
+ * be fetched, and initialized if they do not yet exist in the cache,
+ * using ges_asset_request().
+ *
+ * ``` c
  * GESAsset *effect_asset;
  * GESEffect *effect;
  *
  * // And now you can extract an instance of GESEffect from that asset
  * effect = GES_EFFECT (ges_asset_extract (effect_asset));
  *
- * ]|
+ * ```
+ *
+ * The advantage of using assets, rather than simply creating the object
+ * directly, is that the currently loaded resources can be listed with
+ * ges_list_assets() and displayed to an end user. For example, to show
+ * which media files have been loaded, and a standard list of effects. In
+ * fact, the GES library already creates assets for #GESTransitionClip and
+ * #GESFormatter, which you can use to list all the available transition
+ * types and supported formats.
+ *
+ * The other advantage is that #GESAsset implements #GESMetaContainer, so
+ * metadata can be set on the asset, with some subclasses automatically
+ * creating this metadata on initiation.
  *
- * In that example, the advantages of having a #GESAsset are that you can know what effects
- * you are working with and let your user know about the avalaible ones, you can add metadata
- * to the #GESAsset through the #GESMetaContainer interface and you have a model for your
- * custom effects. Note that #GESAsset management is making easier thanks to the #GESProject class.
- *
- * Each asset is represented by a pair of @extractable_type and @id (string). Actually the @extractable_type
- * is the type that implements the #GESExtractable interface, that means that for example for a #GESUriClip,
- * the type that implements the #GESExtractable interface is #GESClip.
- * The identifier represents different things depending on the @extractable_type and you should check
- * the documentation of each type to know what the ID of #GESAsset actually represents for that type. By default,
- * we only have one #GESAsset per type, and the @id is the name of the type, but this behaviour is overriden
- * to be more useful. For example, for GESTransitionClips, the ID is the vtype of the transition
- * you will extract from it (ie crossfade, box-wipe-rc etc..) For #GESEffect the ID is the
- * @bin-description property of the extracted objects (ie the gst-launch style description of the bin that
- * will be used).
- *
- * Each and every #GESAsset is cached into GES, and you can query those with the #ges_list_assets function.
- * Also the system will automatically register #GESAssets for #GESFormatters and #GESTransitionClips
- * and standard effects (actually not implemented yet) and you can simply query those calling:
+ * For example, to display information about the supported formats, you
+ * could do the following:
  * |[
  *    GList *formatter_assets, *tmp;
  *
  *
  *    // Print some infos about the formatter GESAsset
  *    for (tmp = formatter_assets; tmp; tmp = tmp->next) {
- *      g_print ("Name of the formatter: %s, file extension it produces: %s",
- *        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME),
- *        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION));
+ *      gst_print ("Name of the formatter: %s, file extension it produces: %s",
+ *        ges_meta_container_get_string (
+ *          GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME),
+ *        ges_meta_container_get_string (
+ *          GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION));
  *    }
  *
  *    g_list_free (transition_assets);
  *
  * ]|
  *
- * You can request the creation of #GESAssets using either ges_asset_request() or
- * ges_asset_request_async(). All the #GESAssets are cached and thus any asset that has already
- * been created can be requested again without overhead.
+ * ## ID
+ *
+ * Each asset is uniquely defined in the cache by its
+ * #GESAsset:extractable-type and #GESAsset:id. Depending on the
+ * #GESAsset:extractable-type, the #GESAsset:id can be used to parametrise
+ * the creation of the object upon extraction. By default, a class that
+ * implements #GESExtractable will only have a single associated asset,
+ * with an #GESAsset:id set to the type name of its objects. However, this
+ * is overwritten by some implementations, which allow a class to have
+ * multiple associated assets. For example, for #GESTransitionClip the
+ * #GESAsset:id will be a nickname of the #GESTransitionClip:vtype. You
+ * should check the documentation for each extractable type to see if they
+ * differ from the default.
+ *
+ * Moreover, each #GESAsset:extractable-type may also associate itself
+ * with a specific asset subclass. In such cases, when their asset is
+ * requested, an asset of this subclass will be returned instead.
+ *
+ * ## Managing
+ *
+ * You can use a #GESProject to easily manage the assets of a
+ * #GESTimeline.
+ *
+ * ## Proxies
+ *
+ * Some assets can (temporarily) act as the #GESAsset:proxy of another
+ * asset. When the original asset is requested from the cache, the proxy
+ * will be returned in its place. This can be useful if, say, you want
+ * to substitute a #GESUriClipAsset corresponding to a high resolution
+ * media file with the asset of a lower resolution stand in.
+ *
+ * An asset may even have several proxies, the first of which will act as
+ * its default and be returned on requests, but the others will be ordered
+ * to take its place once it is removed. You can add a proxy to an asset,
+ * or set its default, using ges_asset_set_proxy(), and you can remove
+ * them with ges_asset_unproxy().
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -124,11 +160,14 @@ struct _GESAssetPrivate
   GESAssetState state;
   GType extractable_type;
 
-  /* When an asset is proxied, instantiating it will
-   * return the asset it points to */
+  /* used internally by try_proxy to pre-set a proxy whilst an asset is
+   * still loading. It can be used later to set the proxy for the asset
+   * once it has finished loading */
   char *proxied_asset_id;
 
+  /* actual list of proxies */
   GList *proxies;
+  /* the asset whose proxies list we belong to */
   GESAsset *proxy_target;
 
   /* The error that occurred when an asset has been initialized with error */
@@ -144,8 +183,6 @@ typedef struct
   GESAsset *asset;
 } GESAssetCacheEntry;
 
-/* Also protect all the entries in the cache */
-G_LOCK_DEFINE_STATIC (asset_cache_lock);
 /* We are mapping entries by types and ID, such as:
  *
  * {
@@ -169,8 +206,10 @@ G_LOCK_DEFINE_STATIC (asset_cache_lock);
  * different extractable types.
  **/
 static GHashTable *type_entries_table = NULL;
-#define LOCK_CACHE   (G_LOCK (asset_cache_lock))
-#define UNLOCK_CACHE (G_UNLOCK (asset_cache_lock))
+/* Protect all the entries in the cache */
+static GRecMutex asset_cache_lock;
+#define LOCK_CACHE   (g_rec_mutex_lock (&asset_cache_lock))
+#define UNLOCK_CACHE (g_rec_mutex_unlock (&asset_cache_lock))
 
 static gchar *
 _check_and_update_parameters (GType * extractable_type, const gchar * id,
@@ -206,9 +245,21 @@ _check_and_update_parameters (GType * extractable_type, const gchar * id,
   return real_id;
 }
 
+/* FIXME: why are we not accepting a GError ** error argument, which we
+ * could pass to ges_asset_cache_set_loaded ()? Which would allow the
+ * error to be set for the GInitable init method below */
 static gboolean
 start_loading (GESAsset * asset)
 {
+  GInitableIface *iface;
+
+  iface = g_type_interface_peek (GES_ASSET_GET_CLASS (asset), G_TYPE_INITABLE);
+
+  if (!iface->init) {
+    GST_INFO_OBJECT (asset, "Can not start loading sync, as no ->init vmethod");
+    return FALSE;
+  }
+
   ges_asset_cache_put (gst_object_ref (asset), NULL);
   return ges_asset_cache_set_loaded (asset->priv->extractable_type,
       asset->priv->id, NULL);
@@ -218,6 +269,8 @@ static gboolean
 initable_init (GInitable * initable, GCancellable * cancellable,
     GError ** error)
 {
+  /* FIXME: Is there actually a reason to be freeing the GError that
+   * error points to? */
   g_clear_error (error);
 
   return start_loading (GES_ASSET (initable));
@@ -235,7 +288,7 @@ async_initable_init_async (GAsyncInitable * initable, gint io_priority,
 
   task = g_task_new (asset, cancellable, callback, user_data);
 
-  ges_asset_cache_put (g_object_ref (asset), task);
+  ges_asset_cache_put (gst_object_ref (asset), task);
   switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) {
     case GES_ASSET_LOADING_ERROR:
     {
@@ -257,6 +310,10 @@ async_initable_init_async (GAsyncInitable * initable, gint io_priority,
     }
     case GES_ASSET_LOADING_ASYNC:
       /* If Async....  let it go */
+      /* FIXME: how are user subclasses that implement ->start_loading
+       * to return GES_ASSET_LOADING_ASYNC meant to invoke the private
+       * method ges_asset_cache_set_loaded once they finish initializing?
+       */
       break;
   }
 }
@@ -286,6 +343,7 @@ ges_asset_start_loading_default (GESAsset * asset, GError ** error)
   return GES_ASSET_LOADING_OK;
 }
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
 static GESExtractable *
 ges_asset_extract_default (GESAsset * asset, GError ** error)
 {
@@ -293,34 +351,27 @@ ges_asset_extract_default (GESAsset * asset, GError ** error)
   GParameter *params;
   GESAssetPrivate *priv = asset->priv;
   GESExtractable *n_extractable;
-
+  gint i;
+  GValue *values;
+  const gchar **names;
 
   params = ges_extractable_type_get_parameters_from_id (priv->extractable_type,
       priv->id, &n_params);
 
-#if GLIB_CHECK_VERSION(2, 53, 1)
-  {
-    gint i;
-    GValue *values;
-    const gchar **names;
-
-    values = g_malloc0 (sizeof (GValue) * n_params);
-    names = g_malloc0 (sizeof (gchar *) * n_params);
 
-    for (i = 0; i < n_params; i++) {
-      values[i] = params[i].value;
-      names[i] = params[i].name;
-    }
+  values = g_malloc0 (sizeof (GValue) * n_params);
+  names = g_malloc0 (sizeof (gchar *) * n_params);
 
-    n_extractable =
-        GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type,
-            n_params, names, values));
-    g_free (names);
-    g_free (values);
+  for (i = 0; i < n_params; i++) {
+    values[i] = params[i].value;
+    names[i] = params[i].name;
   }
-#else
-  n_extractable = g_object_newv (priv->extractable_type, n_params, params);
-#endif
+
+  n_extractable =
+      GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type,
+          n_params, names, values));
+  g_free (names);
+  g_free (values);
 
   while (n_params--)
     g_value_unset (&params[n_params].value);
@@ -330,6 +381,8 @@ ges_asset_extract_default (GESAsset * asset, GError ** error)
   return n_extractable;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS;
+
 static gboolean
 ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id,
     GError * error)
@@ -371,6 +424,8 @@ ges_asset_set_property (GObject * object, guint property_id,
   switch (property_id) {
     case PROP_TYPE:
       asset->priv->extractable_type = g_value_get_gtype (value);
+      /* NOTE: we calling this in the setter so metadata is set on the
+       * asset upon initiation, but before it has been loaded. */
       ges_extractable_register_metas (asset->priv->extractable_type, asset);
       break;
     case PROP_ID:
@@ -379,9 +434,6 @@ ges_asset_set_property (GObject * object, guint property_id,
     case PROP_PROXY:
       ges_asset_set_proxy (asset, g_value_get_object (value));
       break;
-    case PROP_PROXY_TARGET:
-      ges_asset_set_proxy (g_value_get_object (value), asset);
-      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -392,7 +444,7 @@ ges_asset_finalize (GObject * object)
 {
   GESAssetPrivate *priv = GES_ASSET (object)->priv;
 
-  GST_DEBUG_OBJECT (object, "finalizing");
+  GST_LOG_OBJECT (object, "finalizing");
 
   if (priv->id)
     g_free (priv->id);
@@ -418,23 +470,67 @@ ges_asset_class_init (GESAssetClass * klass)
   object_class->set_property = ges_asset_set_property;
   object_class->finalize = ges_asset_finalize;
 
+  /**
+   * GESAsset:extractable-type:
+   *
+   * The #GESExtractable object type that can be extracted from the asset.
+   */
   _properties[PROP_TYPE] =
       g_param_spec_gtype ("extractable-type", "Extractable type",
       "The type of the Object that can be extracted out of the asset",
       G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
 
+  /**
+   * GESAsset:id:
+   *
+   * The ID of the asset. This should be unique amongst all assets with
+   * the same #GESAsset:extractable-type. Depending on the associated
+   * #GESExtractable implementation, this id may convey some information
+   * about the #GObject that should be extracted. Note that, as such, the
+   * ID will have an expected format, and you can not choose this value
+   * arbitrarily. By default, this will be set to the type name of the
+   * #GESAsset:extractable-type, but you should check the documentation
+   * of the extractable type to see whether they differ from the
+   * default behaviour.
+   */
   _properties[PROP_ID] =
       g_param_spec_string ("id", "Identifier",
-      "The unic identifier of the asset", NULL,
+      "The unique identifier of the asset", NULL,
       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
 
+  /**
+   * GESAsset:proxy:
+   *
+   * The default proxy for this asset, or %NULL if it has no proxy. A
+   * proxy will act as a substitute for the original asset when the
+   * original is requested (see ges_asset_request()).
+   *
+   * Setting this property will not usually remove the existing proxy, but
+   * will replace it as the default (see ges_asset_set_proxy()).
+   */
   _properties[PROP_PROXY] =
       g_param_spec_object ("proxy", "Proxy",
       "The asset default proxy.", GES_TYPE_ASSET, G_PARAM_READWRITE);
 
+  /**
+   * GESAsset:proxy-target:
+   *
+   * The asset that this asset is a proxy for, or %NULL if it is not a
+   * proxy for another asset.
+   *
+   * Note that even if this asset is acting as a proxy for another asset,
+   * but this asset is not the default #GESAsset:proxy, then @proxy-target
+   * will *still* point to this other asset. So you should check the
+   * #GESAsset:proxy property of @target-proxy before assuming it is the
+   * current default proxy for the target.
+   *
+   * Note that the #GObject::notify for this property is emitted after
+   * the #GESAsset:proxy #GObject::notify for the corresponding (if any)
+   * asset it is now the proxy of/no longer the proxy of.
+   */
   _properties[PROP_PROXY_TARGET] =
       g_param_spec_object ("proxy-target", "Proxy target",
-      "The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READWRITE);
+      "The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READABLE);
 
   g_object_class_install_properties (object_class, PROP_LAST, _properties);
 
@@ -458,24 +554,49 @@ ges_asset_init (GESAsset * self)
 
 /* Internal methods */
 
-/* Find the type that implemented the GESExtractable interface */
 static inline const gchar *
 _extractable_type_name (GType type)
 {
-  while (1) {
-    if (g_type_is_a (g_type_parent (type), GES_TYPE_EXTRACTABLE))
-      type = g_type_parent (type);
-    else
-      return g_type_name (type);
-  }
+  /* We can use `ges_asset_request (GES_TYPE_FORMATTER);` */
+  if (g_type_is_a (type, GES_TYPE_FORMATTER))
+    return g_type_name (GES_TYPE_FORMATTER);
+
+  return g_type_name (type);
+}
+
+static void
+ges_asset_cache_init_unlocked (void)
+{
+  if (type_entries_table)
+    return;
+
+  type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+      g_free, (GDestroyNotify) g_hash_table_unref);
+
+  _init_formatter_assets ();
+  _init_standard_transition_assets ();
 }
 
+
+/* WITH LOCK_CACHE */
+static GHashTable *
+_get_type_entries (void)
+{
+  if (type_entries_table)
+    return type_entries_table;
+
+  ges_asset_cache_init_unlocked ();
+
+  return type_entries_table;
+}
+
+/* WITH LOCK_CACHE */
 static inline GESAssetCacheEntry *
 _lookup_entry (GType extractable_type, const gchar * id)
 {
   GHashTable *entries_table;
 
-  entries_table = g_hash_table_lookup (type_entries_table,
+  entries_table = g_hash_table_lookup (_get_type_entries (),
       _extractable_type_name (extractable_type));
   if (entries_table)
     return g_hash_table_lookup (entries_table, id);
@@ -593,6 +714,7 @@ ges_asset_cache_set_loaded (GType extractable_type, const gchar * id,
   return TRUE;
 }
 
+/* transfer full for both @asset and @task */
 void
 ges_asset_cache_put (GESAsset * asset, GTask * task)
 {
@@ -608,24 +730,27 @@ ges_asset_cache_put (GESAsset * asset, GTask * task)
   if (!(entry = _lookup_entry (extractable_type, asset_id))) {
     GHashTable *entries_table;
 
-    entries_table = g_hash_table_lookup (type_entries_table,
+    entries_table = g_hash_table_lookup (_get_type_entries (),
         _extractable_type_name (extractable_type));
     if (entries_table == NULL) {
       entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
           _free_entries);
 
-      g_hash_table_insert (type_entries_table,
+      g_hash_table_insert (_get_type_entries (),
           g_strdup (_extractable_type_name (extractable_type)), entries_table);
     }
 
     entry = g_slice_new0 (GESAssetCacheEntry);
 
+    /* transfer asset to entry */
     entry->asset = asset;
     if (task)
       entry->results = g_list_prepend (entry->results, task);
     g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id),
         (gpointer) entry);
   } else {
+    /* give up the reference we were given */
+    gst_object_unref (asset);
     if (task) {
       GST_DEBUG ("%s already in cache, adding result %p", asset_id, task);
       entry->results = g_list_prepend (entry->results, task);
@@ -637,18 +762,20 @@ ges_asset_cache_put (GESAsset * asset, GTask * task)
 void
 ges_asset_cache_init (void)
 {
-  type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal,
-      g_free, (GDestroyNotify) g_hash_table_unref);
-
-  _init_formatter_assets ();
-  _init_standard_transition_assets ();
+  LOCK_CACHE;
+  ges_asset_cache_init_unlocked ();
+  UNLOCK_CACHE;
 }
 
 void
 ges_asset_cache_deinit (void)
 {
+  _deinit_formatter_assets ();
+
+  LOCK_CACHE;
   g_hash_table_destroy (type_entries_table);
   type_entries_table = NULL;
+  UNLOCK_CACHE;
 }
 
 gboolean
@@ -661,6 +788,8 @@ ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id,
       error);
 }
 
+/* pre-set a proxy id whilst the asset is still loading. Once the proxy
+ * is loaded, call ges_asset_finish_proxy (proxy) */
 gboolean
 ges_asset_try_proxy (GESAsset * asset, const gchar * new_id)
 {
@@ -685,6 +814,9 @@ ges_asset_try_proxy (GESAsset * asset, const gchar * new_id)
   asset->priv->state = ASSET_PROXIED;
   asset->priv->proxied_asset_id = g_strdup (new_id);
 
+  /* FIXME: inform_proxy is not used consistently. For example, it is
+   * not called in set_proxy. However, it is still used by GESUriAsset.
+   * We should find some other method */
   class = GES_ASSET_GET_CLASS (asset);
   if (class->inform_proxy)
     GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id);
@@ -701,79 +833,131 @@ _lookup_proxied_asset (const gchar * id, GESAssetCacheEntry * entry,
   return !g_strcmp0 (asset_id, entry->asset->priv->proxied_asset_id);
 }
 
+/* find the assets that called try_proxy for the asset id of @proxy
+ * and set @proxy as their proxy */
+gboolean
+ges_asset_finish_proxy (GESAsset * proxy)
+{
+  GESAsset *proxied_asset;
+  GHashTable *entries_table;
+  GESAssetCacheEntry *entry;
+
+  LOCK_CACHE;
+  entries_table = g_hash_table_lookup (_get_type_entries (),
+      _extractable_type_name (proxy->priv->extractable_type));
+  entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset,
+      (gpointer) ges_asset_get_id (proxy));
+
+  if (!entry) {
+    UNLOCK_CACHE;
+    GST_DEBUG_OBJECT (proxy, "Not proxying any asset %s", proxy->priv->id);
+    return FALSE;
+  }
+
+  proxied_asset = entry->asset;
+  UNLOCK_CACHE;
+
+  /* If the asset with the matching ->proxied_asset_id is already proxied
+   * by another asset, we actually want @proxy to proxy this instead */
+  while (proxied_asset->priv->proxies)
+    proxied_asset = proxied_asset->priv->proxies->data;
+
+  /* unless it is ourselves. I.e. it is already proxied by us */
+  if (proxied_asset == proxy)
+    return FALSE;
+
+  GST_INFO_OBJECT (proxied_asset,
+      "%s Making sure the proxy chain is fully set.",
+      ges_asset_get_id (entry->asset));
+  if (g_strcmp0 (proxied_asset->priv->proxied_asset_id, proxy->priv->id) ||
+      g_strcmp0 (proxied_asset->priv->id, proxy->priv->proxied_asset_id))
+    ges_asset_finish_proxy (proxied_asset);
+  return ges_asset_set_proxy (proxied_asset, proxy);
+}
+
+static gboolean
+_contained_in_proxy_tree (GESAsset * node, GESAsset * search)
+{
+  GList *tmp;
+  if (node == search)
+    return TRUE;
+  for (tmp = node->priv->proxies; tmp; tmp = tmp->next) {
+    if (_contained_in_proxy_tree (tmp->data, search))
+      return TRUE;
+  }
+  return FALSE;
+}
+
 /**
  * ges_asset_set_proxy:
- * @asset: The #GESAsset to set proxy on
- * @proxy: (allow-none): The #GESAsset that should be used as default proxy for @asset or
- * %NULL if you want to use the currently set proxy. Note that an asset can proxy one and only
- * one other asset.
+ * @asset: The #GESAsset to proxy
+ * @proxy: (allow-none): A new default proxy for @asset
+ *
+ * Sets the #GESAsset:proxy for the asset.
  *
- * A proxying asset is an asset that can substitue the real @asset. For example if you
- * have a full HD #GESUriClipAsset you might want to set a lower resolution (HD version
- * of the same file) as proxy. Note that when an asset is proxied, calling
- * #ges_asset_request will actually return the proxy asset.
+ * If @proxy is among the existing proxies of the asset (see
+ * ges_asset_list_proxies()) it will be moved to become the default
+ * proxy. Otherwise, if @proxy is not %NULL, it will be added to the list
+ * of proxies, as the new default. The previous default proxy will become
+ * 'next in line' for if the new one is removed, and so on. As such, this
+ * will **not** actually remove the previous default proxy (use
+ * ges_asset_unproxy() for that).
  *
- * Returns: %TRUE if @proxy has been set on @asset, %FALSE otherwise.
+ * Note that an asset can only act as a proxy for one other asset.
+ *
+ * As a special case, if @proxy is %NULL, then this method will actually
+ * remove **all** proxies from the asset.
+ *
+ * Returns: %TRUE if @proxy was successfully set as the default for
+ * @asset.
  */
 gboolean
 ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
 {
-  g_return_val_if_fail (asset == NULL || GES_IS_ASSET (asset), FALSE);
+  GESAsset *current_target;
+  g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
   g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE);
   g_return_val_if_fail (asset != proxy, FALSE);
 
   if (!proxy) {
+    GList *tmp, *proxies;
     if (asset->priv->error) {
       GST_ERROR_OBJECT (asset,
-          "Proxy was loaded with error (%s), it should not be 'unproxied'",
+          "Asset was loaded with error (%s), it should not be 'unproxied'",
           asset->priv->error->message);
 
       return FALSE;
     }
 
-    if (asset->priv->proxies) {
-      GESAsset *old_proxy = GES_ASSET (asset->priv->proxies->data);
+    GST_DEBUG_OBJECT (asset, "Removing all proxies");
+    proxies = asset->priv->proxies;
+    asset->priv->proxies = NULL;
 
-      old_proxy->priv->proxy_target = NULL;
-      g_object_notify_by_pspec (G_OBJECT (old_proxy),
-          _properties[PROP_PROXY_TARGET]);
+    for (tmp = proxies; tmp; tmp = tmp->next) {
+      GESAsset *proxy = tmp->data;
+      proxy->priv->proxy_target = NULL;
     }
-
-    GST_DEBUG_OBJECT (asset, "%s not proxied anymore", asset->priv->id);
     asset->priv->state = ASSET_INITIALIZED;
+
     g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
+    for (tmp = proxies; tmp; tmp = tmp->next)
+      g_object_notify_by_pspec (G_OBJECT (tmp->data),
+          _properties[PROP_PROXY_TARGET]);
 
+    g_list_free (proxies);
     return TRUE;
   }
+  current_target = proxy->priv->proxy_target;
 
-  if (asset == NULL) {
-    GHashTable *entries_table;
-    GESAssetCacheEntry *entry;
-
-    entries_table = g_hash_table_lookup (type_entries_table,
-        _extractable_type_name (proxy->priv->extractable_type));
-    entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset,
-        (gpointer) ges_asset_get_id (proxy));
-
-    if (!entry) {
-      GST_DEBUG_OBJECT (asset, "Not proxying any asset");
-      return FALSE;
-    }
-
-    asset = entry->asset;
-    while (asset->priv->proxies)
-      asset = asset->priv->proxies->data;
-  }
-
-  if (proxy->priv->proxy_target) {
+  if (current_target && current_target != asset) {
     GST_ERROR_OBJECT (asset,
-        "Trying to use %s as a proxy, but it is already proxying %s",
-        proxy->priv->id, proxy->priv->proxy_target->priv->id);
+        "Trying to use '%s' as a proxy, but it is already proxying '%s'",
+        proxy->priv->id, current_target->priv->id);
 
     return FALSE;
   }
 
-  if (g_list_find (proxy->priv->proxies, asset)) {
+  if (_contained_in_proxy_tree (proxy, asset)) {
     GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!");
 
     return FALSE;
@@ -782,58 +966,89 @@ ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
   if (g_list_find (asset->priv->proxies, proxy)) {
     GST_INFO_OBJECT (asset,
         "%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy);
-    GES_ASSET (asset->priv->proxies->data)->priv->proxy_target = NULL;
     asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
   }
 
   GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id);
   asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy);
-  proxy->priv->proxy_target = asset;
-  g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
 
+  proxy->priv->proxy_target = asset;
   asset->priv->state = ASSET_PROXIED;
+
   g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
+  if (current_target != asset)
+    g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
+
+  /* FIXME: ->inform_proxy is not called. We should figure out what the
+   * purpose of ->inform_proxy should be generically. Currently, it is
+   * only called in ges_asset_try_proxy! */
 
   return TRUE;
 }
 
 /**
  * ges_asset_unproxy:
- * @asset: The #GESAsset to stop proxying with @proxy
- * @proxy: The #GESAsset to stop considering as a proxy for @asset
+ * @asset: The #GESAsset to no longer proxy with @proxy
+ * @proxy: An existing proxy of @asset
  *
- * Removes @proxy from the list of known proxies for @asset.
- * If @proxy was the current proxy for @asset, stop using it.
+ * Removes the proxy from the available list of proxies for the asset. If
+ * the given proxy is the default proxy of the list, then the next proxy
+ * in the available list (see ges_asset_list_proxies()) will become the
+ * default. If there are no other proxies, then the asset will no longer
+ * have a default #GESAsset:proxy.
  *
- * Returns: %TRUE if @proxy was a known proxy for @asset, %FALSE otherwise.
+ * Returns: %TRUE if @proxy was successfully removed from @asset's proxy
+ * list.
  */
 gboolean
 ges_asset_unproxy (GESAsset * asset, GESAsset * proxy)
 {
+  gboolean removing_default;
+  gboolean last_proxy;
   g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
   g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE);
   g_return_val_if_fail (asset != proxy, FALSE);
 
+  /* also tests if the list is NULL */
   if (!g_list_find (asset->priv->proxies, proxy)) {
-    GST_INFO_OBJECT (asset, "%s is not a proxy.", proxy->priv->id);
+    GST_INFO_OBJECT (asset, "'%s' is not a proxy.", proxy->priv->id);
+
+    return FALSE;
+  }
 
+  last_proxy = (asset->priv->proxies->next == NULL);
+  if (last_proxy && asset->priv->error) {
+    GST_ERROR_OBJECT (asset,
+        "Asset was loaded with error (%s), its last proxy '%s' should "
+        "not be removed", asset->priv->error->message, proxy->priv->id);
     return FALSE;
   }
 
-  if (asset->priv->proxies->data == proxy)
-    ges_asset_set_proxy (asset, NULL);
+  removing_default = (asset->priv->proxies->data == proxy);
 
   asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
 
+  if (last_proxy)
+    asset->priv->state = ASSET_INITIALIZED;
+  proxy->priv->proxy_target = NULL;
+
+  if (removing_default)
+    g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
+  g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
+
   return TRUE;
 }
 
 /**
  * ges_asset_list_proxies:
- * @asset: The #GESAsset to get proxies from
+ * @asset: A #GESAsset
  *
- * Returns: (element-type GESAsset) (transfer none): The list of proxies @asset has. Note that the default asset to be
- * used is always the first in that list.
+ * Get all the proxies that the asset has. The first item of the list will
+ * be the default #GESAsset:proxy. The second will be the proxy that is
+ * 'next in line' to be default, and so on.
+ *
+ * Returns: (element-type GESAsset) (transfer none): The list of proxies
+ * that @asset has.
  */
 GList *
 ges_asset_list_proxies (GESAsset * asset)
@@ -845,9 +1060,11 @@ ges_asset_list_proxies (GESAsset * asset)
 
 /**
  * ges_asset_get_proxy:
- * @asset: The #GESAsset to get currenlty used proxy
+ * @asset: A #GESAsset
+ *
+ * Gets the default #GESAsset:proxy of the asset.
  *
- * Returns: (transfer none) (nullable): The proxy in use for @asset
+ * Returns: (transfer none) (nullable): The default proxy of @asset.
  */
 GESAsset *
 ges_asset_get_proxy (GESAsset * asset)
@@ -863,9 +1080,15 @@ ges_asset_get_proxy (GESAsset * asset)
 
 /**
  * ges_asset_get_proxy_target:
- * @proxy: The #GESAsset from which to get the the asset it proxies.
+ * @proxy: A #GESAsset
  *
- * Returns: (transfer none) (nullable): The #GESAsset that is proxied by @proxy
+ * Gets the #GESAsset:proxy-target of the asset.
+ *
+ * Note that the proxy target may have loaded with an error, so you should
+ * call ges_asset_get_error() on the returned target.
+ *
+ * Returns: (transfer none) (nullable): The asset that @proxy is a proxy
+ * of.
  */
 GESAsset *
 ges_asset_get_proxy_target (GESAsset * proxy)
@@ -905,7 +1128,7 @@ ges_asset_set_id (GESAsset * asset, const gchar * id)
   }
 
   LOCK_CACHE;
-  entries = g_hash_table_lookup (type_entries_table,
+  entries = g_hash_table_lookup (_get_type_entries (),
       _extractable_type_name (asset->priv->extractable_type));
 
   g_return_if_fail (g_hash_table_lookup_extended (entries, priv->id, &orig_id,
@@ -934,6 +1157,7 @@ _ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type,
   asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id,
       "extractable-type", extractable_type, NULL);
 
+  /* transfer ownership to the cache */
   ges_asset_cache_put (asset, NULL);
   ges_asset_cache_set_loaded (extractable_type, wrong_id, error);
 
@@ -950,9 +1174,9 @@ _ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type,
  * ges_asset_get_extractable_type:
  * @self: The #GESAsset
  *
- * Gets the type of object that can be extracted from @self
+ * Gets the #GESAsset:extractable-type of the asset.
  *
- * Returns: the type of object that can be extracted from @self
+ * Returns: The extractable type of @self.
  */
 GType
 ges_asset_get_extractable_type (GESAsset * self)
@@ -964,18 +1188,46 @@ ges_asset_get_extractable_type (GESAsset * self)
 
 /**
  * ges_asset_request:
- * @extractable_type: The #GType of the object that can be extracted from the new asset.
- * @id: (allow-none): The Identifier or %NULL
- * @error: (allow-none): An error to be set in case something wrong happens or %NULL
+ * @extractable_type: The #GESAsset:extractable-type of the asset
+ * @id: (allow-none): The #GESAsset:id of the asset
+ * @error: (allow-none): An error to be set if the requested asset has
+ * loaded with an error, or %NULL to ignore
+ *
+ * Returns an asset with the given properties. If such an asset already
+ * exists in the cache (it has been previously created in GES), then a
+ * reference to the existing asset is returned. Otherwise, a newly created
+ * asset is returned, and also added to the cache.
+ *
+ * If the requested asset has been loaded with an error, then @error is
+ * set, if given, and %NULL will be returned instead.
+ *
+ * Note that the given @id may not be exactly the #GESAsset:id that is
+ * set on the returned asset. For instance, it may be adjusted into a
+ * standard format. Or, if a #GESExtractable type does not have its
+ * extraction parametrised, as is the case by default, then the given @id
+ * may be ignored entirely and the #GESAsset:id set to some standard, in
+ * which case a %NULL @id can be given.
  *
- * Create a #GESAsset in the most simple cases, you should look at the @extractable_type
- * documentation to see if that constructor can be called for this particular type
+ * Similarly, the given @extractable_type may not be exactly the
+ * #GESAsset:extractable-type that is set on the returned asset. Instead,
+ * the actual extractable type may correspond to a subclass of the given
+ * @extractable_type, depending on the given @id.
  *
- * As it is recommanded not to instanciate assets for GESUriClip synchronously,
- * it will not work with this method, but you can instead use the specific
- * #ges_uri_clip_asset_request_sync method if you really want to.
+ * Moreover, depending on the given @extractable_type, the returned asset
+ * may belong to a subclass of #GESAsset.
  *
- * Returns: (transfer full) (allow-none): A reference to the wanted #GESAsset or %NULL
+ * Finally, if the requested asset has a #GESAsset:proxy, then the proxy
+ * that is found at the end of the chain of proxies is returned (a proxy's
+ * proxy will take its place, and so on, unless it has no proxy).
+ *
+ * Some asset subclasses only support asynchronous construction of its
+ * assets, such as #GESUriClip. For such assets this method will fail, and
+ * you should use ges_asset_request_async() instead. In the case of
+ * #GESUriClip, you can use ges_uri_clip_asset_request_sync() if you only
+ * want to wait for the request to finish.
+ *
+ * Returns: (transfer full) (allow-none): A reference to the requested
+ * asset, or %NULL if an error occurred.
  */
 GESAsset *
 ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
@@ -983,7 +1235,8 @@ ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
   gchar *real_id;
 
   GError *lerr = NULL;
-  GESAsset *asset = NULL;
+  GESAsset *asset = NULL, *proxy;
+  gboolean proxied = TRUE;
 
   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
   g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL);
@@ -1000,45 +1253,51 @@ ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
   if (lerr)
     g_error_free (lerr);
 
+  /* asset owned by cache */
   asset = ges_asset_cache_lookup (extractable_type, real_id);
   if (asset) {
-    while (TRUE) {
+    while (proxied) {
+      proxied = FALSE;
       switch (asset->priv->state) {
         case ASSET_INITIALIZED:
-          gst_object_ref (asset);
-          goto done;
+          break;
         case ASSET_INITIALIZING:
           asset = NULL;
-          goto done;
+          break;
         case ASSET_PROXIED:
-          if (asset->priv->proxies == NULL) {
+          proxy = ges_asset_get_proxy (asset);
+          if (proxy == NULL) {
             GST_ERROR ("Proxied against an asset we do not"
                 " have in cache, something massively screwed");
-
-            goto done;
+            asset = NULL;
+          } else {
+            proxied = TRUE;
+            do {
+              asset = proxy;
+            } while ((proxy = ges_asset_get_proxy (asset)));
           }
-
-          asset = asset->priv->proxies->data;
-          while (ges_asset_get_proxy (asset))
-            asset = ges_asset_get_proxy (asset);
-
           break;
         case ASSET_NEEDS_RELOAD:
           GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
-          start_loading (asset);
-
-          goto done;
+          if (!start_loading (asset)) {
+            GST_ERROR ("Failed to reload the asset for id %s", id);
+            asset = NULL;
+          }
+          break;
         case ASSET_INITIALIZED_WITH_ERROR:
           GST_WARNING_OBJECT (asset, "Initialized with error, not returning");
           if (asset->priv->error && error)
             *error = g_error_copy (asset->priv->error);
           asset = NULL;
-          goto done;
+          break;
         default:
           GST_WARNING ("Case %i not handle, returning", asset->priv->state);
-          goto done;
+          asset = NULL;
+          break;
       }
     }
+    if (asset)
+      gst_object_ref (asset);
   } else {
     GObjectClass *klass;
     GInitableIface *iface;
@@ -1048,6 +1307,8 @@ ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
     iface = g_type_interface_peek (klass, G_TYPE_INITABLE);
 
     if (iface->init) {
+      /* FIXME: allow the error to be set, which GInitable is designed
+       * for! */
       asset = g_initable_new (asset_type,
           NULL, NULL, "id", real_id, "extractable-type",
           extractable_type, NULL);
@@ -1058,7 +1319,6 @@ ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
     g_type_class_unref (klass);
   }
 
-done:
   if (real_id)
     g_free (real_id);
 
@@ -1068,33 +1328,33 @@ done:
 
 /**
  * ges_asset_request_async:
- * @extractable_type: The #GType of the object that can be extracted from the
- *    new asset. The class must implement the #GESExtractable interface.
- * @id: The Identifier of the asset we want to create. This identifier depends of the extractable,
- * type you want. By default it is the name of the class itself (or %NULL), but for example for a
- * GESEffect, it will be the pipeline description, for a GESUriClip it
- * will be the name of the file, etc... You should refer to the documentation of the #GESExtractable
- * type you want to create a #GESAsset for.
- * @cancellable: (allow-none): optional %GCancellable object, %NULL to ignore.
- * @callback: a #GAsyncReadyCallback to call when the initialization is finished,
- * Note that the @source of the callback will be the #GESAsset, but you need to
- * make sure that the asset is properly loaded using the #ges_asset_request_finish
- * method. This asset can not be used as is.
- * @user_data: The user data to pass when @callback is called
- *
- * The @callback will be called from a running #GMainLoop which is iterating a #GMainContext.
- * Note that, users should ensure the #GMainContext, since this method will notify
- * @callback from the thread which was associated with a thread default
- * #GMainContext at calling ges_init().
- * For example, if a user wants non-default #GMainContext to be associated
- * with @callback, ges_init() must be called after g_main_context_push_thread_default ()
- * with custom #GMainContext.
- *
- * Request a new #GESAsset asyncronously, @callback will be called when the materail is
- * ready to be used or if an error occured.
- *
- * Example of request of a GESAsset async:
- * |[
+ * @extractable_type: The #GESAsset:extractable-type of the asset
+ * @id: (allow-none): The #GESAsset:id of the asset
+ * @cancellable: (allow-none): An object to allow cancellation of the
+ * asset request, or %NULL to ignore
+ * @callback: A function to call when the initialization is finished
+ * @user_data: Data to be passed to @callback
+ *
+ * Requests an asset with the given properties asynchronously (see
+ * ges_asset_request()). When the asset has been initialized or fetched
+ * from the cache, the given callback function will be called. The
+ * asset can then be retrieved in the callback using the
+ * ges_asset_request_finish() method on the given #GAsyncResult.
+ *
+ * Note that the source object passed to the callback will be the
+ * #GESAsset corresponding to the request, but it may not have loaded
+ * correctly and therefore can not be used as is. Instead,
+ * ges_asset_request_finish() should be used to fetch a usable asset, or
+ * indicate that an error occurred in the asset's creation.
+ *
+ * Note that the callback will be called in the #GMainLoop running under
+ * the same #GMainContext that ges_init() was called in. So, if you wish
+ * the callback to be invoked outside the default #GMainContext, you can
+ * call g_main_context_push_thread_default() in a new thread before
+ * calling ges_init().
+ *
+ * Example of an asynchronous asset request:
+ * ``` c
  * // The request callback
  * static void
  * asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data)
@@ -1104,20 +1364,20 @@ done:
  *
  *   asset = ges_asset_request_finish (res, &error);
  *   if (asset) {
- *    g_print ("The file: %s is usable as a FileSource",
+ *    gst_print ("The file: %s is usable as a GESUriClip",
  *        ges_asset_get_id (asset));
  *   } else {
- *    g_print ("The file: %s is *not* usable as a FileSource because: %s",
+ *    gst_print ("The file: %s is *not* usable as a GESUriClip because: %s",
  *        ges_asset_get_id (source), error->message);
  *   }
  *
- *   gst_object_unref (mfs);
+ *   gst_object_unref (asset);
  * }
  *
  * // The request:
  * ges_asset_request_async (GES_TYPE_URI_CLIP, some_uri, NULL,
  *    (GAsyncReadyCallback) asset_loaded_cb, user_data);
- * ]|
+ * ```
  */
 void
 ges_asset_request_async (GType extractable_type,
@@ -1212,21 +1472,23 @@ done:
 
 /**
  * ges_asset_needs_reload
- * @extractable_type: The #GType of the object that can be extracted from the
- *  asset to be reloaded.
- * @id: The identifier of the asset to mark as needing reload
+ * @extractable_type: The #GESAsset:extractable-type of the asset that
+ * needs reloading
+ * @id: (allow-none): The #GESAsset:id of the asset asset that needs
+ * reloading
  *
- * Sets an asset from the internal cache as needing reload. An asset needs reload
- * in the case where, for example, we were missing a GstPlugin to use it and that
- * plugin has been installed, or, that particular asset content as changed
- * meanwhile (in the case of the usage of proxies).
+ * Indicate that an existing #GESAsset in the cache should be reloaded
+ * upon the next request. This can be used when some condition has
+ * changed, which may require that an existing asset should be updated.
+ * For example, if an external resource has changed or now become
+ * available.
  *
- * Once an asset has been set as "needs reload", requesting that asset again
- * will lead to it being re discovered, and reloaded as if it was not in the
- * cache before.
+ * Note, the asset is not immediately changed, but will only actually
+ * reload on the next call to ges_asset_request() or
+ * ges_asset_request_async().
  *
- * Returns: %TRUE if the asset was in the cache and could be set as needing reload,
- * %FALSE otherwise.
+ * Returns: %TRUE if the specified asset exists in the cache and could be
+ * marked for reloading.
  */
 gboolean
 ges_asset_needs_reload (GType extractable_type, const gchar * id)
@@ -1235,6 +1497,9 @@ ges_asset_needs_reload (GType extractable_type, const gchar * id)
   GESAsset *asset;
   GError *error = NULL;
 
+  g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
+      FALSE);
+
   real_id = _check_and_update_parameters (&extractable_type, id, &error);
   if (error) {
     _ensure_asset_for_wrong_id (id, extractable_type, error);
@@ -1262,11 +1527,11 @@ ges_asset_needs_reload (GType extractable_type, const gchar * id)
 
 /**
  * ges_asset_get_id:
- * @self: The #GESAsset to get ID from
+ * @self: A #GESAsset
  *
- * Gets the ID of a #GESAsset
+ * Gets the #GESAsset:id of the asset.
  *
- * Returns: The ID of @self
+ * Returns: The ID of @self.
  */
 const gchar *
 ges_asset_get_id (GESAsset * self)
@@ -1278,15 +1543,16 @@ ges_asset_get_id (GESAsset * self)
 
 /**
  * ges_asset_extract:
- * @self: The #GESAsset to get extract an object from
- * @error: (allow-none): An error to be set in case something wrong happens or %NULL
+ * @self: The #GESAsset to extract an object from
+ * @error: (allow-none): An error to be set in case something goes wrong,
+ * or %NULL to ignore
  *
- * Extracts a new #GObject from @asset. The type of the object is
- * defined by the extractable-type of @asset, you can check what
- * type will be extracted from @asset using
- * #ges_asset_get_extractable_type
+ * Extracts a new #GESAsset:extractable-type object from the asset. The
+ * #GESAsset:id of the asset may determine the properties and state of the
+ * newly created object.
  *
- * Returns: (transfer floating) (allow-none): A newly created #GESExtractable
+ * Returns: (transfer floating): A newly created object, or %NULL if an
+ * error occurred.
  */
 GESExtractable *
 ges_asset_extract (GESAsset * self, GError ** error)
@@ -1311,13 +1577,15 @@ ges_asset_extract (GESAsset * self, GError ** error)
 
 /**
  * ges_asset_request_finish:
- * @res: The #GAsyncResult from which to get the newly created #GESAsset
+ * @res: The task result to fetch the asset from
  * @error: (out) (allow-none) (transfer full): An error to be set in case
- * something wrong happens or %NULL
+ * something goes wrong, or %NULL to ignore
  *
- * Finalize the request of an async #GESAsset
+ * Fetches an asset requested by ges_asset_request_async(), which
+ * finalises the request.
  *
- * Returns: (transfer full)(allow-none): The #GESAsset previously requested
+ * Returns: (transfer full): The requested asset, or %NULL if an error
+ * occurred.
  */
 GESAsset *
 ges_asset_request_finish (GAsyncResult * res, GError ** error)
@@ -1340,14 +1608,19 @@ ges_asset_request_finish (GAsyncResult * res, GError ** error)
 
 /**
  * ges_list_assets:
- * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE  will list
- * all assets
+ * @filter: The type of object that can be extracted from the asset
  *
- * List all @asset filtering per filter as defined by @filter.
- * It copies the asset and thus will not be updated in time.
+ * List all the assets in the current cache whose
+ * #GESAsset:extractable-type are of the given type (including
+ * subclasses).
  *
- * Returns: (transfer container) (element-type GESAsset): The list of
- * #GESAsset the object contains
+ * Note that, since only a #GESExtractable can be extracted from an asset,
+ * using `GES_TYPE_EXTRACTABLE` as @filter will return all the assets in
+ * the current cache.
+ *
+ * Returns: (transfer container) (element-type GESAsset): A list of all
+ * #GESAsset-s currently in the cache whose #GESAsset:extractable-type is
+ * of the @filter type.
  */
 GList *
 ges_list_assets (GType filter)
@@ -1360,7 +1633,7 @@ ges_list_assets (GType filter)
   g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL);
 
   LOCK_CACHE;
-  g_hash_table_iter_init (&types_iter, type_entries_table);
+  g_hash_table_iter_init (&types_iter, _get_type_entries ());
   while (g_hash_table_iter_next (&types_iter, &typename, &assets)) {
     if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE)
       continue;
@@ -1380,10 +1653,12 @@ ges_list_assets (GType filter)
 
 /**
  * ges_asset_get_error:
- * @self: The asset to retrieve the error from
+ * @self: A #GESAsset
+ *
+ * Retrieve the error that was set on the asset when it was loaded.
  *
- * Returns: (transfer none) (nullable): The #GError of the asset or %NULL if
- * the asset was loaded without issue
+ * Returns: (transfer none) (nullable): The error set on @asset, or
+ * %NULL if no error occurred when @asset was loaded.
  *
  * Since: 1.8
  */
index 0fe83e3..b2a23c2 100644 (file)
@@ -19,8 +19,7 @@
  * Boston, MA 02111-1307, USA.
  */
 
-#ifndef _GES_ASSET_
-#define _GES_ASSET_
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-extractable.h>
 
 G_BEGIN_DECLS
 #define GES_TYPE_ASSET ges_asset_get_type()
-#define GES_ASSET(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_ASSET, GESAsset))
-#define GES_ASSET_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_ASSET, GESAssetClass))
-#define GES_IS_ASSET(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_ASSET))
-#define GES_IS_ASSET_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_ASSET))
-#define GES_ASSET_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_ASSET, GESAssetClass))
-
+GES_DECLARE_TYPE(Asset, asset, ASSET);
+
+/**
+ * GESAssetLoadingReturn:
+ * @GES_ASSET_LOADING_ERROR: Indicates that an error occurred
+ * @GES_ASSET_LOADING_ASYNC: Indicates that the loading is being performed
+ * asynchronously
+ * @GES_ASSET_LOADING_OK: Indicates that the loading is complete, without
+ * error
+ */
 typedef enum
 {
   GES_ASSET_LOADING_ERROR,
@@ -49,11 +47,6 @@ typedef enum
   GES_ASSET_LOADING_OK
 } GESAssetLoadingReturn;
 
-typedef struct _GESAssetPrivate GESAssetPrivate;
-
-GES_API
-GType ges_asset_get_type (void);
-
 struct _GESAsset
 {
   GObject parent;
@@ -65,6 +58,27 @@ struct _GESAsset
   gpointer _ges_reserved[GES_PADDING];
 };
 
+/**
+ * GESAssetClass:
+ * @start_loading: A method to be called when an asset is being requested
+ * asynchronously. This will be after the properties of the asset have
+ * been set, so it is tasked with (re)loading the 'state' of the asset.
+ * The return value should indicated whether the loading is complete, is
+ * carrying on asynchronously, or an error occurred. The default
+ * implementation will simply return that loading is already complete (the
+ * asset is already in a usable state after the properties have been set).
+ * @extract: A method that returns a new object of the asset's
+ * #GESAsset:extractable-type, or %NULL if an error occurs. The default
+ * implementation will fetch the properties of the #GESExtractable from
+ * its get_parameters_from_id() class method and set them on a new
+ * #GESAsset:extractable-type #GObject, which is returned.
+ * @request_id_update: A method called by a #GESProject when an asset has
+ * failed to load. @error is the error given by
+ * ges_asset_request_finish (). Returns: %TRUE if a new id for @self was
+ * passed to @proposed_new_id.
+ * @proxied: Deprecated: 1.18: This vmethod is no longer called.
+ */
+/* FIXME: add documentation for inform_proxy when it is used properly */
 struct _GESAssetClass
 {
   GObjectClass parent;
@@ -130,4 +144,3 @@ gboolean ges_asset_needs_reload      (GType extractable_type,
                                                                          const gchar * id);
 
 G_END_DECLS
-#endif /* _GES_ASSET */
index 8997107..1f1ce75 100644 (file)
  * You can use the following children properties through the
  * #ges_track_element_set_child_property and alike set of methods:
  *
- * <informaltable frame="none">
- * <tgroup cols="3">
- * <colspec colname="properties_type" colwidth="150px"/>
- * <colspec colname="properties_name" colwidth="200px"/>
- * <colspec colname="properties_flags" colwidth="400px"/>
- * <tbody>
- * <row>
- *  <entry role="property_type"><link linkend="gdouble"><type>double</type></link></entry>
- *  <entry role="property_name"><link linkend="GESAudioSource--volume">volume</link></entry>
- *  <entry>volume factor, 1.0=100%.</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gboolean"><type>gboolean</type></link></entry>
- *  <entry role="property_name"><link linkend="GESAudioSource--mute">mute</link></entry>
- *  <entry>mute channel.</entry>
- * </row>
- * </tbody>
- * </tgroup>
- * </informaltable>
+ * - #gdouble `volume`: volume factor, 1.0=100%.
+ * - #gboolean `mute`: mute channel.
+ *
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -141,21 +125,24 @@ ges_audio_source_create_element (GESTrackElement * trksrc)
   GstElement *volume, *vbin;
   GstElement *topbin;
   GstElement *sub_element;
-  GESAudioSourceClass *source_class = GES_AUDIO_SOURCE_GET_CLASS (trksrc);
+  GPtrArray *elements;
+  GESSourceClass *source_class = GES_SOURCE_GET_CLASS (trksrc);
   const gchar *props[] = { "volume", "mute", NULL };
   GESAudioSource *self = GES_AUDIO_SOURCE (trksrc);
 
-  if (!source_class->create_source)
-    return NULL;
+  g_assert (source_class->create_source);
 
-  sub_element = source_class->create_source (trksrc);
+  sub_element = source_class->create_source (GES_SOURCE (trksrc));
 
   GST_DEBUG_OBJECT (trksrc, "Creating a bin sub_element ! volume");
   vbin =
       gst_parse_bin_from_description
       ("audioconvert ! audioresample ! volume name=v ! capsfilter name=audio-track-caps-filter",
       TRUE, NULL);
-  topbin = ges_source_create_topbin ("audiosrcbin", sub_element, vbin, NULL);
+  elements = g_ptr_array_new ();
+  g_ptr_array_add (elements, vbin);
+  topbin = ges_source_create_topbin (GES_SOURCE (trksrc), "audiosrcbin",
+      sub_element, elements);
   volume = gst_bin_get_by_name (GST_BIN (vbin), "v");
   self->priv->capsfilter = gst_bin_get_by_name (GST_BIN (vbin),
       "audio-track-caps-filter");
@@ -189,12 +176,11 @@ ges_audio_source_class_init (GESAudioSourceClass * klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   GESTrackElementClass *track_class = GES_TRACK_ELEMENT_CLASS (klass);
-  GESAudioSourceClass *audio_source_class = GES_AUDIO_SOURCE_CLASS (klass);
 
   gobject_class->dispose = ges_audio_source_dispose;
   track_class->nleobject_factorytype = "nlesource";
   track_class->create_element = ges_audio_source_create_element;
-  audio_source_class->create_source = NULL;
+  track_class->ABI.abi.default_track_type = GES_TRACK_TYPE_AUDIO;
 }
 
 static void
index a040cc1..b8a23ac 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_AUDIO_SOURCE
-#define _GES_AUDIO_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_AUDIO_SOURCE ges_audio_source_get_type()
-
-#define GES_AUDIO_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUDIO_SOURCE, GESAudioSource))
-
-#define GES_AUDIO_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUDIO_SOURCE, GESAudioSourceClass))
-
-#define GES_IS_AUDIO_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUDIO_SOURCE))
-
-#define GES_IS_AUDIO_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUDIO_SOURCE))
-
-#define GES_AUDIO_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUDIO_SOURCE, GESAudioSourceClass))
-
-typedef struct _GESAudioSourcePrivate GESAudioSourcePrivate;
+GES_DECLARE_TYPE(AudioSource, audio_source, AUDIO_SOURCE);
 
 /**
  * GESAudioSource:
@@ -66,16 +49,21 @@ struct _GESAudioSource {
 
 /**
  * GESAudioSourceClass:
- * @create_source: method to return the GstElement to put in the source topbin.
- * Other elements will be queued, like a volume.
- * In the case of a AudioUriSource, for example, the subclass will return a decodebin,
- * and we will append a volume.
  */
 struct _GESAudioSourceClass {
   /*< private >*/
   GESSourceClass parent_class;
 
   /*< public >*/
+  /**
+   * GESAudioSource::create_element:
+   * @object: The #GESTrackElement
+   *
+   * Returns: (transfer floating): the #GstElement that the underlying nleobject
+   * controls.
+   *
+   * Deprecated: 1.20: Use #GESSourceClass::create_element instead.
+   */
   GstElement*  (*create_source)           (GESTrackElement * object);
 
   /*< private >*/
@@ -83,9 +71,4 @@ struct _GESAudioSourceClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_audio_source_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_AUDIO_SOURCE */
index 81627ab..69d2f36 100644 (file)
@@ -57,13 +57,13 @@ static void ges_audio_test_source_get_property (GObject * object, guint
 static void ges_audio_test_source_set_property (GObject * object, guint
     property_id, const GValue * value, GParamSpec * pspec);
 
-static GstElement *ges_audio_test_source_create_source (GESTrackElement * self);
+static GstElement *ges_audio_test_source_create_source (GESSource * source);
 
 static void
 ges_audio_test_source_class_init (GESAudioTestSourceClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESAudioSourceClass *source_class = GES_AUDIO_SOURCE_CLASS (klass);
+  GESSourceClass *source_class = GES_SOURCE_CLASS (klass);
 
   object_class->get_property = ges_audio_test_source_get_property;
   object_class->set_property = ges_audio_test_source_set_property;
@@ -100,18 +100,19 @@ ges_audio_test_source_set_property (GObject * object,
 }
 
 static GstElement *
-ges_audio_test_source_create_source (GESTrackElement * trksrc)
+ges_audio_test_source_create_source (GESSource * source)
 {
   GESAudioTestSource *self;
   GstElement *ret;
   const gchar *props[] = { "volume", "freq", NULL };
 
-  self = (GESAudioTestSource *) trksrc;
+  self = (GESAudioTestSource *) source;
   ret = gst_element_factory_make ("audiotestsrc", NULL);
   g_object_set (ret, "volume", (gdouble) self->priv->volume, "freq", (gdouble)
       self->priv->freq, NULL);
 
-  ges_track_element_add_children_props (trksrc, ret, NULL, NULL, props);
+  ges_track_element_add_children_props (GES_TRACK_ELEMENT (self), ret, NULL,
+      NULL, props);
 
   return ret;
 }
@@ -199,16 +200,18 @@ ges_audio_test_source_get_volume (GESAudioTestSource * self)
   return g_value_get_double (&val);
 }
 
-/**
- * ges_audio_test_source_new:
- *
- * Creates a new #GESAudioTestSource.
+/* Creates a new #GESAudioTestSource.
  *
  * Returns: (transfer floating) (nullable): The newly created #GESAudioTestSource.
  */
 GESAudioTestSource *
 ges_audio_test_source_new (void)
 {
-  return g_object_new (GES_TYPE_AUDIO_TEST_SOURCE, "track-type",
-      GES_TRACK_TYPE_AUDIO, NULL);
+  GESAudioTestSource *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_AUDIO_TEST_SOURCE, NULL, NULL);
+
+  res = GES_AUDIO_TEST_SOURCE (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index 693e950..1105c9b 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_AUDIO_TEST_SOURCE
-#define _GES_AUDIO_TEST_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_AUDIO_TEST_SOURCE ges_audio_test_source_get_type()
-
-#define GES_AUDIO_TEST_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUDIO_TEST_SOURCE, GESAudioTestSource))
-
-#define GES_AUDIO_TEST_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUDIO_TEST_SOURCE, GESAudioTestSourceClass))
-
-#define GES_IS_AUDIO_TEST_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUDIO_TEST_SOURCE))
-
-#define GES_IS_AUDIO_TEST_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUDIO_TEST_SOURCE))
-
-#define GES_AUDIO_TEST_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUDIO_TEST_SOURCE, GESAudioTestSourceClass))
-
-typedef struct _GESAudioTestSourcePrivate GESAudioTestSourcePrivate;
-
+GES_DECLARE_TYPE(AudioTestSource, audio_test_source, AUDIO_TEST_SOURCE);
 
 /**
  * GESAudioTestSource:
  *
+ * ### Children Properties
+ *
+ *  {{ libs/GESAudioTestSource-children-props.md }}
  */
 
 struct _GESAudioTestSource {
@@ -71,10 +56,6 @@ struct _GESAudioTestSourceClass {
 };
 
 GES_API
-GType ges_audio_test_source_get_type (void);
-
-
-GES_API
 void ges_audio_test_source_set_freq(GESAudioTestSource *self,
                                           gdouble freq);
 
@@ -87,6 +68,3 @@ double ges_audio_test_source_get_freq(GESAudioTestSource *self);
 GES_API
 double ges_audio_test_source_get_volume(GESAudioTestSource *self);
 G_END_DECLS
-
-#endif /* _GES_AUDIO_TEST_SOURCE */
-
index 1ac8af0..48f2554 100644 (file)
 /**
  * SECTION: gesaudiotrack
  * @title: GESAudioTrack
- * @short_description: A standard GESTrack for raw audio
+ * @short_description: A standard #GESTrack for raw audio
  *
- * Sane default properties to specify and fixate the output stream are
- * set as restriction-caps.
- * It is advised, to modify these properties, to use
- * #ges_track_update_restriction_caps, setting them directly is
- * possible through #ges_track_set_restriction_caps, but not specifying
- * one of them can lead to negotiation issues, only use that function
- * if you actually know what you're doing :)
+ * A #GESAudioTrack is a default audio #GESTrack, with a
+ * #GES_TRACK_TYPE_AUDIO #GESTrack:track-type and "audio/x-raw(ANY)"
+ * #GESTrack:caps.
  *
- * The default properties are:
- * - format: S32LE
+ * By default, an audio track will have its #GESTrack:restriction-caps
+ * set to "audio/x-raw" with the following properties:
+ *
+ * - format: "S32LE"
  * - channels: 2
  * - rate: 44100
- * - layout: interleaved
+ * - layout: "interleaved"
+ *
+ * These fields are needed for negotiation purposes, but you can change
+ * their values if you wish. It is advised that you do so using
+ * ges_track_update_restriction_caps() with new values for the fields you
+ * wish to change, and any additional fields you may want to add. Unlike
+ * using ges_track_set_restriction_caps(), this will ensure that these
+ * default fields will at least have some value set.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -64,15 +69,68 @@ G_DEFINE_TYPE_WITH_PRIVATE (GESAudioTrack, ges_audio_track, GES_TYPE_TRACK);
 /****************************************************
  *              Private methods and utils           *
  ****************************************************/
+static void
+_sync_capsfilter_with_track (GESTrack * track, GstElement * capsfilter)
+{
+  GstCaps *restriction, *caps;
+  gint rate;
+  GstStructure *structure;
+
+  g_object_get (track, "restriction-caps", &restriction, NULL);
+  if (restriction == NULL)
+    return;
+
+  if (gst_caps_get_size (restriction) == 0)
+    goto done;
+
+  structure = gst_caps_get_structure (restriction, 0);
+  if (!gst_structure_get_int (structure, "rate", &rate))
+    goto done;
+
+  caps = gst_caps_new_simple ("audio/x-raw", "rate", G_TYPE_INT, rate, NULL);
+
+  g_object_set (capsfilter, "caps", caps, NULL);
+  gst_caps_unref (caps);
+
+done:
+  gst_caps_unref (restriction);
+}
+
+static void
+_track_restriction_changed_cb (GESTrack * track, GParamSpec * arg G_GNUC_UNUSED,
+    GstElement * capsfilter)
+{
+  _sync_capsfilter_with_track (track, capsfilter);
+}
+
+static void
+_weak_notify_cb (GESTrack * track, GstElement * capsfilter)
+{
+  g_signal_handlers_disconnect_by_func (track,
+      (GCallback) _track_restriction_changed_cb, capsfilter);
+}
+
 static GstElement *
 create_element_for_raw_audio_gap (GESTrack * track)
 {
-  GstElement *elem;
+  GstElement *bin;
+  GstElement *capsfilter;
 
-  elem = gst_element_factory_make ("audiotestsrc", NULL);
-  g_object_set (elem, "wave", 4, NULL);
+  bin = gst_parse_bin_from_description
+      ("audiotestsrc wave=silence name=src ! audioconvert ! audioresample ! audioconvert ! capsfilter name=gapfilter caps=audio/x-raw",
+      TRUE, NULL);
 
-  return elem;
+  capsfilter = gst_bin_get_by_name (GST_BIN (bin), "gapfilter");
+  g_object_weak_ref (G_OBJECT (capsfilter), (GWeakNotify) _weak_notify_cb,
+      track);
+  g_signal_connect (track, "notify::restriction-caps",
+      (GCallback) _track_restriction_changed_cb, capsfilter);
+
+  _sync_capsfilter_with_track (track, capsfilter);
+
+  gst_object_unref (capsfilter);
+
+  return bin;
 }
 
 
@@ -112,10 +170,19 @@ ges_audio_track_class_init (GESAudioTrackClass * klass)
 /**
  * ges_audio_track_new:
  *
- * Creates a new #GESAudioTrack of type #GES_TRACK_TYPE_AUDIO and with generic
- * raw audio caps ("audio/x-raw");
+ * Creates a new audio track, with a #GES_TRACK_TYPE_AUDIO
+ * #GESTrack:track-type, "audio/x-raw(ANY)" #GESTrack:caps, and
+ * "audio/x-raw" #GESTrack:restriction-caps with the properties:
+ *
+ * - format: "S32LE"
+ * - channels: 2
+ * - rate: 44100
+ * - layout: "interleaved"
+ *
+ * You should use ges_track_update_restriction_caps() if you wish to
+ * modify these fields, or add additional ones.
  *
- * Returns: (transfer floating): A new #GESTrack
+ * Returns: (transfer floating): The newly created audio track.
  */
 GESAudioTrack *
 ges_audio_track_new (void)
index 87ceda2..45d8f5d 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_AUDIO_TRACK_H_
-#define _GES_AUDIO_TRACK_H_
+#pragma once
 
 #include <glib-object.h>
 
 G_BEGIN_DECLS
 
 #define GES_TYPE_AUDIO_TRACK             (ges_audio_track_get_type ())
-#define GES_AUDIO_TRACK(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUDIO_TRACK, GESAudioTrack))
-#define GES_AUDIO_TRACK_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUDIO_TRACK, GESAudioTrackClass))
-#define GES_IS_AUDIO_TRACK(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUDIO_TRACK))
-#define GES_IS_AUDIO_TRACK_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUDIO_TRACK))
-#define GES_AUDIO_TRACK_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUDIO_TRACK, GESAudioTrackClass))
+GES_DECLARE_TYPE(AudioTrack, audio_track, AUDIO_TRACK);
 
-typedef struct _GESAudioTrackPrivate GESAudioTrackPrivate;
 
 struct _GESAudioTrackClass
 {
@@ -55,9 +49,6 @@ struct _GESAudioTrack
 };
 
 GES_API
-GType          ges_audio_track_get_type (void) G_GNUC_CONST;
-GES_API
 GESAudioTrack* ges_audio_track_new (void);
 
-G_END_DECLS
-#endif /* _GES_AUDIO_TRACK_H_ */
+G_END_DECLS
\ No newline at end of file
index 3a94876..b78eae6 100644 (file)
@@ -90,6 +90,7 @@ ges_audio_transition_class_init (GESAudioTransitionClass * klass)
   object_class->finalize = ges_audio_transition_finalize;
 
   toclass->create_element = ges_audio_transition_create_element;
+  toclass->ABI.abi.default_track_type = GES_TRACK_TYPE_AUDIO;
 
 }
 
@@ -289,10 +290,18 @@ ges_audio_transition_duration_changed (GESTrackElement * track_element,
  * Creates a new #GESAudioTransition.
  *
  * Returns: (transfer floating): The newly created #GESAudioTransition.
+ *
+ * Deprecated: 1.18: This should never be called by applications as this will
+ * be created by clips.
  */
 GESAudioTransition *
 ges_audio_transition_new (void)
 {
-  return g_object_new (GES_TYPE_AUDIO_TRANSITION, "track-type",
-      GES_TRACK_TYPE_AUDIO, NULL);
+  GESAudioTransition *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_AUDIO_TRANSITION, NULL, NULL);
+
+  res = GES_AUDIO_TRANSITION (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index 203973f..2be0d8b 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_AUDIO_TRANSITION
-#define _GES_AUDIO_TRANSITION
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_AUDIO_TRANSITION ges_audio_transition_get_type()
-
-#define GES_AUDIO_TRANSITION(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUDIO_TRANSITION, GESAudioTransition))
-
-#define GES_AUDIO_TRANSITION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUDIO_TRANSITION, GESAudioTransitionClass))
-
-#define GES_IS_AUDIO_TRANSITION(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUDIO_TRANSITION))
-
-#define GES_IS_AUDIO_TRANSITION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUDIO_TRANSITION))
-
-#define GES_AUDIO_TRANSITION_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUDIO_TRANSITION, GESAudioTransitionClass))
-
-typedef struct _GESAudioTransitionPrivate GESAudioTransitionPrivate;
+GES_DECLARE_TYPE(AudioTransition, audio_transition, AUDIO_TRANSITION);
 
 /**
  * GESAudioTransition:
@@ -68,13 +51,7 @@ struct _GESAudioTransitionClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_audio_transition_get_type (void);
-
-GES_API
+GES_DEPRECATED
 GESAudioTransition* ges_audio_transition_new (void);
 
 G_END_DECLS
-
-#endif /* _GES_TRACK_AUDIO_transition */
-
index 755d6e0..2d56160 100644 (file)
 #include "ges-utils.h"
 #include "ges-internal.h"
 #include "ges-track-element.h"
+#include "ges-uri-source.h"
 #include "ges-audio-uri-source.h"
 #include "ges-uri-asset.h"
 #include "ges-extractable.h"
 
 struct _GESAudioUriSourcePrivate
 {
-  GstElement *decodebin;        /* Reference owned by parent class */
+  GESUriSource parent;
 };
 
 enum
@@ -45,49 +46,11 @@ enum
   PROP_URI
 };
 
-static void
-ges_audio_uri_source_track_set_cb (GESAudioUriSource * self,
-    GParamSpec * arg G_GNUC_UNUSED, gpointer nothing)
-{
-  GESTrack *track;
-  const GstCaps *caps = NULL;
-
-  if (!self->priv->decodebin)
-    return;
-
-  track = ges_track_element_get_track (GES_TRACK_ELEMENT (self));
-  if (!track)
-    return;
-
-  caps = ges_track_get_caps (track);
-
-  GST_INFO_OBJECT (self, "Setting caps to: %" GST_PTR_FORMAT, caps);
-  g_object_set (self->priv->decodebin, "caps", caps, NULL);
-}
-
 /* GESSource VMethod */
 static GstElement *
-ges_audio_uri_source_create_source (GESTrackElement * trksrc)
+ges_audio_uri_source_create_source (GESSource * element)
 {
-  GESAudioUriSource *self;
-  GESTrack *track;
-  GstElement *decodebin;
-  const GstCaps *caps = NULL;
-
-  self = (GESAudioUriSource *) trksrc;
-
-  track = ges_track_element_get_track (trksrc);
-
-  self->priv->decodebin = decodebin =
-      gst_element_factory_make ("uridecodebin", NULL);
-
-  if (track)
-    caps = ges_track_get_caps (track);
-
-  g_object_set (decodebin, "caps", caps,
-      "expose-all-streams", FALSE, "uri", self->uri, NULL);
-
-  return decodebin;
+  return ges_uri_source_create_source (GES_AUDIO_URI_SOURCE (element)->priv);
 }
 
 /* Extractable interface implementation */
@@ -99,25 +62,10 @@ ges_extractable_check_id (GType type, const gchar * id, GError ** error)
 }
 
 static void
-extractable_set_asset (GESExtractable * self, GESAsset * asset)
-{
-  /* FIXME That should go into #GESTrackElement, but
-   * some work is needed to make sure it works properly */
-
-  if (ges_track_element_get_track_type (GES_TRACK_ELEMENT (self)) ==
-      GES_TRACK_TYPE_UNKNOWN) {
-    ges_track_element_set_track_type (GES_TRACK_ELEMENT (self),
-        ges_track_element_asset_get_track_type (GES_TRACK_ELEMENT_ASSET
-            (asset)));
-  }
-}
-
-static void
 ges_extractable_interface_init (GESExtractableInterface * iface)
 {
   iface->asset_type = GES_TYPE_URI_SOURCE_ASSET;
   iface->check_id = ges_extractable_check_id;
-  iface->set_asset = extractable_set_asset;
 }
 
 G_DEFINE_TYPE_WITH_CODE (GESAudioUriSource, ges_audio_uri_source,
@@ -128,6 +76,17 @@ G_DEFINE_TYPE_WITH_CODE (GESAudioUriSource, ges_audio_uri_source,
 
 /* GObject VMethods */
 
+static gboolean
+_get_natural_framerate (GESTimelineElement * self, gint * framerate_n,
+    gint * framerate_d)
+{
+  if (self->parent)
+    return ges_timeline_element_get_natural_framerate (self->parent,
+        framerate_n, framerate_d);
+
+  return FALSE;
+}
+
 static void
 ges_audio_uri_source_get_property (GObject * object, guint property_id,
     GValue * value, GParamSpec * pspec)
@@ -155,7 +114,7 @@ ges_audio_uri_source_set_property (GObject * object, guint property_id,
         GST_WARNING_OBJECT (object, "Uri already set to %s", uriclip->uri);
         return;
       }
-      uriclip->uri = g_value_dup_string (value);
+      uriclip->priv->uri = uriclip->uri = g_value_dup_string (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -163,25 +122,25 @@ ges_audio_uri_source_set_property (GObject * object, guint property_id,
 }
 
 static void
-ges_audio_uri_source_dispose (GObject * object)
+ges_audio_uri_source_finalize (GObject * object)
 {
   GESAudioUriSource *uriclip = GES_AUDIO_URI_SOURCE (object);
 
-  if (uriclip->uri)
-    g_free (uriclip->uri);
+  g_free (uriclip->uri);
 
-  G_OBJECT_CLASS (ges_audio_uri_source_parent_class)->dispose (object);
+  G_OBJECT_CLASS (ges_audio_uri_source_parent_class)->finalize (object);
 }
 
 static void
 ges_audio_uri_source_class_init (GESAudioUriSourceClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESAudioSourceClass *source_class = GES_AUDIO_SOURCE_CLASS (klass);
+  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
+  GESSourceClass *src_class = GES_SOURCE_CLASS (klass);
 
   object_class->get_property = ges_audio_uri_source_get_property;
   object_class->set_property = ges_audio_uri_source_set_property;
-  object_class->dispose = ges_audio_uri_source_dispose;
+  object_class->finalize = ges_audio_uri_source_finalize;
 
   /**
    * GESAudioUriSource:uri:
@@ -192,16 +151,17 @@ ges_audio_uri_source_class_init (GESAudioUriSourceClass * klass)
       g_param_spec_string ("uri", "URI", "uri of the resource",
           NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
-  source_class->create_source = ges_audio_uri_source_create_source;
+  element_class->get_natural_framerate = _get_natural_framerate;
+
+  src_class->select_pad = ges_uri_source_select_pad;
+  src_class->create_source = ges_audio_uri_source_create_source;
 }
 
 static void
 ges_audio_uri_source_init (GESAudioUriSource * self)
 {
   self->priv = ges_audio_uri_source_get_instance_private (self);
-
-  g_signal_connect (self, "notify::track",
-      G_CALLBACK (ges_audio_uri_source_track_set_cb), NULL);
+  ges_uri_source_init (GES_TRACK_ELEMENT (self), self->priv);
 }
 
 /**
index eddaedc..b62aed4 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_AUDIO_URI_SOURCE
-#define _GES_AUDIO_URI_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 
 G_BEGIN_DECLS
 
+typedef struct _GESUriSource GESUriSource;
 #define GES_TYPE_AUDIO_URI_SOURCE ges_audio_uri_source_get_type()
-
-#define GES_AUDIO_URI_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUDIO_URI_SOURCE, GESAudioUriSource))
-
-#define GES_AUDIO_URI_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUDIO_URI_SOURCE, GESAudioUriSourceClass))
-
-#define GES_IS_AUDIO_URI_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUDIO_URI_SOURCE))
-
-#define GES_IS_AUDIO_URI_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUDIO_URI_SOURCE))
-
-#define GES_AUDIO_URI_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUDIO_URI_SOURCE, GESAudioUriSourceClass))
-
-typedef struct _GESAudioUriSourcePrivate GESAudioUriSourcePrivate;
+GES_DECLARE_TYPE(AudioUriSource, audio_uri_source, AUDIO_URI_SOURCE);
 
 /**
  * GESAudioUriSource:
+ *
+ * ### Children Properties
+ *
+ *  {{ libs/GESVideoUriSource-children-props.md }}
  */
 struct _GESAudioUriSource {
   /*< private >*/
@@ -55,7 +43,7 @@ struct _GESAudioUriSource {
 
   gchar *uri;
 
-  GESAudioUriSourcePrivate *priv;
+  GESUriSource *priv;
 
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
@@ -69,10 +57,4 @@ struct _GESAudioUriSourceClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_audio_uri_source_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_AUDIO_URI_SOURCE */
-
index f28f37a..e2e083d 100644 (file)
@@ -39,39 +39,29 @@ static guint auto_transition_signals[LAST_SIGNAL] = { 0 };
 G_DEFINE_TYPE (GESAutoTransition, ges_auto_transition, G_TYPE_OBJECT);
 
 static void
-neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
-    GESAutoTransition * self)
+neighbour_changed_cb (G_GNUC_UNUSED GObject * object,
+    G_GNUC_UNUSED GParamSpec * arg, GESAutoTransition * self)
 {
   gint64 new_duration;
-  GESTimelineElement *parent =
-      ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT (clip));
+  guint32 layer_prio;
+  GESLayer *layer;
+  GESTimeline *timeline;
 
-  if (ELEMENT_FLAG_IS_SET (parent, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+  if (self->frozen) {
+    GST_LOG_OBJECT (self, "Not updating because frozen");
     return;
   }
 
-  if (parent) {
-    GESTimelineElement *prev_topparent =
-        ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT
-        (self->next_source));
-    GESTimelineElement *next_topparent =
-        ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT
-        (self->previous_source));
-
-    if (ELEMENT_FLAG_IS_SET (prev_topparent, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
-        ELEMENT_FLAG_IS_SET (next_topparent, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-      return;
-    }
-
-    if (parent == prev_topparent && parent == next_topparent) {
-      GST_DEBUG_OBJECT (self,
-          "Moving all inside the same group, nothing to do");
-      return;
-    }
+  if (self->positioning) {
+    /* this can happen when the transition is moved layers as the layer
+     * may resync its priorities */
+    GST_LOG_OBJECT (self, "Not updating because positioning");
+    return;
   }
 
-  if (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self->next_source) !=
-      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self->previous_source)) {
+  layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self->next_source);
+
+  if (layer_prio != GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self->previous_source)) {
     GST_DEBUG_OBJECT (self, "Destroy changed layer");
     g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0);
     return;
@@ -90,24 +80,75 @@ neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
     return;
   }
 
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (self->transition_clip);
+  layer = timeline ? ges_timeline_get_layer (timeline, layer_prio) : NULL;
+  if (!layer) {
+    GST_DEBUG_OBJECT (self, "Destroy no layer");
+    g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0);
+    return;
+  }
+
   self->positioning = TRUE;
+  GES_TIMELINE_ELEMENT_SET_BEING_EDITED (self->transition_clip);
   _set_start0 (GES_TIMELINE_ELEMENT (self->transition_clip),
       _START (self->next_source));
   _set_duration0 (GES_TIMELINE_ELEMENT (self->transition_clip), new_duration);
+  ges_clip_move_to_layer (self->transition_clip, layer);
+  GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (self->transition_clip);
   self->positioning = FALSE;
+
+  gst_object_unref (layer);
 }
 
 static void
 _track_changed_cb (GESTrackElement * track_element,
     GParamSpec * arg G_GNUC_UNUSED, GESAutoTransition * self)
 {
+  if (self->frozen) {
+    GST_LOG_OBJECT (self, "Not updating because frozen");
+    return;
+  }
+
   if (ges_track_element_get_track (track_element) == NULL) {
     GST_DEBUG_OBJECT (self, "Neighboor %" GST_PTR_FORMAT
         " removed from track ... auto destructing", track_element);
 
     g_signal_emit (self, auto_transition_signals[DESTROY_ME], 0);
   }
+}
+
+static void
+_connect_to_source (GESAutoTransition * self, GESTrackElement * source)
+{
+  g_signal_connect (source, "notify::start",
+      G_CALLBACK (neighbour_changed_cb), self);
+  g_signal_connect_after (source, "notify::priority",
+      G_CALLBACK (neighbour_changed_cb), self);
+  g_signal_connect (source, "notify::duration",
+      G_CALLBACK (neighbour_changed_cb), self);
 
+  g_signal_connect (source, "notify::track",
+      G_CALLBACK (_track_changed_cb), self);
+}
+
+static void
+_disconnect_from_source (GESAutoTransition * self, GESTrackElement * source)
+{
+  g_signal_handlers_disconnect_by_func (source, neighbour_changed_cb, self);
+  g_signal_handlers_disconnect_by_func (source, _track_changed_cb, self);
+}
+
+void
+ges_auto_transition_set_source (GESAutoTransition * self,
+    GESTrackElement * source, GESEdge edge)
+{
+  _disconnect_from_source (self, self->previous_source);
+  _connect_to_source (self, source);
+
+  if (edge == GES_EDGE_END)
+    self->next_source = source;
+  else
+    self->previous_source = source;
 }
 
 static void
@@ -120,16 +161,8 @@ ges_auto_transition_finalize (GObject * object)
 {
   GESAutoTransition *self = GES_AUTO_TRANSITION (object);
 
-  g_signal_handlers_disconnect_by_func (self->previous_source,
-      neighbour_changed_cb, self);
-  g_signal_handlers_disconnect_by_func (self->next_source, neighbour_changed_cb,
-      self);
-  g_signal_handlers_disconnect_by_func (self->next_source, _track_changed_cb,
-      self);
-  g_signal_handlers_disconnect_by_func (self->previous_source,
-      _track_changed_cb, self);
-
-  g_free (self->key);
+  _disconnect_from_source (self, self->previous_source);
+  _disconnect_from_source (self, self->next_source);
 
   G_OBJECT_CLASS (ges_auto_transition_parent_class)->finalize (object);
 }
@@ -146,59 +179,37 @@ ges_auto_transition_class_init (GESAutoTransitionClass * klass)
   object_class->finalize = ges_auto_transition_finalize;
 }
 
-
 GESAutoTransition *
 ges_auto_transition_new (GESTrackElement * transition,
     GESTrackElement * previous_source, GESTrackElement * next_source)
 {
   GESAutoTransition *self = g_object_new (GES_TYPE_AUTO_TRANSITION, NULL);
 
+  self->frozen = FALSE;
   self->previous_source = previous_source;
   self->next_source = next_source;
   self->transition = transition;
 
-  self->previous_clip =
-      GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (previous_source));
-  self->next_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (next_source));
   self->transition_clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (transition));
 
-  g_signal_connect (previous_source, "notify::start",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect_after (previous_source, "notify::priority",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (next_source, "notify::start",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (next_source, "notify::priority",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (previous_source, "notify::duration",
-      G_CALLBACK (neighbour_changed_cb), self);
-  g_signal_connect (next_source, "notify::duration",
-      G_CALLBACK (neighbour_changed_cb), self);
-
-  g_signal_connect (next_source, "notify::track",
-      G_CALLBACK (_track_changed_cb), self);
-  g_signal_connect (previous_source, "notify::track",
-      G_CALLBACK (_track_changed_cb), self);
+  _connect_to_source (self, previous_source);
+  _connect_to_source (self, next_source);
 
   GST_DEBUG_OBJECT (self, "Created transition %" GST_PTR_FORMAT
       " between %" GST_PTR_FORMAT "[%" GST_TIME_FORMAT
       " - %" GST_TIME_FORMAT "] and: %" GST_PTR_FORMAT
       "[%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "]"
-      " in layer nb %i, start: %" GST_TIME_FORMAT " duration: %"
-      GST_TIME_FORMAT, transition, previous_source,
+      " in layer nb %" G_GUINT32_FORMAT ", start: %" GST_TIME_FORMAT
+      " duration: %" GST_TIME_FORMAT, transition, previous_source,
       GST_TIME_ARGS (_START (previous_source)),
       GST_TIME_ARGS (_END (previous_source)),
       next_source,
       GST_TIME_ARGS (_START (next_source)),
       GST_TIME_ARGS (_END (next_source)),
-      ges_layer_get_priority (ges_clip_get_layer
-          (self->previous_clip)),
+      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next_source),
       GST_TIME_ARGS (_START (transition)),
       GST_TIME_ARGS (_DURATION (transition)));
 
-  self->key = g_strdup_printf ("%p%p", self->previous_source,
-      self->next_source);
-
   return self;
 }
 
@@ -207,5 +218,5 @@ ges_auto_transition_update (GESAutoTransition * self)
 {
   GST_INFO ("Updating info %s",
       GES_TIMELINE_ELEMENT_NAME (self->transition_clip));
-  neighbour_changed_cb (self->previous_clip, NULL, self);
+  neighbour_changed_cb (NULL, NULL, self);
 }
index 4058b04..29f6238 100644 (file)
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
  */
 
-#ifndef _GES_AUTO_TRANSITION_H_
-#define _GES_AUTO_TRANSITION_H_
+#pragma once
 
 #include <glib-object.h>
 #include "ges-track-element.h"
 G_BEGIN_DECLS
 
 #define GES_TYPE_AUTO_TRANSITION             (ges_auto_transition_get_type ())
-#define GES_AUTO_TRANSITION(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_AUTO_TRANSITION, GESAutoTransition))
-#define GES_AUTO_TRANSITION_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_AUTO_TRANSITION, GESAutoTransitionClass))
-#define GES_IS_AUTO_TRANSITION(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_AUTO_TRANSITION))
-#define GES_IS_AUTO_TRANSITION_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_AUTO_TRANSITION))
-#define GES_AUTO_TRANSITION_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_AUTO_TRANSITION, GESAutoTransitionClass))
-
 typedef struct _GESAutoTransitionClass GESAutoTransitionClass;
 typedef struct _GESAutoTransition GESAutoTransition;
 
-
+GES_DECLARE_TYPE(AutoTransition, auto_transition, AUTO_TRANSITION);
 
 struct _GESAutoTransitionClass
 {
@@ -56,25 +49,18 @@ struct _GESAutoTransition
   GESTrackElement *next_source;
   GESTrackElement *transition;
 
-  GESLayer *layer;
-
-  GESClip *previous_clip;
-  GESClip *next_clip;
   GESClip *transition_clip;
   gboolean positioning;
 
-  gchar *key;
+  gboolean frozen;
 
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
 };
 
-G_GNUC_INTERNAL GType ges_auto_transition_get_type (void) G_GNUC_CONST;
-
 G_GNUC_INTERNAL void ges_auto_transition_update (GESAutoTransition *self);
 G_GNUC_INTERNAL GESAutoTransition * ges_auto_transition_new (GESTrackElement * transition,
                                              GESTrackElement * previous_source,
                                              GESTrackElement * next_source);
 
 G_END_DECLS
-#endif /* _GES_AUTO_TRANSITION_H_ */
index 4cf1e65..952abf5 100644 (file)
 /**
  * SECTION: gesbaseeffectclip
  * @title: GESBaseEffectClip
- * @short_description: An effect in a GESLayer
+ * @short_description: An effect in a #GESLayer
  *
- * The effect will be applied on the sources that have lower priorities
- * (higher number) between the inpoint and the end of it.
+ * #GESBaseEffectClip-s are clips whose core elements are
+ * #GESBaseEffect-s.
+ *
+ * ## Effects
+ *
+ * #GESBaseEffectClip-s can have **additional** #GESBaseEffect-s added as
+ * non-core elements. These additional effects are applied to the output
+ * of the core effects of the clip that they share a #GESTrack with. See
+ * #GESClip for how to add and move these effects from the clip.
+ *
+ * Note that you cannot add time effects to #GESBaseEffectClip, neither
+ * as core children, nor as additional effects.
  */
+
+/* FIXME: properly handle the priority of the children. How should we sort
+ * the priority of effects when two #GESBaseEffectClip's overlap? */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -41,14 +54,32 @@ struct _GESBaseEffectClipPrivate
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffectClip, ges_base_effect_clip,
     GES_TYPE_OPERATION_CLIP);
 
+static gboolean
+ges_base_effect_clip_add_child (GESContainer * container,
+    GESTimelineElement * element)
+{
+  if (GES_IS_TIME_EFFECT (element)) {
+    GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
+        "because it is a time effect", GES_ARGS (element));
+    return FALSE;
+  }
+
+  return
+      GES_CONTAINER_CLASS (ges_base_effect_clip_parent_class)->add_child
+      (container, element);
+}
+
 static void
 ges_base_effect_clip_class_init (GESBaseEffectClipClass * klass)
 {
+  GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
+
+  GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE;
+  container_class->add_child = ges_base_effect_clip_add_child;
 }
 
 static void
 ges_base_effect_clip_init (GESBaseEffectClip * self)
 {
   self->priv = ges_base_effect_clip_get_instance_private (self);
-
 }
index a152739..867c6d1 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_BASE_EFFECT_CLIP
-#define _GES_BASE_EFFECT_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_BASE_EFFECT_CLIP ges_base_effect_clip_get_type()
-
-#define GES_BASE_EFFECT_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_BASE_EFFECT_CLIP, GESBaseEffectClip))
-
-#define GES_BASE_EFFECT_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_BASE_EFFECT_CLIP, GESBaseEffectClipClass))
-
-#define GES_IS_BASE_EFFECT_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_BASE_EFFECT_CLIP))
-
-#define GES_IS_BASE_EFFECT_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_BASE_EFFECT_CLIP))
-
-#define GES_BASE_EFFECT_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_BASE_EFFECT_CLIP, GESBaseEffectClipClass))
-
-typedef struct _GESBaseEffectClipPrivate GESBaseEffectClipPrivate;
+GES_DECLARE_TYPE(BaseEffectClip, base_effect_clip, BASE_EFFECT_CLIP);
 
 /**
  * GESBaseEffectClip:
@@ -70,8 +53,4 @@ struct _GESBaseEffectClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_base_effect_clip_get_type (void);
-
-G_END_DECLS
-#endif /* _GES_BASE_EFFECT_CLIP */
+G_END_DECLS
\ No newline at end of file
index b604833..42f42e3 100644 (file)
  * @title: GESBaseEffect
  * @short_description: adds an effect to a stream in a GESSourceClip or a
  * GESLayer
+ *
+ * A #GESBaseEffect is some operation that applies an effect to the data
+ * it receives.
+ *
+ * ## Time Effects
+ *
+ * Some operations will change the timing of the stream data they receive
+ * in some way. In particular, the #GstElement that they wrap could alter
+ * the times of the segment they receive in a #GST_EVENT_SEGMENT event,
+ * or the times of a seek they receive in a #GST_EVENT_SEEK event. Such
+ * operations would be considered time effects since they translate the
+ * times they receive on their source to different times at their sink,
+ * and vis versa. This introduces two sets of time coordinates for the
+ * event: (internal) sink coordinates and (internal) source coordinates,
+ * where segment times are translated from the sink coordinates to the
+ * source coordinates, and seek times are translated from the source
+ * coordinates to the sink coordinates.
+ *
+ * If you use such an effect in GES, you will need to inform GES of the
+ * properties that control the timing with
+ * ges_base_effect_register_time_property(), and the effect's timing
+ * behaviour using ges_base_effect_set_time_translation_funcs().
+ *
+ * Note that a time effect should not have its
+ * #GESTrackElement:has-internal-source set to %TRUE.
+ *
+ * In addition, note that GES only *fully* supports time effects whose
+ * mapping from the source to sink coordinates (those applied to seeks)
+ * obeys:
+ *
+ * + Maps the time `0` to `0`. So initial time-shifting effects are
+ *   excluded.
+ * + Is monotonically increasing. So reversing effects, and effects that
+ *   jump backwards in the stream are excluded.
+ * + Can handle a reasonable #GstClockTime, relative to the project. So
+ *   this would exclude a time effect with an extremely large speed-up
+ *   that would cause the converted #GstClockTime seeks to overflow.
+ * + Is 'continuously reversible'. This essentially means that for every
+ *   time in the sink coordinates, we can, to 'good enough' accuracy,
+ *   calculate the corresponding time in the source coordinates. Moreover,
+ *   this should correspond to how segment times are translated from
+ *   sink to source.
+ * + Only depends on the registered time properties, rather than the
+ *   state of the #GstElement or the data it receives. This would exclude,
+ *   say, an effect that would speedup if there is more red in the image
+ *   it receives.
+ *
+ * Note that a constant-rate-change effect that is not extremely fast or
+ * slow would satisfy these conditions. For such effects, you may wish to
+ * use ges_effect_class_register_rate_property().
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #include "ges-track-element.h"
 #include "ges-base-effect.h"
 
+typedef struct _TimePropertyData
+{
+  gchar *property_name;
+  GObject *child;
+  GParamSpec *pspec;
+} TimePropertyData;
+
+static void
+_time_property_data_free (gpointer data_p)
+{
+  TimePropertyData *data = data_p;
+  g_free (data->property_name);
+  gst_object_unref (data->child);
+  g_param_spec_unref (data->pspec);
+  g_free (data);
+}
+
 struct _GESBaseEffectPrivate
 {
-  void *nothing;
+  GList *time_properties;
+  GESBaseEffectTimeTranslationFunc source_to_sink;
+  GESBaseEffectTimeTranslationFunc sink_to_source;
+  gpointer translation_data;
+  GDestroyNotify destroy_translation_data;
 };
 
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseEffect, ges_base_effect,
     GES_TYPE_OPERATION);
 
+static gboolean
+ges_base_effect_set_child_property_full (GESTimelineElement * element,
+    GObject * child, GParamSpec * pspec, const GValue * value, GError ** error)
+{
+  GESClip *parent = GES_IS_CLIP (element->parent) ?
+      GES_CLIP (element->parent) : NULL;
+
+  if (parent && !ges_clip_can_set_time_property_of_child (parent,
+          GES_TRACK_ELEMENT (element), child, pspec, value, error)) {
+    GST_INFO_OBJECT (element, "Cannot set time property '%s::%s' "
+        "because the parent clip %" GES_FORMAT " would not allow it",
+        G_OBJECT_TYPE_NAME (child), pspec->name, GES_ARGS (parent));
+    return FALSE;
+  }
+
+  return
+      GES_TIMELINE_ELEMENT_CLASS
+      (ges_base_effect_parent_class)->set_child_property_full (element, child,
+      pspec, value, error);
+}
+
+static void
+ges_base_effect_dispose (GObject * object)
+{
+  GESBaseEffectPrivate *priv = GES_BASE_EFFECT (object)->priv;
+
+  g_list_free_full (priv->time_properties, _time_property_data_free);
+  priv->time_properties = NULL;
+  if (priv->destroy_translation_data)
+    priv->destroy_translation_data (priv->translation_data);
+  priv->destroy_translation_data = NULL;
+  priv->source_to_sink = NULL;
+  priv->sink_to_source = NULL;
+
+  G_OBJECT_CLASS (ges_base_effect_parent_class)->dispose (object);
+}
+
 static void
 ges_base_effect_class_init (GESBaseEffectClass * klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
+
+  object_class->dispose = ges_base_effect_dispose;
+  element_class->set_child_property_full =
+      ges_base_effect_set_child_property_full;
 }
 
 static void
@@ -52,3 +166,264 @@ ges_base_effect_init (GESBaseEffect * self)
 {
   self->priv = ges_base_effect_get_instance_private (self);
 }
+
+static void
+_child_property_removed (GESTimelineElement * element, GObject * child,
+    GParamSpec * pspec, gpointer user_data)
+{
+  GList *tmp;
+  GESBaseEffectPrivate *priv = GES_BASE_EFFECT (element)->priv;
+
+  for (tmp = priv->time_properties; tmp; tmp = tmp->next) {
+    TimePropertyData *data = tmp->data;
+    if (data->child == child && data->pspec == pspec) {
+      priv->time_properties = g_list_remove (priv->time_properties, data);
+      _time_property_data_free (data);
+      return;
+    }
+  }
+}
+
+/**
+ * ges_base_effect_register_time_property:
+ * @effect: A #GESBaseEffect
+ * @child_property_name: The name of the child property to register as
+ * a time property
+ *
+ * Register a child property of the effect as a property that, when set,
+ * can change the timing of its input data. The child property should be
+ * specified as in ges_timeline_element_lookup_child().
+ *
+ * You should also set the corresponding time translation using
+ * ges_base_effect_set_time_translation_funcs().
+ *
+ * Note that @effect must not be part of a clip, nor can it have
+ * #GESTrackElement:has-internal-source set to %TRUE.
+ *
+ * Returns: %TRUE if the child property was found and newly registered.
+ * Since: 1.18
+ */
+gboolean
+ges_base_effect_register_time_property (GESBaseEffect * effect,
+    const gchar * child_property_name)
+{
+  GESTimelineElement *element;
+  GESTrackElement *el;
+  GParamSpec *pspec;
+  GObject *child;
+  GList *tmp;
+  TimePropertyData *data;
+
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+  el = GES_TRACK_ELEMENT (effect);
+  element = GES_TIMELINE_ELEMENT (el);
+
+  g_return_val_if_fail (element->parent == NULL, FALSE);
+  g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE,
+      FALSE);
+
+  if (!ges_timeline_element_lookup_child (element, child_property_name,
+          &child, &pspec))
+    return FALSE;
+
+  for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) {
+    data = tmp->data;
+    if (data->child == child && data->pspec == pspec) {
+      GST_WARNING_OBJECT (effect, "Already registered the time effect for %s",
+          child_property_name);
+      g_object_unref (child);
+      g_param_spec_unref (pspec);
+      return FALSE;
+    }
+  }
+
+  ges_track_element_set_has_internal_source_is_forbidden (el);
+
+  data = g_new0 (TimePropertyData, 1);
+  data->child = child;
+  data->pspec = pspec;
+  data->property_name = g_strdup (child_property_name);
+
+  effect->priv->time_properties =
+      g_list_prepend (effect->priv->time_properties, data);
+
+  g_signal_handlers_disconnect_by_func (effect, _child_property_removed, NULL);
+  g_signal_connect (effect, "child-property-removed",
+      G_CALLBACK (_child_property_removed), NULL);
+
+  return TRUE;
+}
+
+/**
+ * ges_base_effect_set_time_translation_funcs:
+ * @effect: A #GESBaseEffect
+ * @source_to_sink_func: (nullable) (scope notified): The function to use
+ * for querying how a time is translated from the source coordinates to
+ * the sink coordinates of @effect
+ * @sink_to_source_func: (nullable) (scope notified): The function to use
+ * for querying how a time is translated from the sink coordinates to the
+ * source coordinates of @effect
+ * @user_data: (closure): Data to pass to both @source_to_sink_func and
+ * @sink_to_source_func
+ * @destroy: (destroy user_data) (nullable): Method to call to destroy
+ * @user_data, or %NULL
+ *
+ * Set the time translation query functions for the time effect. If an
+ * effect is a time effect, it will have two sets of coordinates: one
+ * at its sink and one at its source. The given functions should be able
+ * to translate between these two sets of coordinates. More specifically,
+ * @source_to_sink_func should *emulate* how the corresponding #GstElement
+ * would translate the #GstSegment @time field, and @sink_to_source_func
+ * should emulate how the corresponding #GstElement would translate the
+ * seek query @start and @stop values, as used in gst_element_seek(). As
+ * such, @sink_to_source_func should act as an approximate reverse of
+ * @source_to_sink_func.
+ *
+ * Note, these functions will be passed a table of time properties, as
+ * registered in ges_base_effect_register_time_property(), and their
+ * values. The functions should emulate what the translation *would* be
+ * *if* the time properties were set to the given values. They should not
+ * use the currently set values.
+ *
+ * Note that @effect must not be part of a clip, nor can it have
+ * #GESTrackElement:has-internal-source set to %TRUE.
+ *
+ * Returns: %TRUE if the translation functions were set.
+ * Since: 1.18
+ */
+gboolean
+ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect,
+    GESBaseEffectTimeTranslationFunc source_to_sink_func,
+    GESBaseEffectTimeTranslationFunc sink_to_source_func,
+    gpointer user_data, GDestroyNotify destroy)
+{
+  GESTimelineElement *element;
+  GESTrackElement *el;
+  GESBaseEffectPrivate *priv;
+
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+
+  element = GES_TIMELINE_ELEMENT (effect);
+  el = GES_TRACK_ELEMENT (element);
+
+  g_return_val_if_fail (element->parent == NULL, FALSE);
+  g_return_val_if_fail (ges_track_element_has_internal_source (el) == FALSE,
+      FALSE);
+
+  ges_track_element_set_has_internal_source_is_forbidden (el);
+
+  priv = effect->priv;
+  if (priv->destroy_translation_data)
+    priv->destroy_translation_data (priv->translation_data);
+
+  priv->translation_data = user_data;
+  priv->destroy_translation_data = destroy;
+  priv->source_to_sink = source_to_sink_func;
+  priv->sink_to_source = sink_to_source_func;
+
+  return TRUE;
+}
+
+/**
+ * ges_base_effect_is_time_effect:
+ * @effect: A #GESBaseEffect
+ *
+ * Get whether the effect is considered a time effect or not. An effect
+ * with registered time properties or set translation functions is
+ * considered a time effect.
+ *
+ * Returns: %TRUE if @effect is considered a time effect.
+ * Since: 1.18
+ */
+gboolean
+ges_base_effect_is_time_effect (GESBaseEffect * effect)
+{
+  GESBaseEffectPrivate *priv;
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+
+  priv = effect->priv;
+  if (priv->time_properties || priv->source_to_sink || priv->sink_to_source)
+    return TRUE;
+  return FALSE;
+}
+
+gchar *
+ges_base_effect_get_time_property_name (GESBaseEffect * effect,
+    GObject * child, GParamSpec * pspec)
+{
+  GList *tmp;
+  for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) {
+    TimePropertyData *data = tmp->data;
+    if (data->pspec == pspec && data->child == child)
+      return g_strdup (data->property_name);
+  }
+  return NULL;
+}
+
+static void
+_gvalue_free (gpointer data)
+{
+  GValue *val = data;
+  g_value_unset (val);
+  g_free (val);
+}
+
+GHashTable *
+ges_base_effect_get_time_property_values (GESBaseEffect * effect)
+{
+  GList *tmp;
+  GHashTable *ret =
+      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _gvalue_free);
+
+  for (tmp = effect->priv->time_properties; tmp; tmp = tmp->next) {
+    TimePropertyData *data = tmp->data;
+    GValue *value = g_new0 (GValue, 1);
+
+    /* FIXME: once we move to GLib 2.60, g_object_get_property() will
+     * automatically initialize the type */
+    g_value_init (value, data->pspec->value_type);
+    g_object_get_property (data->child, data->pspec->name, value);
+
+    g_hash_table_insert (ret, g_strdup (data->property_name), value);
+  }
+
+  return ret;
+}
+
+GstClockTime
+ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect,
+    GstClockTime time, GHashTable * time_property_values)
+{
+  GESBaseEffectPrivate *priv = effect->priv;
+
+  if (!GST_CLOCK_TIME_IS_VALID (time))
+    return GST_CLOCK_TIME_NONE;
+
+  if (priv->source_to_sink)
+    return priv->source_to_sink (effect, time, time_property_values,
+        priv->translation_data);
+
+  if (time_property_values && g_hash_table_size (time_property_values))
+    GST_ERROR_OBJECT (effect, "The time effect is missing its source to "
+        "sink translation function");
+  return time;
+}
+
+GstClockTime
+ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect,
+    GstClockTime time, GHashTable * time_property_values)
+{
+  GESBaseEffectPrivate *priv = effect->priv;
+
+  if (!GST_CLOCK_TIME_IS_VALID (time))
+    return GST_CLOCK_TIME_NONE;
+
+  if (priv->sink_to_source)
+    return effect->priv->sink_to_source (effect, time, time_property_values,
+        priv->translation_data);
+
+  if (time_property_values && g_hash_table_size (time_property_values))
+    GST_ERROR_OBJECT (effect, "The time effect is missing its sink to "
+        "source translation function");
+  return time;
+}
index 2c83214..abf3e55 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_BASE_EFFECT
-#define _GES_BASE_EFFECT
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_BASE_EFFECT ges_base_effect_get_type()
-#define GES_BASE_EFFECT(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_BASE_EFFECT, GESBaseEffect))
-#define GES_BASE_EFFECT_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_BASE_EFFECT, GESBaseEffectClass))
-#define GES_IS_BASE_EFFECT(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_BASE_EFFECT))
-#define GES_IS_BASE_EFFECT_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_BASE_EFFECT))
-#define GES_BASE_EFFECT_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_BASE_EFFECT, GESBaseEffectClass))
-
-
-typedef struct _GESBaseEffectPrivate   GESBaseEffectPrivate;
+GES_DECLARE_TYPE(BaseEffect, base_effect, BASE_EFFECT);
 
 /**
  * GESBaseEffect:
@@ -63,13 +50,45 @@ struct _GESBaseEffectClass
 {
   /*< private > */
   GESOperationClass parent_class;
+
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
 
 };
 
-GES_API
-GType ges_base_effect_get_type (void);
+/**
+ * GESBaseEffectTimeTranslationFunc:
+ * @effect: The #GESBaseEffect that is doing the time translation
+ * @time: The #GstClockTime to translation
+ * @time_property_values: (element-type gchar* GValue*): A table of child
+ * property name/value pairs
+ * @user_data: Data passed to ges_base_effect_set_time_translation_funcs()
+ *
+ * A function for querying how an effect would translate a time if it had
+ * the given child property values set. The keys for @time_properties will
+ * be the same string that was passed to
+ * ges_base_effect_register_time_property(), the values will be #GValue*
+ * values of the corresponding child properties. You should always use the
+ * values given in @time_properties before using the currently set values.
+ *
+ * Returns: The translated time.
+ * Since: 1.18
+ */
+typedef GstClockTime (*GESBaseEffectTimeTranslationFunc) (GESBaseEffect * effect,
+                                                          GstClockTime time,
+                                                          GHashTable * time_property_values,
+                                                          gpointer user_data);
+
+GES_API gboolean
+ges_base_effect_register_time_property     (GESBaseEffect * effect,
+                                            const gchar * child_property_name);
+GES_API gboolean
+ges_base_effect_set_time_translation_funcs (GESBaseEffect * effect,
+                                            GESBaseEffectTimeTranslationFunc source_to_sink_func,
+                                            GESBaseEffectTimeTranslationFunc sink_to_source_func,
+                                            gpointer user_data,
+                                            GDestroyNotify destroy);
+GES_API gboolean
+ges_base_effect_is_time_effect             (GESBaseEffect * effect);
 
 G_END_DECLS
-#endif /* _GES_BASE_EFFECT */
index 87242ab..e4f23ad 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_BASE_TRANSITION_CLIP
-#define _GES_BASE_TRANSITION_CLIP
+#pragma once
 
 #include "ges-operation-clip.h"
 
 G_BEGIN_DECLS
 
 #define GES_TYPE_BASE_TRANSITION_CLIP ges_base_transition_clip_get_type()
-
-#define GES_BASE_TRANSITION_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_BASE_TRANSITION_CLIP, GESBaseTransitionClip))
-
-#define GES_BASE_TRANSITION_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_BASE_TRANSITION_CLIP, GESBaseTransitionClipClass))
-
-#define GES_IS_BASE_TRANSITION_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_BASE_TRANSITION_CLIP))
-
-#define GES_IS_BASE_TRANSITION_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_BASE_TRANSITION_CLIP))
-
-#define GES_BASE_TRANSITION_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_BASE_TRANSITION_CLIP, GESBaseTransitionClipClass))
-
-typedef struct _GESBaseTransitionClipPrivate GESBaseTransitionClipPrivate;
+GES_DECLARE_TYPE(BaseTransitionClip, base_transition_clip, BASE_TRANSITION_CLIP);
 
 /**
  * GESBaseTransitionClip:
@@ -74,9 +57,4 @@ struct _GESBaseTransitionClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_base_transition_clip_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_BASE_TRANSITION_CLIP */
index 3058d4d..1a1f4e3 100644 (file)
@@ -30,35 +30,11 @@ GST_DEBUG_CATEGORY_STATIC (base_xml_formatter);
 
 #define parent_class ges_base_xml_formatter_parent_class
 
-#define _GET_PRIV(o)\
-  (((GESBaseXmlFormatter*) o)->priv)
+#define _GET_PRIV(o) (((GESBaseXmlFormatter*) o)->priv)
 
 
 static gboolean _loading_done_cb (GESFormatter * self);
 
-typedef struct PendingEffects
-{
-  gchar *track_id;
-  GESTrackElement *trackelement;
-  GstStructure *children_properties;
-  GstStructure *properties;
-
-} PendingEffects;
-
-typedef struct PendingBinding
-{
-  gchar *track_id;
-  GstControlSource *source;
-  gchar *propname;
-  gchar *binding_type;
-} PendingBinding;
-
-typedef struct PendingChildProperties
-{
-  gchar *track_id;
-  GstStructure *structure;
-} PendingChildProperties;
-
 typedef struct PendingGroup
 {
   GESGroup *group;
@@ -66,31 +42,6 @@ typedef struct PendingGroup
   GList *pending_children;
 } PendingGroup;
 
-typedef struct PendingClip
-{
-  gchar *id;
-  guint layer_prio;
-  GstClockTime start;
-  GstClockTime inpoint;
-  GESAsset *asset;
-  GstClockTime duration;
-  GESTrackType track_types;
-  GESLayer *layer;
-
-  GstStructure *properties;
-  GstStructure *children_properties;
-  gchar *metadatas;
-
-  GList *effects;
-
-  GList *pending_bindings;
-
-  GList *children_props;
-
-  /* TODO Implement asset effect management
-   * PendingTrackElements *track_elements; */
-} PendingClip;
-
 typedef struct LayerEntry
 {
   GESLayer *layer;
@@ -103,18 +54,27 @@ typedef struct PendingAsset
   gchar *metadatas;
   GstStructure *properties;
   gchar *proxy_id;
+  GType extractable_type;
+  gchar *id;
 } PendingAsset;
 
+/* @STATE_CHECK_LOADABLE: Quickly check if XML is valid
+ * @STATE_ASSETS: start loading all assets asynchronously
+ * and setup all elements that are synchronously loadable (tracks, and layers basically).
+ * @STATE_LOADING_CLIPS: adding clips and groups to the timeline
+ */
+typedef enum
+{
+  STATE_CHECK_LOADABLE,
+  STATE_LOADING_ASSETS_AND_SYNC,
+  STATE_LOADING_CLIPS,
+} LoadingState;
+
 struct _GESBaseXmlFormatterPrivate
 {
   GMarkupParseContext *parsecontext;
-  gboolean check_only;
-
-  /* Asset.id -> PendingClip */
-  GHashTable *assetid_pendingclips;
-
-  /* Clip.ID -> Pending */
-  GHashTable *clipid_pendings;
+  gsize xmlsize;
+  LoadingState state;
 
   /* Clip.ID -> Clip */
   GHashTable *containers;
@@ -128,19 +88,37 @@ struct _GESBaseXmlFormatterPrivate
   /* List of asset waited to be created */
   GList *pending_assets;
 
+  GError *asset_error;
+
   /* current track element */
   GESTrackElement *current_track_element;
 
   GESClip *current_clip;
-  PendingClip *current_pending_clip;
+  GstClockTime current_clip_duration;
 
   gboolean timeline_auto_transition;
 
   GList *groups;
 };
 
-static void
-_free_pending_clip (GESBaseXmlFormatterPrivate * priv, PendingClip * pend);
+static void new_asset_cb (GESAsset * source, GAsyncResult * res,
+    PendingAsset * passet);
+
+static const gchar *
+loading_state_name (LoadingState state)
+{
+  switch (state) {
+    case STATE_CHECK_LOADABLE:
+      return "check-loadable";
+    case STATE_LOADING_ASSETS_AND_SYNC:
+      return "loading-assets-and-sync";
+    case STATE_LOADING_CLIPS:
+      return "loading-clips";
+  }
+
+  return "??";
+}
+
 
 static void
 _free_layer_entry (LayerEntry * entry)
@@ -158,73 +136,74 @@ _free_pending_group (PendingGroup * pgroup)
   g_slice_free (PendingGroup, pgroup);
 }
 
-/*
-enum
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseXmlFormatter,
+    ges_base_xml_formatter, GES_TYPE_FORMATTER);
+static gint
+compare_assets_for_loading (PendingAsset * a, PendingAsset * b)
 {
-  PROP_0,
-  PROP_LAST
-};
-static GParamSpec *properties[PROP_LAST];
+  if (a->extractable_type == GES_TYPE_TIMELINE)
+    return -1;
 
-enum
-{
-  LAST_SIGNAL
-};
-static guint signals[LAST_SIGNAL];
-*/
+  if (b->extractable_type == GES_TYPE_TIMELINE)
+    return 1;
 
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseXmlFormatter,
-    ges_base_xml_formatter, GES_TYPE_FORMATTER);
+  if (a->proxy_id)
+    return -1;
+
+  if (b->proxy_id)
+    return 1;
+
+  return 0;
+}
 
 static GMarkupParseContext *
-create_parser_context (GESBaseXmlFormatter * self, const gchar * uri,
-    GError ** error)
+_parse (GESBaseXmlFormatter * self, GError ** error, LoadingState state)
 {
-  gsize xmlsize;
-  GFile *file = NULL;
-  gchar *xmlcontent = NULL;
+  GError *err = NULL;
   GMarkupParseContext *parsecontext = NULL;
   GESBaseXmlFormatterClass *self_class =
       GES_BASE_XML_FORMATTER_GET_CLASS (self);
+  GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  GError *err = NULL;
-
-  GST_DEBUG_OBJECT (self, "loading xml from %s", uri);
-
-  file = g_file_new_for_uri (uri);
-
-  /* TODO Handle GCancellable */
-  if (!g_file_query_exists (file, NULL)) {
+  if (!self->xmlcontent || g_strcmp0 (self->xmlcontent, "") == 0) {
     err = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
-        "Invalid URI: \"%s\"", uri);
-    goto failed;
-  }
-
-  if (!g_file_load_contents (file, NULL, &xmlcontent, &xmlsize, NULL, &err))
-    goto failed;
+        "Nothing contained in the project file.");
 
-  if (g_strcmp0 (xmlcontent, "") == 0)
     goto failed;
+  }
 
   parsecontext = g_markup_parse_context_new (&self_class->content_parser,
       G_MARKUP_TREAT_CDATA_AS_TEXT, self, NULL);
 
-  if (g_markup_parse_context_parse (parsecontext, xmlcontent, xmlsize,
-          &err) == FALSE)
+  priv->state = state;
+  GST_DEBUG_OBJECT (self, "Running %s pass", loading_state_name (state));
+  if (!g_markup_parse_context_parse (parsecontext, self->xmlcontent,
+          priv->xmlsize, &err))
     goto failed;
 
   if (!g_markup_parse_context_end_parse (parsecontext, &err))
     goto failed;
 
+  if (priv->pending_assets) {
+    GList *tmp;
+    priv->pending_assets = g_list_sort (priv->pending_assets,
+        (GCompareFunc) compare_assets_for_loading);
 
-done:
-  g_free (xmlcontent);
-  g_object_unref (file);
+    for (tmp = priv->pending_assets; tmp; tmp = tmp->next) {
+      PendingAsset *passet = tmp->data;
+
+      ges_asset_request_async (passet->extractable_type, passet->id, NULL,
+          (GAsyncReadyCallback) new_asset_cb, passet);
+      ges_project_add_loading_asset (GES_FORMATTER (self)->project,
+          passet->extractable_type, passet->id);
+    }
+  }
 
+done:
   return parsecontext;
 
 failed:
-  GST_WARNING ("failed to load contents from \"%s\"", uri);
+  GST_WARNING ("failed to load contents: %s", err->message);
   g_propagate_error (error, err);
 
   if (parsecontext) {
@@ -235,6 +214,41 @@ failed:
   goto done;
 }
 
+static GMarkupParseContext *
+_load_and_parse (GESBaseXmlFormatter * self, const gchar * uri, GError ** error,
+    LoadingState state)
+{
+  GFile *file = NULL;
+  GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
+
+  GError *err = NULL;
+
+  GST_DEBUG_OBJECT (self, "loading xml from %s, %s", uri,
+      loading_state_name (state));
+
+  file = g_file_new_for_uri (uri);
+  /* TODO Handle GCancellable */
+  if (!g_file_query_exists (file, NULL)) {
+    err = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
+        "Invalid URI: \"%s\"", uri);
+    goto failed;
+  }
+
+  g_clear_pointer (&self->xmlcontent, g_free);
+  if (!g_file_load_contents (file, NULL, &self->xmlcontent, &priv->xmlsize,
+          NULL, &err))
+    goto failed;
+  g_object_unref (file);
+
+  return _parse (self, error, state);
+
+failed:
+  g_object_unref (file);
+  GST_INFO_OBJECT (self, "failed to load contents from \"%s\"", uri);
+  g_propagate_error (error, err);
+  return NULL;
+}
+
 /***********************************************
  *                                             *
  * GESFormatter virtual methods implementation *
@@ -248,11 +262,7 @@ _can_load_uri (GESFormatter * dummy_formatter, const gchar * uri,
   GMarkupParseContext *ctx;
   GESBaseXmlFormatter *self = GES_BASE_XML_FORMATTER (dummy_formatter);
 
-  /* we create a temporary object so we can use it as a context */
-  _GET_PRIV (self)->check_only = TRUE;
-
-
-  ctx = create_parser_context (self, uri, error);
+  ctx = _load_and_parse (self, uri, error, STATE_CHECK_LOADABLE);
   if (!ctx)
     return FALSE;
 
@@ -266,17 +276,18 @@ _load_from_uri (GESFormatter * self, GESTimeline * timeline, const gchar * uri,
 {
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
+  GST_INFO_OBJECT (self, "Loading %s in %" GST_PTR_FORMAT, uri, timeline);
   ges_timeline_set_auto_transition (timeline, FALSE);
 
   priv->parsecontext =
-      create_parser_context (GES_BASE_XML_FORMATTER (self), uri, error);
+      _load_and_parse (GES_BASE_XML_FORMATTER (self), uri, error,
+      STATE_LOADING_ASSETS_AND_SYNC);
 
   if (!priv->parsecontext)
     return FALSE;
 
-  if (g_hash_table_size (priv->assetid_pendingclips) == 0 &&
-      priv->pending_assets == NULL)
-    g_idle_add ((GSourceFunc) _loading_done_cb, g_object_ref (self));
+  if (priv->pending_assets == NULL)
+    ges_idle_add ((GSourceFunc) _loading_done_cb, g_object_ref (self), NULL);
 
   return TRUE;
 }
@@ -362,16 +373,8 @@ static void
 _dispose (GObject * object)
 {
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (object);
-  GList *pendings, *pending_clips_lists;
-
-  pending_clips_lists = g_hash_table_get_values (priv->assetid_pendingclips);
-  for (pendings = pending_clips_lists; pendings; pendings = pendings->next)
-    g_list_free_full (pendings, (GDestroyNotify) _free_pending_clip);
-  g_list_free (pending_clips_lists);
 
-  g_clear_pointer (&priv->assetid_pendingclips, g_hash_table_unref);
   g_clear_pointer (&priv->containers, g_hash_table_unref);
-  g_clear_pointer (&priv->clipid_pendings, g_hash_table_unref);
   g_clear_pointer (&priv->tracks, g_hash_table_unref);
   g_clear_pointer (&priv->layers, g_hash_table_unref);
 
@@ -381,10 +384,12 @@ _dispose (GObject * object)
 static void
 _finalize (GObject * object)
 {
+  GESBaseXmlFormatter *self = GES_BASE_XML_FORMATTER (object);
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (object);
 
   if (priv->parsecontext != NULL)
     g_markup_parse_context_free (priv->parsecontext);
+  g_clear_pointer (&self->xmlcontent, g_free);
 
   g_list_free_full (priv->groups, (GDestroyNotify) _free_pending_group);
   priv->groups = NULL;
@@ -401,15 +406,9 @@ ges_base_xml_formatter_init (GESBaseXmlFormatter * self)
 
   priv = self->priv;
 
-  priv->check_only = FALSE;
   priv->parsecontext = NULL;
   priv->pending_assets = NULL;
 
-  /* The PendingClip are owned by the assetid_pendingclips table */
-  priv->assetid_pendingclips = g_hash_table_new_full (g_str_hash,
-      g_str_equal, g_free, NULL);
-  priv->clipid_pendings = g_hash_table_new_full (g_str_hash,
-      g_str_equal, g_free, NULL);
   priv->containers = g_hash_table_new_full (g_str_hash,
       g_str_equal, g_free, gst_object_unref);
   priv->tracks = g_hash_table_new_full (g_str_hash,
@@ -418,7 +417,7 @@ ges_base_xml_formatter_init (GESBaseXmlFormatter * self)
       g_direct_equal, NULL, (GDestroyNotify) _free_layer_entry);
   priv->current_track_element = NULL;
   priv->current_clip = NULL;
-  priv->current_pending_clip = NULL;
+  priv->current_clip_duration = GST_CLOCK_TIME_NONE;
   priv->timeline_auto_transition = FALSE;
 }
 
@@ -437,7 +436,7 @@ ges_base_xml_formatter_class_init (GESBaseXmlFormatterClass * self_class)
 
   self_class->save = NULL;
 
-  GST_DEBUG_CATEGORY_INIT (base_xml_formatter, "base-xml-formatter",
+  GST_DEBUG_CATEGORY_INIT (base_xml_formatter, "gesbasexmlformatter",
       GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "Base XML Formatter");
 }
 
@@ -483,7 +482,11 @@ _add_all_groups (GESFormatter * self)
       GST_DEBUG_OBJECT (tmp->data, "Adding %s child %" GST_PTR_FORMAT " %s",
           (const gchar *) lchild->data, child,
           GES_TIMELINE_ELEMENT_NAME (child));
-      ges_container_add (GES_CONTAINER (pgroup->group), child);
+      if (!ges_container_add (GES_CONTAINER (pgroup->group), child)) {
+        GST_ERROR ("%" GES_FORMAT " could not add child %p while"
+            " reloading, this should never happen", GES_ARGS (pgroup->group),
+            child);
+      }
     }
     pgroup->group = NULL;
   }
@@ -496,27 +499,40 @@ static void
 _loading_done (GESFormatter * self)
 {
   GList *assets, *tmp;
+  GError *error = NULL;
   GESBaseXmlFormatterPrivate *priv = GES_BASE_XML_FORMATTER (self)->priv;
 
-  _add_all_groups (self);
-
   if (priv->parsecontext)
     g_markup_parse_context_free (priv->parsecontext);
   priv->parsecontext = NULL;
-
-  ges_timeline_set_auto_transition (self->timeline,
-      priv->timeline_auto_transition);
-
   /* Go over all assets and make sure that all proxies we were 'trying' to set are finally
    * properly set */
   assets = ges_project_list_assets (self->project, GES_TYPE_EXTRACTABLE);
   for (tmp = assets; tmp; tmp = tmp->next) {
-    ges_asset_set_proxy (NULL, tmp->data);
+    ges_asset_finish_proxy (tmp->data);
   }
   g_list_free_full (assets, g_object_unref);
 
+  if (priv->asset_error) {
+    error = priv->asset_error;
+    priv->asset_error = NULL;
+  } else if (priv->state == STATE_LOADING_ASSETS_AND_SYNC) {
+    GMarkupParseContext *context =
+        _parse (GES_BASE_XML_FORMATTER (self), &error, STATE_LOADING_CLIPS);
+    GST_INFO_OBJECT (self, "Assets cached... now loading the timeline.");
+
+    if (context)
+      g_markup_parse_context_free (context);
+    g_assert (priv->pending_assets == NULL);
+  }
+
+  _add_all_groups (self);
+  ges_timeline_set_auto_transition (self->timeline,
+      priv->timeline_auto_transition);
+
   g_hash_table_foreach (priv->layers, (GHFunc) _set_auto_transition, NULL);
-  ges_project_set_loaded (self->project, self);
+  ges_project_set_loaded (self->project, self, error);
+  g_clear_error (&error);
 }
 
 static gboolean
@@ -565,14 +581,17 @@ _add_object_to_layer (GESBaseXmlFormatterPrivate * priv, const gchar * id,
     GESLayer * layer, GESAsset * asset, GstClockTime start,
     GstClockTime inpoint, GstClockTime duration,
     GESTrackType track_types, const gchar * metadatas,
-    GstStructure * properties, GstStructure * children_properties)
+    GstStructure * properties, GstStructure * children_properties,
+    GError ** error)
 {
   GESClip *clip = ges_layer_add_asset (layer,
       asset, start, inpoint, duration, track_types);
 
   if (clip == NULL) {
-    GST_WARNING_OBJECT (clip, "Could not add object from asset: %s",
-        ges_asset_get_id (asset));
+    g_set_error (error, GES_ERROR, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE,
+        "Could not add clip %s [ %" GST_TIME_FORMAT ", ( %" GST_TIME_FORMAT
+        ") - %" GST_TIME_FORMAT "]", id, GST_TIME_ARGS (start),
+        GST_TIME_ARGS (inpoint), GST_TIME_ARGS (duration));
 
     return NULL;
   }
@@ -611,67 +630,34 @@ _add_track_element (GESFormatter * self, GESClip * clip,
   GST_DEBUG_OBJECT (self, "Adding track_element: %" GST_PTR_FORMAT
       " To : %" GST_PTR_FORMAT, trackelement, clip);
 
-  ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (trackelement));
+  if (!ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (trackelement)))
+    GST_ERROR ("%" GES_FORMAT " could not add child %p while"
+        " reloading, this should never happen", GES_ARGS (clip), trackelement);
   gst_structure_foreach (children_properties,
       (GstStructureForeachFunc) _set_child_property, trackelement);
 
   if (properties) {
+    gboolean has_internal_source;
     /* We do not serialize the priority anymore, and we should never have. */
     gst_structure_remove_field (properties, "priority");
+
+    /* Ensure that has-internal-source is set before inpoint as otherwise
+     * the inpoint will be ignored */
+    if (gst_structure_get_boolean (properties, "has-internal-source",
+            &has_internal_source) && has_internal_source)
+      g_object_set (trackelement, "has-internal-source", has_internal_source,
+          NULL);
     gst_structure_foreach (properties,
         (GstStructureForeachFunc) set_property_foreach, trackelement);
   }
 }
 
 static void
-_free_pending_children_props (PendingChildProperties * pend)
-{
-  g_free (pend->track_id);
-  if (pend->structure)
-    gst_structure_free (pend->structure);
-}
-
-static void
-_free_pending_binding (PendingBinding * pend)
-{
-  g_free (pend->propname);
-  g_free (pend->binding_type);
-  g_free (pend->track_id);
-}
-
-static void
-_free_pending_effect (PendingEffects * pend)
-{
-  g_free (pend->track_id);
-  gst_object_unref (pend->trackelement);
-  if (pend->children_properties)
-    gst_structure_free (pend->children_properties);
-  if (pend->properties)
-    gst_structure_free (pend->properties);
-
-  g_slice_free (PendingEffects, pend);
-}
-
-static void
-_free_pending_clip (GESBaseXmlFormatterPrivate * priv, PendingClip * pend)
-{
-  gst_object_unref (pend->layer);
-  if (pend->properties)
-    gst_structure_free (pend->properties);
-  g_list_free_full (pend->effects, (GDestroyNotify) _free_pending_effect);
-  g_list_free_full (pend->pending_bindings,
-      (GDestroyNotify) _free_pending_binding);
-  g_list_free_full (pend->children_props,
-      (GDestroyNotify) _free_pending_children_props);
-  g_hash_table_remove (priv->clipid_pendings, pend->id);
-  g_free (pend->id);
-  g_slice_free (PendingClip, pend);
-}
-
-static void
 _free_pending_asset (GESBaseXmlFormatterPrivate * priv, PendingAsset * passet)
 {
   g_free (passet->metadatas);
+  g_free (passet->id);
   g_free (passet->proxy_id);
   if (passet->properties)
     gst_structure_free (passet->properties);
@@ -681,51 +667,18 @@ _free_pending_asset (GESBaseXmlFormatterPrivate * priv, PendingAsset * passet)
 }
 
 static void
-_add_children_properties (GESBaseXmlFormatterPrivate * priv, GList * childprops,
-    GESClip * clip)
-{
-  GList *tmpchildprops;
-
-  for (tmpchildprops = childprops; tmpchildprops;
-      tmpchildprops = tmpchildprops->next) {
-    PendingChildProperties *pchildprops = tmpchildprops->data;
-    GESTrackElement *element =
-        _get_element_by_track_id (priv, pchildprops->track_id, clip);
-    if (element && pchildprops->structure)
-      gst_structure_foreach (pchildprops->structure,
-          (GstStructureForeachFunc) _set_child_property, element);
-  }
-}
-
-static void
-_add_pending_bindings (GESBaseXmlFormatterPrivate * priv, GList * bindings,
-    GESClip * clip)
-{
-  GList *tmpbinding;
-
-  for (tmpbinding = bindings; tmpbinding; tmpbinding = tmpbinding->next) {
-    PendingBinding *pbinding = tmpbinding->data;
-    GESTrackElement *element =
-        _get_element_by_track_id (priv, pbinding->track_id, clip);
-    if (element)
-      ges_track_element_set_control_source (element,
-          pbinding->source, pbinding->propname, pbinding->binding_type);
-  }
-}
-
-static void
 new_asset_cb (GESAsset * source, GAsyncResult * res, PendingAsset * passet)
 {
   GError *error = NULL;
   gchar *possible_id = NULL;
-  GList *tmp, *pendings = NULL;
   GESFormatter *self = passet->formatter;
   const gchar *id = ges_asset_get_id (source);
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
   GESAsset *asset = ges_asset_request_finish (res, &error);
 
   if (error) {
-    GST_LOG_OBJECT (self, "Error %s creating asset id: %s", error->message, id);
+    GST_INFO_OBJECT (self, "Error %s creating asset id: %s", error->message,
+        id);
 
     /* We set the metas on the Asset to give hints to the user */
     if (passet->metadatas)
@@ -743,27 +696,18 @@ new_asset_cb (GESAsset * source, GAsyncResult * res, PendingAsset * passet)
           "- Error: %s", g_type_name (G_OBJECT_TYPE (source)), id,
           error->message);
 
-      pendings = g_hash_table_lookup (priv->assetid_pendingclips, id);
       _free_pending_asset (priv, passet);
+      if (!priv->asset_error)
+        priv->asset_error = g_error_copy (error);
       goto done;
     }
 
-    /* We got a possible ID replacement for that asset, create it, and
-     * make sure the assetid_pendingclips will use it */
+    /* We got a possible ID replacement for that asset, create it */
     ges_asset_request_async (ges_asset_get_extractable_type (source),
         possible_id, NULL, (GAsyncReadyCallback) new_asset_cb, passet);
     ges_project_add_loading_asset (GES_FORMATTER (self)->project,
         ges_asset_get_extractable_type (source), possible_id);
 
-    pendings = g_hash_table_lookup (priv->assetid_pendingclips, id);
-    if (pendings) {
-      g_hash_table_remove (priv->assetid_pendingclips, id);
-      g_hash_table_insert (priv->assetid_pendingclips,
-          g_strdup (possible_id), pendings);
-
-      /* pendings should no be freed */
-      pendings = NULL;
-    }
     goto done;
   }
 
@@ -774,36 +718,9 @@ new_asset_cb (GESAsset * source, GAsyncResult * res, PendingAsset * passet)
     ges_asset_try_proxy (asset, passet->proxy_id);
   }
 
-  /* now that we have the GESAsset, we create the GESClips */
-  pendings = g_hash_table_lookup (priv->assetid_pendingclips, id);
-  GST_DEBUG_OBJECT (self, "Asset created with ID %s, now creating pending "
-      " Clips, nb pendings: %i", id, g_list_length (pendings));
-  for (tmp = pendings; tmp; tmp = tmp->next) {
-    GList *tmpeffect;
-    GESClip *clip;
-    PendingClip *pend = (PendingClip *) tmp->data;
-
-    clip =
-        _add_object_to_layer (priv, pend->id, pend->layer, asset,
-        pend->start, pend->inpoint, pend->duration, pend->track_types,
-        pend->metadatas, pend->properties, pend->children_properties);
-
-    if (clip == NULL)
-      continue;
-
-    _add_children_properties (priv, pend->children_props, clip);
-    _add_pending_bindings (priv, pend->pending_bindings, clip);
-
-    GST_DEBUG_OBJECT (self, "Adding %i effect to new object",
-        g_list_length (pend->effects));
-    for (tmpeffect = pend->effects; tmpeffect; tmpeffect = tmpeffect->next) {
-      PendingEffects *peffect = (PendingEffects *) tmpeffect->data;
-
-      /* We keep a ref as _free_pending_effect unrefs it */
-      _add_track_element (self, clip, gst_object_ref (peffect->trackelement),
-          peffect->track_id, peffect->children_properties, peffect->properties);
-    }
-  }
+  if (passet->metadatas)
+    ges_meta_container_add_metas_from_string (GES_META_CONTAINER (asset),
+        passet->metadatas);
 
   /* And now add to the project */
   ges_project_add_asset (self->project, asset);
@@ -819,15 +736,7 @@ done:
 
   g_clear_error (&error);
 
-  if (pendings) {
-    for (tmp = pendings; tmp; tmp = tmp->next)
-      _free_pending_clip (priv, tmp->data);
-    g_hash_table_remove (priv->assetid_pendingclips, id);
-    g_list_free (pendings);
-  }
-
-  if (g_hash_table_size (priv->assetid_pendingclips) == 0 &&
-      priv->pending_assets == NULL)
+  if (priv->pending_assets == NULL)
     _loading_done (self);
 }
 
@@ -904,36 +813,9 @@ _create_profile (GESBaseXmlFormatter * self,
     gst_encoding_profile_set_description (profile, description);
     gst_encoding_profile_set_preset_name (profile, preset_name);
   }
-
-  if (preset && preset_properties) {
-    GstElement *element;
-
-    if (!g_strcmp0 (type, "container")) {
-      element = get_element_for_encoding_profile (profile,
-          GST_ELEMENT_FACTORY_TYPE_MUXER);
-    } else {
-      element = get_element_for_encoding_profile (profile,
-          GST_ELEMENT_FACTORY_TYPE_ENCODER);
-    }
-
-    if (G_UNLIKELY (!element || !GST_IS_PRESET (element))) {
-      GST_WARNING_OBJECT (element, "Element is not a GstPreset");
-      goto done;
-    }
-
-    /* If the preset doesn't exist on the system, create it */
-    if (!gst_preset_load_preset (GST_PRESET (element), preset)) {
-      gst_structure_foreach (preset_properties,
-          (GstStructureForeachFunc) set_property_foreach, element);
-
-      if (!gst_preset_save_preset (GST_PRESET (element), preset)) {
-        GST_WARNING_OBJECT (element, "Could not save preset %s", preset);
-      }
-    }
-
-  done:
-    if (element)
-      gst_object_unref (element);
+  if (preset_properties) {
+    gst_encoding_profile_set_element_properties (profile,
+        gst_structure_copy (preset_properties));
   }
 
   return profile;
@@ -953,20 +835,21 @@ ges_base_xml_formatter_add_asset (GESBaseXmlFormatter * self,
   PendingAsset *passet;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) {
+    GST_DEBUG_OBJECT (self, "Not parsing assets in %s state",
+        loading_state_name (priv->state));
+
     return;
+  }
 
   passet = g_slice_new0 (PendingAsset);
   passet->metadatas = g_strdup (metadatas);
+  passet->id = g_strdup (id);
+  passet->extractable_type = extractable_type;
   passet->proxy_id = g_strdup (proxy_id);
   passet->formatter = gst_object_ref (self);
   if (properties)
     passet->properties = gst_structure_copy (properties);
-
-  ges_asset_request_async (extractable_type, id, NULL,
-      (GAsyncReadyCallback) new_asset_cb, passet);
-  ges_project_add_loading_asset (GES_FORMATTER (self)->project,
-      extractable_type, id);
   priv->pending_assets = g_list_prepend (priv->pending_assets, passet);
 }
 
@@ -983,8 +866,11 @@ ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self,
   LayerEntry *entry;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_CLIPS) {
+    GST_DEBUG_OBJECT (self, "Not adding clip in %s state.",
+        loading_state_name (priv->state));
     return;
+  }
 
   entry = g_hash_table_lookup (priv->layers, GINT_TO_POINTER (layer_prio));
   if (entry == NULL) {
@@ -1001,58 +887,22 @@ ges_base_xml_formatter_add_clip (GESBaseXmlFormatter * self,
         "inpoint", "start", "duration", NULL);
 
   asset = ges_asset_request (type, asset_id, NULL);
-  if (asset == NULL) {
-    gchar *real_id;
-    PendingClip *pclip;
-    GList *pendings;
-
-    real_id = ges_extractable_type_check_id (type, asset_id, error);
-    if (real_id == NULL) {
-      if (*error == NULL)
-        g_set_error (error, G_MARKUP_ERROR,
-            G_MARKUP_ERROR_INVALID_CONTENT,
-            "Object type '%s' with Asset id: %s not be created'",
-            g_type_name (type), asset_id);
-
-      return;
-    }
-
-    pendings = g_hash_table_lookup (priv->assetid_pendingclips, asset_id);
-
-    pclip = g_slice_new0 (PendingClip);
-    GST_DEBUG_OBJECT (self, "Adding pending %p for %s, currently: %i",
-        pclip, asset_id, g_list_length (pendings));
-
-    pclip->id = g_strdup (id);
-    pclip->track_types = track_types;
-    pclip->duration = duration;
-    pclip->inpoint = inpoint;
-    pclip->start = start;
-    pclip->layer = gst_object_ref (entry->layer);
-
-    pclip->properties = properties ? gst_structure_copy (properties) : NULL;
-    pclip->children_properties =
-        children_properties ? gst_structure_copy (children_properties) : NULL;
-    pclip->metadatas = g_strdup (metadatas);
-
-    /* Add the new pending object to the hashtable */
-    g_hash_table_insert (priv->assetid_pendingclips, real_id,
-        g_list_append (pendings, pclip));
-    g_hash_table_insert (priv->clipid_pendings, g_strdup (id), pclip);
-
-    priv->current_clip = NULL;
-    priv->current_pending_clip = pclip;
-
+  if (!asset) {
+    g_set_error (error, GES_ERROR, GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE,
+        "Clip references asset %s of type %s which was not present in the list of ressource,"
+        " the file seems to be malformed.", asset_id, g_type_name (type));
     return;
   }
 
   nclip = _add_object_to_layer (priv, id, entry->layer,
       asset, start, inpoint, duration, track_types, metadatas, properties,
-      children_properties);
+      children_properties, error);
 
+  gst_object_unref (asset);
   if (!nclip)
     return;
 
+  priv->current_clip_duration = duration;
   priv->current_clip = nclip;
 }
 
@@ -1088,7 +938,7 @@ ges_base_xml_formatter_set_timeline_properties (GESBaseXmlFormatter * self,
 void
 ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
     GType extractable_type, guint priority, GstStructure * properties,
-    const gchar * metadatas, GError ** error)
+    const gchar * metadatas, gchar ** deactivated_tracks, GError ** error)
 {
   LayerEntry *entry;
   GESAsset *asset;
@@ -1096,8 +946,11 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
   gboolean auto_transition = FALSE;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) {
+    GST_INFO_OBJECT (self, "Not loading layer in %s state.",
+        loading_state_name (priv->state));
     return;
+  }
 
   if (extractable_type == G_TYPE_NONE)
     layer = ges_layer_new ();
@@ -1109,10 +962,11 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
             G_MARKUP_ERROR_INVALID_CONTENT,
             "Layer type %s could not be created'",
             g_type_name (extractable_type));
-        return;
       }
+      return;
     }
     layer = GES_LAYER (ges_asset_extract (asset, error));
+    gst_object_unref (asset);
   }
 
   ges_layer_set_priority (layer, priority);
@@ -1130,6 +984,27 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
     ges_meta_container_add_metas_from_string (GES_META_CONTAINER (layer),
         metadatas);
 
+  if (deactivated_tracks) {
+    gint i;
+    GList *tracks = NULL;
+
+    for (i = 0; deactivated_tracks[i] && deactivated_tracks[i][0] != '\0'; i++) {
+      GESTrack *track =
+          g_hash_table_lookup (priv->tracks, deactivated_tracks[i]);
+
+      if (!track) {
+        GST_ERROR_OBJECT (self,
+            "Unknown deactivated track: %s", deactivated_tracks[i]);
+        continue;
+      }
+
+      tracks = g_list_append (tracks, track);
+    }
+
+    ges_layer_set_active_for_tracks (layer, FALSE, tracks);
+    g_list_free (tracks);
+  }
+
   entry = g_slice_new0 (LayerEntry);
   entry->layer = gst_object_ref (layer);
   entry->auto_trans = auto_transition;
@@ -1145,7 +1020,9 @@ ges_base_xml_formatter_add_track (GESBaseXmlFormatter * self,
   GESTrack *track;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only) {
+  if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) {
+    GST_INFO_OBJECT (self, "Not loading track in %s state.",
+        loading_state_name (priv->state));
     return;
   }
 
@@ -1153,18 +1030,22 @@ ges_base_xml_formatter_add_track (GESBaseXmlFormatter * self,
   ges_timeline_add_track (GES_FORMATTER (self)->timeline, track);
 
   if (properties) {
-    gchar *restriction;
+    gchar *restriction = NULL;
     GstCaps *restriction_caps;
 
-    gst_structure_get (properties, "restriction-caps", G_TYPE_STRING,
-        &restriction, NULL);
-    gst_structure_remove_fields (properties, "restriction-caps", "caps",
-        "message-forward", NULL);
-    if (g_strcmp0 (restriction, "NULL")) {
+    if (gst_structure_get (properties, "restriction-caps", G_TYPE_STRING,
+            &restriction, NULL) && g_strcmp0 (restriction, "NULL")) {
       restriction_caps = gst_caps_from_string (restriction);
-      ges_track_set_restriction_caps (track, restriction_caps);
-      gst_caps_unref (restriction_caps);
+      if (restriction_caps) {
+        ges_track_set_restriction_caps (track, restriction_caps);
+        gst_caps_unref (restriction_caps);
+      } else {
+        GST_ERROR_OBJECT (self, "No caps read from the given track property: "
+            "restriction-caps=\"%s\"", restriction);
+      }
     }
+    gst_structure_remove_fields (properties, "restriction-caps", "caps",
+        "message-forward", NULL);
     gst_structure_foreach (properties,
         (GstStructureForeachFunc) set_property_foreach, track);
     g_free (restriction);
@@ -1185,72 +1066,64 @@ ges_base_xml_formatter_add_control_binding (GESBaseXmlFormatter * self,
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
   GESTrackElement *element = NULL;
 
-  if (track_id[0] != '-' && priv->current_clip)
-    element = _get_element_by_track_id (priv, track_id, priv->current_clip);
-
-  else if (track_id[0] != '-' && priv->current_pending_clip) {
-    PendingBinding *pbinding;
-
-    pbinding = g_slice_new0 (PendingBinding);
-    pbinding->source = gst_interpolation_control_source_new ();
-    g_object_set (pbinding->source, "mode", mode, NULL);
-    gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE
-        (pbinding->source), timed_values);
-    pbinding->propname = g_strdup (property_name);
-    pbinding->binding_type = g_strdup (binding_type);
-    pbinding->track_id = g_strdup (track_id);
-    priv->current_pending_clip->pending_bindings =
-        g_list_append (priv->current_pending_clip->pending_bindings, pbinding);
-    return;
+  if (priv->state != STATE_LOADING_CLIPS) {
+    GST_DEBUG_OBJECT (self, "Not loading control bindings in %s state.",
+        loading_state_name (priv->state));
+    goto done;
   }
 
-  else {
+  if (track_id[0] != '-' && priv->current_clip)
+    element = _get_element_by_track_id (priv, track_id, priv->current_clip);
+  else
     element = priv->current_track_element;
-  }
 
   if (element == NULL) {
     GST_WARNING ("No current track element to which we can append a binding");
-    return;
+    goto done;
   }
 
   if (!g_strcmp0 (source_type, "interpolation")) {
     GstControlSource *source;
 
     source = gst_interpolation_control_source_new ();
+
+    /* add first before setting values to avoid clamping */
     ges_track_element_set_control_source (element, source,
         property_name, binding_type);
 
     g_object_set (source, "mode", mode, NULL);
+    if (!gst_timed_value_control_source_set_from_list
+        (GST_TIMED_VALUE_CONTROL_SOURCE (source), timed_values)) {
+      GST_ERROR_OBJECT (self, "Could not set timed values on %" GES_FORMAT,
+          GES_ARGS (source));
+    }
 
-    gst_timed_value_control_source_set_from_list (GST_TIMED_VALUE_CONTROL_SOURCE
-        (source), timed_values);
+    gst_object_unref (source);
   } else
     GST_WARNING ("This interpolation type is not supported\n");
+
+done:
+  g_slist_free_full (timed_values, g_free);
 }
 
 void
 ges_base_xml_formatter_add_source (GESBaseXmlFormatter * self,
-    const gchar * track_id, GstStructure * children_properties)
+    const gchar * track_id, GstStructure * children_properties,
+    GstStructure * properties, const gchar * metadatas)
 {
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
   GESTrackElement *element = NULL;
 
+  if (priv->state != STATE_LOADING_CLIPS) {
+    GST_DEBUG_OBJECT (self, "Not loading source elements in %s state.",
+        loading_state_name (priv->state));
+    return;
+  }
+
   if (track_id[0] != '-' && priv->current_clip)
     element = _get_element_by_track_id (priv, track_id, priv->current_clip);
-
-  else if (track_id[0] != '-' && priv->current_pending_clip) {
-    PendingChildProperties *pchildprops;
-
-    pchildprops = g_slice_new0 (PendingChildProperties);
-    pchildprops->track_id = g_strdup (track_id);
-    pchildprops->structure = children_properties ?
-        gst_structure_copy (children_properties) : NULL;
-    priv->current_pending_clip->children_props =
-        g_list_append (priv->current_pending_clip->children_props, pchildprops);
-    return;
-  } else {
+  else
     element = priv->current_track_element;
-  }
 
   if (element == NULL) {
     GST_WARNING
@@ -1258,8 +1131,17 @@ ges_base_xml_formatter_add_source (GESBaseXmlFormatter * self,
     return;
   }
 
-  gst_structure_foreach (children_properties,
-      (GstStructureForeachFunc) _set_child_property, element);
+  if (properties)
+    gst_structure_foreach (properties,
+        (GstStructureForeachFunc) set_property_foreach, element);
+
+  if (children_properties)
+    gst_structure_foreach (children_properties,
+        (GstStructureForeachFunc) _set_child_property, element);
+
+  if (metadatas)
+    ges_meta_container_add_metas_from_string (GES_META_CONTAINER
+        (element), metadatas);
 }
 
 void
@@ -1274,8 +1156,11 @@ ges_base_xml_formatter_add_track_element (GESBaseXmlFormatter * self,
   GESAsset *asset = NULL;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_CLIPS) {
+    GST_DEBUG_OBJECT (self, "Not loading track elements in %s state.",
+        loading_state_name (priv->state));
     return;
+  }
 
   if (g_type_is_a (track_element_type, GES_TYPE_TRACK_ELEMENT) == FALSE) {
     GST_DEBUG_OBJECT (self, "%s is not a TrackElement, can not create it",
@@ -1306,29 +1191,8 @@ ges_base_xml_formatter_add_track_element (GESBaseXmlFormatter * self,
           (trackelement), metadatas);
 
     clip = g_hash_table_lookup (priv->containers, timeline_obj_id);
-    if (clip) {
-      _add_track_element (GES_FORMATTER (self), clip, trackelement, track_id,
-          children_properties, properties);
-    } else {
-      PendingEffects *peffect;
-      PendingClip *pend = g_hash_table_lookup (priv->clipid_pendings,
-          timeline_obj_id);
-      if (pend == NULL) {
-        GST_WARNING_OBJECT (self, "No Clip with id: %s can not "
-            "add TrackElement", timeline_obj_id);
-        goto out;
-      }
-
-      peffect = g_slice_new0 (PendingEffects);
-
-      peffect->trackelement = trackelement;
-      peffect->track_id = g_strdup (track_id);
-      peffect->properties = properties ? gst_structure_copy (properties) : NULL;
-      peffect->children_properties = children_properties ?
-          gst_structure_copy (children_properties) : NULL;
-
-      pend->effects = g_list_append (pend->effects, peffect);
-    }
+    _add_track_element (GES_FORMATTER (self), clip, trackelement, track_id,
+        children_properties, properties);
     priv->current_track_element = trackelement;
   }
 
@@ -1357,8 +1221,11 @@ ges_base_xml_formatter_add_encoding_profile (GESBaseXmlFormatter * self,
   GstEncodingContainerProfile *parent_profile = NULL;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) {
+    GST_DEBUG_OBJECT (self, "Not loading encoding profiles in %s state.",
+        loading_state_name (priv->state));
     goto done;
+  }
 
   if (parent == NULL) {
     profile =
@@ -1419,8 +1286,11 @@ ges_base_xml_formatter_add_group (GESBaseXmlFormatter * self,
   PendingGroup *pgroup;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_ASSETS_AND_SYNC) {
+    GST_DEBUG_OBJECT (self, "Not loading groups in %s state.",
+        loading_state_name (priv->state));
     return;
+  }
 
   pgroup = g_slice_new0 (PendingGroup);
   pgroup->group = ges_group_new ();
@@ -1443,8 +1313,12 @@ ges_base_xml_formatter_last_group_add_child (GESBaseXmlFormatter * self,
   PendingGroup *pgroup;
   GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (priv->check_only)
+  if (priv->state != STATE_LOADING_CLIPS) {
+    GST_DEBUG_OBJECT (self, "Not adding children to groups in %s state.",
+        loading_state_name (priv->state));
+
     return;
+  }
 
   g_return_if_fail (priv->groups);
 
@@ -1456,3 +1330,24 @@ ges_base_xml_formatter_last_group_add_child (GESBaseXmlFormatter * self,
   GST_DEBUG_OBJECT (self, "Adding %s to %s", child_id,
       GES_TIMELINE_ELEMENT_NAME (((PendingGroup *) priv->groups->data)->group));
 }
+
+void
+ges_base_xml_formatter_end_current_clip (GESBaseXmlFormatter * self)
+{
+  GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
+
+  if (priv->state != STATE_LOADING_CLIPS) {
+    GST_DEBUG_OBJECT (self, "Not ending clip in %s state.",
+        loading_state_name (priv->state));
+    return;
+  }
+
+  g_return_if_fail (priv->current_clip);
+
+  if (_DURATION (priv->current_clip) != priv->current_clip_duration)
+    _set_duration0 (GES_TIMELINE_ELEMENT (priv->current_clip),
+        priv->current_clip_duration);
+
+  priv->current_clip = NULL;
+  priv->current_clip_duration = GST_CLOCK_TIME_NONE;
+}
index fafde1a..fa543e4 100644 (file)
 
 #include "ges-formatter.h"
 
-#ifndef GES_BASE_XML_FORMATTER_H
-#define GES_BASE_XML_FORMATTER_H
+#pragma once
 
 G_BEGIN_DECLS
-#define GES_TYPE_BASE_XML_FORMATTER (ges_base_xml_formatter_get_type ())
-#define GES_BASE_XML_FORMATTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_BASE_XML_FORMATTER, GESBaseXmlFormatter))
-#define GES_BASE_XML_FORMATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_BASE_XML_FORMATTER, GESBaseXmlFormatterClass))
-#define GES_IS_BASE_XML_FORMATTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_BASE_XML_FORMATTER))
-#define GES_IS_BASE_XML_FORMATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_BASE_XML_FORMATTER))
-#define GES_BASE_XML_FORMATTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_BASE_XML_FORMATTER, GESBaseXmlFormatterClass))
-
-typedef struct _GESBaseXmlFormatterPrivate GESBaseXmlFormatterPrivate;
 typedef struct _GESBaseXmlFormatter GESBaseXmlFormatter;
 typedef struct _GESBaseXmlFormatterClass GESBaseXmlFormatterClass;
 
+#define GES_TYPE_BASE_XML_FORMATTER (ges_base_xml_formatter_get_type ())
+GES_DECLARE_TYPE(BaseXmlFormatter, base_xml_formatter, BASE_XML_FORMATTER);
+
 /**
  * GESBaseXmlFormatter:
  */
@@ -45,8 +39,9 @@ struct _GESBaseXmlFormatter
   /*< public > */
   /* <private> */
   GESBaseXmlFormatterPrivate *priv;
+  gchar *xmlcontent;
 
-  gpointer _ges_reserved[GES_PADDING];
+  gpointer _ges_reserved[GES_PADDING - 1];
 };
 
 /**
@@ -64,8 +59,4 @@ struct _GESBaseXmlFormatterClass
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_base_xml_formatter_get_type    (void);
-
 G_END_DECLS
-#endif /* _GES_BASE_XML_FORMATTER_H */
index e749c8a..38b8855 100644 (file)
@@ -31,6 +31,8 @@
 #endif
 
 #include "ges-clip-asset.h"
+#include "ges-source-clip.h"
+#include "ges-internal.h"
 
 #define GES_CLIP_ASSET_GET_PRIVATE(o)\
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), GES_TYPE_CLIP_ASSET, \
@@ -172,3 +174,65 @@ ges_clip_asset_get_supported_formats (GESClipAsset * self)
 
   return self->priv->supportedformats;
 }
+
+/**
+ * ges_clip_asset_get_natural_framerate:
+ * @self: The object from which to retrieve the natural framerate
+ * @framerate_n: The framerate numerator
+ * @framerate_d: The framerate denominator
+ *
+ * Result: %TRUE if @self has a natural framerate %FALSE otherwise
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_clip_asset_get_natural_framerate (GESClipAsset * self,
+    gint * framerate_n, gint * framerate_d)
+{
+  GESClipAssetClass *klass;
+  g_return_val_if_fail (GES_IS_CLIP_ASSET (self), FALSE);
+  g_return_val_if_fail (framerate_n && framerate_d, FALSE);
+
+  klass = GES_CLIP_ASSET_GET_CLASS (self);
+
+  *framerate_n = 0;
+  *framerate_d = -1;
+
+  if (klass->get_natural_framerate)
+    return klass->get_natural_framerate (self, framerate_n, framerate_d);
+
+  return FALSE;
+}
+
+/**
+ * ges_clip_asset_get_frame_time:
+ * @self: The object for which to compute timestamp for specifed frame
+ * @frame_number: The frame number we want the internal time coordinate timestamp of
+ *
+ * Converts the given frame number into a timestamp, using the "natural" frame
+ * rate of the asset.
+ *
+ * You can use this to reference a specific frame in a media file and use this
+ * as, for example, the `in-point` or `max-duration` of a #GESClip.
+ *
+ * Returns: The timestamp corresponding to @frame_number in the element source, given
+ * in internal time coordinates, or #GST_CLOCK_TIME_NONE if the clip asset does not have a
+ * natural frame rate.
+ *
+ * Since: 1.18
+ */
+GstClockTime
+ges_clip_asset_get_frame_time (GESClipAsset * self, GESFrameNumber frame_number)
+{
+  gint fps_n, fps_d;
+
+  g_return_val_if_fail (GES_IS_CLIP_ASSET (self), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (GES_FRAME_NUMBER_IS_VALID (frame_number),
+      GST_CLOCK_TIME_NONE);
+
+
+  if (!ges_clip_asset_get_natural_framerate (self, &fps_n, &fps_d))
+    return GST_CLOCK_TIME_NONE;
+
+  return gst_util_uint64_scale_ceil (frame_number, fps_d * GST_SECOND, fps_n);
+}
index cd241d1..bcfce5b 100644 (file)
@@ -20,8 +20,7 @@
  */
 
 
-#ifndef GES_CLIP_ASSET_H
-#define GES_CLIP_ASSET_H
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_CLIP_ASSET (ges_clip_asset_get_type ())
-#define GES_CLIP_ASSET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_CLIP_ASSET, GESClipAsset))
-#define GES_CLIP_ASSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_CLIP_ASSET, GESClipAssetClass))
-#define GES_IS_CLIP_ASSET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_CLIP_ASSET))
-#define GES_IS_CLIP_ASSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_CLIP_ASSET))
-#define GES_CLIP_ASSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_CLIP_ASSET, GESClipAssetClass))
-
-typedef struct _GESClipAssetPrivate GESClipAssetPrivate;
+GES_DECLARE_TYPE(ClipAsset, clip_asset, CLIP_ASSET);
 
 struct _GESClipAsset
 {
@@ -52,16 +45,29 @@ struct _GESClipAssetClass
 {
   GESAssetClass parent;
 
-  gpointer _ges_reserved[GES_PADDING];
+  /**
+   * GESClipAssetClass::get_natural_framerate:
+   * @self: A #GESClipAsset
+   * @framerate_n: The framerate numerator to retrieve
+   * @framerate_d: The framerate denominator to retrieve
+   *
+   * Returns: %TRUE if @self has a natural framerate @FALSE otherwise.
+   *
+   * Since: 1.18
+   */
+  gboolean (*get_natural_framerate)        (GESClipAsset *self, gint *framerate_n, gint *framerate_d);
+
+  gpointer _ges_reserved[GES_PADDING - 1];
 };
 
 GES_API
-GType ges_clip_asset_get_type (void);
-GES_API
 void ges_clip_asset_set_supported_formats         (GESClipAsset *self,
                                                               GESTrackType supportedformats);
 GES_API
 GESTrackType ges_clip_asset_get_supported_formats (GESClipAsset *self);
+GES_API
+gboolean ges_clip_asset_get_natural_framerate (GESClipAsset* self, gint* framerate_n, gint* framerate_d);
+GES_API
+GstClockTime ges_clip_asset_get_frame_time (GESClipAsset* self, GESFrameNumber frame_number);
 
 G_END_DECLS
-#endif /* _GES_CLIP_ASSET_H */
index e8b4fa5..1b297b3 100644 (file)
 /**
  * SECTION:gesclip
  * @title: GESClip
- * @short_description: Base Class for objects in a GESLayer
+ * @short_description: Base class for elements that occupy a single
+ * #GESLayer and maintain equal timings of their children
  *
- * A #GESClip is a 'natural' object which controls one or more
- * #GESTrackElement(s) in one or more #GESTrack(s).
+ * #GESClip-s are the core objects of a #GESLayer. Each clip may exist in
+ * a single layer but may control several #GESTrackElement-s that span
+ * several #GESTrack-s. A clip will ensure that all its children share the
+ * same #GESTimelineElement:start and #GESTimelineElement:duration in
+ * their tracks, which will match the #GESTimelineElement:start and
+ * #GESTimelineElement:duration of the clip itself. Therefore, changing
+ * the timing of the clip will change the timing of the children, and a
+ * change in the timing of a child will change the timing of the clip and
+ * subsequently all its siblings. As such, a clip can be treated as a
+ * singular object in its layer.
  *
- * Keeps a reference to the #GESTrackElement(s) it created and
- * sets/updates their properties.
+ * For most uses of a #GESTimeline, it is often sufficient to only
+ * interact with #GESClip-s directly, which will take care of creating and
+ * organising the elements of the timeline's tracks.
+ *
+ * ## Core Children
+ *
+ * In more detail, clips will usually have some *core* #GESTrackElement
+ * children, which are created by the clip when it is added to a layer in
+ * a timeline. The type and form of these core children will depend on the
+ * clip's subclass. You can use ges_track_element_is_core() to determine
+ * whether a track element is considered such a core track element. Note,
+ * if a core track element is part of a clip, it will always be treated as
+ * a core *child* of the clip. You can connect to the
+ * #GESContainer::child-added signal to be notified of their creation.
+ *
+ * When a child is added to a clip, the timeline will select its tracks
+ * using #GESTimeline::select-tracks-for-object. Note that it may be the
+ * case that the child will still have no set #GESTrackElement:track
+ * after this process. For example, if the timeline does not have a track
+ * of the corresponding #GESTrack:track-type. A clip can safely contain
+ * such children, which may have their track set later, although they will
+ * play no functioning role in the timeline in the meantime.
+ *
+ * If a clip may create track elements with various
+ * #GESTrackElement:track-type(s), such as a #GESUriClip, but you only
+ * want it to create a subset of these types, you should set the
+ * #GESClip:supported-formats of the clip to the subset of types. This
+ * should be done *before* adding the clip to a layer.
+ *
+ * If a clip will produce several core elements of the same
+ * #GESTrackElement:track-type, you should connect to the timeline's
+ * #GESTimeline::select-tracks-for-object signal to coordinate which
+ * tracks each element should land in. Note, no two core children within a
+ * clip can share the same #GESTrack, so you should not select the same
+ * track for two separate core children. Provided you stick to this rule,
+ * it is still safe to select several tracks for the same core child, the
+ * core child will be copied into the additional tracks. You can manually
+ * add the child to more tracks later using ges_clip_add_child_to_track().
+ * If you do not wish to use a core child, you can always select no track.
+ *
+ * The #GESTimelineElement:in-point of the clip will control the
+ * #GESTimelineElement:in-point of its core children to be the same
+ * value if their #GESTrackElement:has-internal-source is set to %TRUE.
+ *
+ * The #GESTimelineElement:max-duration of the clip is the minimum
+ * #GESTimelineElement:max-duration of its core children. If you set its
+ * value to anything other than its current value, this will also set the
+ * #GESTimelineElement:max-duration of all its core children to the same
+ * value if their #GESTrackElement:has-internal-source is set to %TRUE.
+ * As a special case, whilst a clip does not yet have any core children,
+ * its #GESTimelineElement:max-duration may be set to indicate what its
+ * value will be once they are created.
+ *
+ * ## Effects
+ *
+ * Some subclasses (#GESSourceClip and #GESBaseEffectClip) may also allow
+ * their objects to have additional non-core #GESBaseEffect-s elements as
+ * children. These are additional effects that are applied to the output
+ * data of the core elements. They can be added to the clip using
+ * ges_clip_add_top_effect(), which will take care of adding the effect to
+ * the timeline's tracks. The new effect will be placed between the clip's
+ * core track elements and its other effects. As such, the newly added
+ * effect will be applied to any source data **before** the other existing
+ * effects. You can change the ordering of effects using
+ * ges_clip_set_top_effect_index().
+ *
+ * Tracks are selected for top effects in the same way as core children.
+ * If you add a top effect to a clip before it is part of a timeline, and
+ * later add the clip to a timeline, the track selection for the top
+ * effects will occur just after the track selection for the core
+ * children. If you add a top effect to a clip that is already part of a
+ * timeline, the track selection will occur immediately. Since a top
+ * effect must be applied on top of a core child, if you use
+ * #GESTimeline::select-tracks-for-object, you should ensure that the
+ * added effects are destined for a #GESTrack that already contains a core
+ * child.
+ *
+ * In addition, if the core child in the track is not
+ * #GESTrackElement:active, then neither can any of its effects be
+ * #GESTrackElement:active. Therefore, if a core child is made in-active,
+ * all of the additional effects in the same track will also become
+ * in-active. Similarly, if an effect is set to be active, then the core
+ * child will also become active, but other effects will be left alone.
+ * Finally, if an active effect is added to the track of an in-active core
+ * child, it will become in-active as well. Note, in contrast, setting a
+ * core child to be active, or an effect to be in-active will *not* change
+ * the other children in the same track.
+ *
+ * ### Time Effects
+ *
+ * Some effects also change the timing of their data (see #GESBaseEffect
+ * for what counts as a time effect). Note that a #GESBaseEffectClip will
+ * refuse time effects, but a #GESSource will allow them.
+ *
+ * When added to a clip, time effects may adjust the timing of other
+ * children in the same track. Similarly, when changing the order of
+ * effects, making them (in)-active, setting their time property values
+ * or removing time effects. These can cause the #GESClip:duration-limit
+ * to change in value. However, if such an operation would ever cause the
+ * #GESTimelineElement:duration to shrink such that a clip's #GESSource is
+ * totally overlapped in the timeline, the operation would be prevented.
+ * Note that the same can happen when adding non-time effects with a
+ * finite #GESTimelineElement:max-duration.
+ *
+ * Therefore, when working with time effects, you should -- more so than
+ * usual -- not assume that setting the properties of the clip's children
+ * will succeed. In particular, you should use
+ * ges_timeline_element_set_child_property_full() when setting the time
+ * properties.
+ *
+ * If you wish to preserve the *internal* duration of a source in a clip
+ * during these time effect operations, you can do something like the
+ * following.
+ *
+ * ```c
+ * void
+ * do_time_effect_change (GESClip * clip)
+ * {
+ *   GList *tmp, *children;
+ *   GESTrackElement *source;
+ *   GstClockTime source_outpoint;
+ *   GstClockTime new_end;
+ *   GError *error = NULL;
+ *
+ *   // choose some active source in a track to preserve the internal
+ *   // duration of
+ *   source = ges_clip_get_track_element (clip, NULL, GES_TYPE_SOURCE);
+ *
+ *   // note its current internal end time
+ *   source_outpoint = ges_clip_get_internal_time_from_timeline_time (
+ *         clip, source, GES_TIMELINE_ELEMENT_END (clip), NULL);
+ *
+ *   // handle invalid out-point
+ *
+ *   // stop the children's control sources from clamping when their
+ *   // out-point changes with a change in the time effects
+ *   children = ges_container_get_children (GES_CONTAINER (clip), FALSE);
+ *
+ *   for (tmp = children; tmp; tmp = tmp->next)
+ *     ges_track_element_set_auto_clamp_control_source (tmp->data, FALSE);
+ *
+ *   // add time effect, or set their children properties, or move them around
+ *   ...
+ *   // user can make sure that if a time effect changes one source, we should
+ *   // also change the time effect for another source. E.g. if
+ *   // "GstVideorate::rate" is set to 2.0, we also set "GstPitch::rate" to
+ *   // 2.0
+ *
+ *   // Note the duration of the clip may have already changed if the
+ *   // duration-limit of the clip dropped below its current value
+ *
+ *   new_end = ges_clip_get_timeline_time_from_internal_time (
+ *         clip, source, source_outpoint, &error);
+ *   // handle error
+ *
+ *   if (!ges_timeline_elemnet_edit_full (GES_TIMELINE_ELEMENT (clip),
+ *         -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, new_end, &error))
+ *     // handle error
+ *
+ *   for (tmp = children; tmp; tmp = tmp->next)
+ *     ges_track_element_set_auto_clamp_control_source (tmp->data, TRUE);
+ *
+ *   g_list_free_full (children, gst_object_unref);
+ *   gst_object_unref (source);
+ * }
+ * ```
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 
 static GList *ges_clip_create_track_elements_func (GESClip * clip,
     GESTrackType type);
-static gboolean _ripple (GESTimelineElement * element, GstClockTime start);
-static gboolean _ripple_end (GESTimelineElement * element, GstClockTime end);
-static gboolean _roll_start (GESTimelineElement * element, GstClockTime start);
-static gboolean _roll_end (GESTimelineElement * element, GstClockTime end);
-static gboolean _trim (GESTimelineElement * element, GstClockTime start);
 static void _compute_height (GESContainer * container);
+static GstClockTime _convert_core_time (GESClip * clip, GstClockTime time,
+    gboolean to_timeline, gboolean * no_core, GError ** error);
 
 struct _GESClipPrivate
 {
@@ -60,708 +230,2267 @@ struct _GESClipPrivate
 
   GList *copied_track_elements;
   GESLayer *copied_layer;
+  GESTimeline *copied_timeline;
+  gboolean prevent_resort;
+
+  gboolean updating_max_duration;
+  gboolean setting_max_duration;
+  gboolean setting_inpoint;
+  gboolean setting_priority;
+  gboolean setting_active;
+
+  gboolean allow_any_track;
 
   /* The formats supported by this Clip */
   GESTrackType supportedformats;
-};
 
-typedef struct _CheckTrack
-{
-  GESTrack *track;
-  GESTrackElement *source;
-} CheckTrack;
+  GstClockTime duration_limit;
+  gboolean prevent_duration_limit_update;
+  gboolean prevent_children_outpoint_update;
+
+  gboolean allow_any_remove;
+
+  gboolean use_effect_priority;
+  guint32 effect_priority;
+  GError *add_error;
+  GError *remove_error;
+};
 
 enum
 {
   PROP_0,
   PROP_LAYER,
   PROP_SUPPORTED_FORMATS,
+  PROP_DURATION_LIMIT,
   PROP_LAST
 };
 
 static GParamSpec *properties[PROP_LAST];
 
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESClip, ges_clip, GES_TYPE_CONTAINER);
+static void
+ges_extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->asset_type = GES_TYPE_CLIP_ASSET;
+}
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip,
+    GES_TYPE_CONTAINER, G_ADD_PRIVATE (GESClip)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
+        ges_extractable_interface_init));
 
 /****************************************************
+ *                                                  *
  *              Listen to our children              *
+ *                 and restrict them                *
+ *                                                  *
  ****************************************************/
 
-/* @min_priority: The absolute minimum priority a child of @container should have
- * @max_priority: The absolute maximum priority a child of @container should have
- */
-static void
-_get_priority_range (GESContainer * container, guint32 * min_priority,
-    guint32 * max_priority)
+#define _IS_CORE_CHILD(child) \
+    ges_track_element_is_core (GES_TRACK_ELEMENT (child))
+
+#define _IS_TOP_EFFECT(child) \
+  (!_IS_CORE_CHILD (child) && GES_IS_BASE_EFFECT (child))
+
+#define _IS_CORE_INTERNAL_SOURCE_CHILD(child) \
+  (_IS_CORE_CHILD (child) \
+  && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (child)))
+
+#define _MIN_CLOCK_TIME(a, b) \
+  (GST_CLOCK_TIME_IS_VALID (a) ? \
+  (GST_CLOCK_TIME_IS_VALID (b) ? MIN (a, b) : a) : b) \
+
+/****************************************************
+ *                 duration-limit                   *
+ ****************************************************/
+
+typedef struct _DurationLimitData
 {
-  GESLayer *layer = GES_CLIP (container)->priv->layer;
+  GESTrackElement *child;
+  GESTrack *track;
+  guint32 priority;
+  GstClockTime max_duration;
+  GstClockTime inpoint;
+  gboolean active;
+  GHashTable *time_property_values;
+} DurationLimitData;
+
+static DurationLimitData *
+_duration_limit_data_new (GESTrackElement * child)
+{
+  GESTrack *track = ges_track_element_get_track (child);
+  DurationLimitData *data = g_new0 (DurationLimitData, 1);
 
-  if (layer) {
-    *min_priority = _PRIORITY (container) + layer->min_nle_priority;
-    *max_priority = layer->max_nle_priority;
-  } else {
-    *min_priority = _PRIORITY (container) + MIN_NLE_PRIO;
-    *max_priority = G_MAXUINT32;
-  }
+  data->child = gst_object_ref (child);
+  data->track = track ? gst_object_ref (track) : NULL;
+  data->inpoint = _INPOINT (child);
+  data->max_duration = _MAXDURATION (child);
+  data->priority = _PRIORITY (child);
+  data->active = ges_track_element_is_active (child);
+
+  if (GES_IS_TIME_EFFECT (child))
+    data->time_property_values =
+        ges_base_effect_get_time_property_values (GES_BASE_EFFECT (child));
+
+  return data;
 }
 
 static void
-_child_priority_changed_cb (GESTimelineElement * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
+_duration_limit_data_free (gpointer data_p)
+{
+  DurationLimitData *data = data_p;
+  gst_clear_object (&data->track);
+  gst_clear_object (&data->child);
+  if (data->time_property_values)
+    g_hash_table_unref (data->time_property_values);
+  g_free (data);
+}
+
+static GList *
+_duration_limit_data_list (GESClip * clip)
 {
-  guint32 min_prio, max_prio;
+  GList *tmp, *list = NULL;
 
-  GST_DEBUG_OBJECT (container, "TimelineElement %p priority changed to %i",
-      child, _PRIORITY (child));
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    list = g_list_prepend (list, _duration_limit_data_new (tmp->data));
 
-  if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
-    return;
+  return list;
+}
 
-  /* Update mapping */
-  _get_priority_range (container, &min_prio, &max_prio);
+/* transfer-full of data */
+static GList *
+_duration_limit_data_list_with_data (GESClip * clip, DurationLimitData * data)
+{
+  GList *tmp, *list = g_list_append (NULL, data);
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    if (data->child == child)
+      continue;
+    list = g_list_prepend (list, _duration_limit_data_new (child));
+  }
 
-  _ges_container_set_priority_offset (container, child,
-      min_prio - _PRIORITY (child));
+  return list;
 }
 
-/*****************************************************
- *                                                   *
- * GESTimelineElement virtual methods implementation *
- *                                                   *
- *****************************************************/
+static gint
+_cmp_duration_limit_data_by_track_then_priority (gconstpointer a_p,
+    gconstpointer b_p)
+{
+  const DurationLimitData *a = a_p, *b = b_p;
+  if (a->track < b->track)
+    return -1;
+  else if (a->track > b->track)
+    return 1;
+  /* if higher priority (numerically lower) place later */
+  if (a->priority < b->priority)
+    return 1;
+  else if (a->priority > b->priority)
+    return -1;
+  return 0;
+}
 
-static gboolean
-_set_start (GESTimelineElement * element, GstClockTime start)
+#define _INTERNAL_LIMIT(data) \
+  ((data->active && GST_CLOCK_TIME_IS_VALID (data->max_duration)) ? \
+    data->max_duration - data->inpoint : GST_CLOCK_TIME_NONE)
+
+static GstClockTime
+_calculate_track_duration_limit (GESClip * self, GList * start, GList * end)
 {
   GList *tmp;
-  GESContainer *container = GES_CONTAINER (element);
+  DurationLimitData *data = start->data;
+  GstClockTime track_limit;
 
-  GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %"
-      GST_PTR_FORMAT ")", container->initiated_move);
+  /* convert source-duration to timeline-duration
+   * E.g. consider the following stack
+   *
+   *       *=============================*
+   *       |           source            |
+   *       |        in-point = 5         |
+   *       |      max-duration = 20      |
+   *       *=============================*
+   *       5         10        15        20   (internal coordinates)
+   *
+   *  duration-limit = 15 because max-duration - in-point = 15
+   *
+   *       0         5         10        15
+   *       *=============================*
+   *       |         time-effect         |    | sink_to_source
+   *       |         rate = 0.5          |    v    / 0.5
+   *       *=============================*
+   *       0         10        20        30
+   *
+   *  duration-limit = 30 because rate effect can make it last longer
+   *
+   *       13        23        33    (internal coordinates)
+   *       *===================*
+   *       |effect-with-source |
+   *       |   in-point = 13   |
+   *       | max-duration = 33 |
+   *       *===================*
+   *       13        23        33    (internal coordinates)
+   *
+   *  duration-limit = 20 because effect-with-source cannot cover 30
+   *
+   *       0         10        20
+   *       *===================*
+   *       |    time-effect    |    | sink_to_source
+   *       |    rate = 2.0     |    v     / 2.0
+   *       *===================*
+   *       0         5         10
+   *
+   *  duration-limit = 10 because rate effect uses up twice as much
+   *
+   * -----------------------------------------------timeline
+   */
 
-  element->start = start;
-  g_object_notify (G_OBJECT (element), "start");
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
+  while (!_IS_CORE_CHILD (data->child)) {
+    GST_WARNING_OBJECT (self, "Child %" GES_FORMAT " has a lower "
+        "priority than the core child in the same track. Ignoring.",
+        GES_ARGS (data->child));
 
-    _set_start0 (GES_TIMELINE_ELEMENT (child), start);
+    start = start->next;
+    if (start == end) {
+      GST_ERROR_OBJECT (self, "Track %" GST_PTR_FORMAT " is missing a "
+          "core child", data->track);
+      return GST_CLOCK_TIME_NONE;
+    }
+    data = start->data;
   }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
 
-  return FALSE;
+  track_limit = _INTERNAL_LIMIT (data);
+
+  for (tmp = start->next; tmp != end; tmp = tmp->next) {
+    data = tmp->data;
+
+    if (GES_IS_TIME_EFFECT (data->child)) {
+      GESBaseEffect *effect = GES_BASE_EFFECT (data->child);
+      if (data->inpoint)
+        GST_ERROR_OBJECT (self, "Did not expect an in-point to be set "
+            "for the time effect %" GES_FORMAT, GES_ARGS (effect));
+      if (GST_CLOCK_TIME_IS_VALID (data->max_duration))
+        GST_ERROR_OBJECT (self, "Did not expect a max-duration to be set "
+            "for the time effect %" GES_FORMAT, GES_ARGS (effect));
+
+      if (data->active) {
+        /* for the time effect, the minimum time it will receive is 0
+         * (it should map 0 -> 0), and the maximum time will be track_limit */
+        track_limit = ges_base_effect_translate_sink_to_source_time (effect,
+            track_limit, data->time_property_values);
+      }
+    } else {
+      GstClockTime el_limit = _INTERNAL_LIMIT (data);
+      track_limit = _MIN_CLOCK_TIME (track_limit, el_limit);
+    }
+  }
+
+  GST_LOG_OBJECT (self, "Track duration-limit for track %" GST_PTR_FORMAT
+      " is %" GST_TIME_FORMAT, data->track, GST_TIME_ARGS (track_limit));
+
+  return track_limit;
 }
 
-static gboolean
-_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
+/* transfer-full of child_data */
+static GstClockTime
+_calculate_duration_limit (GESClip * self, GList * child_data)
 {
-  GList *tmp;
-  GESContainer *container = GES_CONTAINER (element);
+  GstClockTime limit = GST_CLOCK_TIME_NONE;
+  GList *start, *end;
 
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
+  child_data = g_list_sort (child_data,
+      _cmp_duration_limit_data_by_track_then_priority);
 
-    if (child != container->initiated_move) {
-      _set_inpoint0 (child, inpoint);
+  start = child_data;
+
+  while (start) {
+    /* we have the first element in the track, of the lowest priority, and
+     * work our way up from here */
+    GESTrack *track = ((DurationLimitData *) (start->data))->track;
+
+    end = start;
+    do {
+      end = end->next;
+    } while (end && ((DurationLimitData *) (end->data))->track == track);
+
+    if (track) {
+      GstClockTime track_limit =
+          _calculate_track_duration_limit (self, start, end);
+      limit = _MIN_CLOCK_TIME (limit, track_limit);
     }
+    start = end;
   }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
+  GST_LOG_OBJECT (self, "calculated duration-limit for the clip is %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (limit));
 
-  return TRUE;
+  g_list_free_full (child_data, _duration_limit_data_free);
+
+  return limit;
 }
 
-static gboolean
-_set_duration (GESTimelineElement * element, GstClockTime duration)
+static void
+_update_children_outpoints (GESClip * self)
 {
   GList *tmp;
 
-  GESContainer *container = GES_CONTAINER (element);
-
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
+  if (self->priv->prevent_children_outpoint_update)
+    return;
 
-    if (child != container->initiated_move)
-      _set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
+  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
+    ges_track_element_update_outpoint (tmp->data);
   }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
-
-  return TRUE;
 }
 
-static gboolean
-_set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
+static void
+_update_duration_limit (GESClip * self)
 {
-  GList *tmp;
+  GstClockTime duration_limit;
 
-  for (tmp = GES_CONTAINER (element)->children; tmp; tmp = g_list_next (tmp))
-    ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (tmp->data),
-        maxduration);
+  if (self->priv->prevent_duration_limit_update)
+    return;
 
-  return TRUE;
+  duration_limit = _calculate_duration_limit (self,
+      _duration_limit_data_list (self));
+
+  if (duration_limit != self->priv->duration_limit) {
+    GESTimelineElement *element = GES_TIMELINE_ELEMENT (self);
+
+    self->priv->duration_limit = duration_limit;
+    GST_INFO_OBJECT (self, "duration-limit for the clip is %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (duration_limit));
+
+    if (GES_CLOCK_TIME_IS_LESS (duration_limit, element->duration)
+        && !GES_TIMELINE_ELEMENT_BEING_EDITED (self)) {
+      gboolean res;
+
+      GST_INFO_OBJECT (self, "Automatically reducing duration to %"
+          GST_TIME_FORMAT " to match the new duration-limit because "
+          "the current duration %" GST_TIME_FORMAT " exceeds it",
+          GST_TIME_ARGS (duration_limit), GST_TIME_ARGS (element->duration));
+
+      /* trim end with no snapping */
+      if (element->timeline)
+        res = timeline_tree_trim (timeline_get_tree (element->timeline),
+            element, 0, GST_CLOCK_DIFF (duration_limit, element->duration),
+            GES_EDGE_END, 0, NULL);
+      else
+        res = ges_timeline_element_set_duration (element, duration_limit);
+
+      if (!res)
+        GST_ERROR_OBJECT (self, "Could not reduce the duration of the "
+            "clip to below its duration-limit of %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (duration_limit));
+    }
+    /* notify after the auto-change in duration to allow the user to set
+     * the duration in response to the change in their callbacks */
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION_LIMIT]);
+  }
 }
 
+/* transfer full of child_data */
 static gboolean
-_set_priority (GESTimelineElement * element, guint32 priority)
+_can_update_duration_limit (GESClip * self, GList * child_data, GError ** error)
 {
-  GList *tmp;
-  guint32 min_prio, max_prio;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
+  GstClockTime duration = _calculate_duration_limit (self, child_data);
+  GESTimelineElement *element = GES_TIMELINE_ELEMENT (self);
+
+  if (GES_CLOCK_TIME_IS_LESS (duration, element->duration)) {
+    /* NOTE: timeline would normally not be NULL at this point */
+    if (timeline
+        && !timeline_tree_can_move_element (timeline_get_tree (timeline),
+            element, ges_timeline_element_get_layer_priority (element),
+            element->start, duration, error)) {
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
 
-  GESContainer *container = GES_CONTAINER (element);
+/****************************************************
+ *                    priority                      *
+ ****************************************************/
 
-  _get_priority_range (container, &min_prio, &max_prio);
+/* @min_priority: The absolute minimum priority a child of @container should have
+ * @max_priority: The absolute maximum priority a child of @container should have
+ */
+static void
+_get_priority_range_full (GESContainer * container, guint32 * min_priority,
+    guint32 * max_priority, guint32 priority_base)
+{
+  GESLayer *layer = GES_CLIP (container)->priv->layer;
 
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
-    guint32 track_element_prio;
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
-    gint off = _ges_container_get_priority_offset (container, child);
+  if (layer) {
+    *min_priority = priority_base + layer->min_nle_priority;
+    *max_priority = layer->max_nle_priority;
+  } else {
+    *min_priority = priority_base + MIN_NLE_PRIO;
+    *max_priority = G_MAXUINT32;
+  }
+}
+
+static void
+_get_priority_range (GESContainer * container, guint32 * min_priority,
+    guint32 * max_priority)
+{
+  _get_priority_range_full (container, min_priority, max_priority,
+      _PRIORITY (container));
+}
 
+gboolean
+ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child,
+    guint32 priority, GError ** error)
+{
+  GList *child_data;
+  DurationLimitData *data;
 
-    if (off >= LAYER_HEIGHT) {
-      GST_ERROR ("%s child %s as a priority offset %d >= LAYER_HEIGHT %d"
-          " ==> clamping it to 0", GES_TIMELINE_ELEMENT_NAME (element),
-          GES_TIMELINE_ELEMENT_NAME (child), off, LAYER_HEIGHT);
-      off = 0;
-    }
+  if (clip->priv->setting_priority)
+    return TRUE;
 
-    /* We need to remove our current priority from @min_prio
-     * as it is the absolute minimum priority @child could have
-     * before we set @container to @priority.
-     */
-    track_element_prio = min_prio - _PRIORITY (container) + priority - off;
+  data = _duration_limit_data_new (child);
+  data->priority = priority;
 
-    if (track_element_prio > max_prio) {
-      GST_WARNING ("%p priority of %i, is outside of the its containing "
-          "layer space. (%d/%d) setting it to the maximum it can be",
-          container, priority, min_prio - _PRIORITY (container) + priority,
-          max_prio);
+  child_data = _duration_limit_data_list_with_data (clip, data);
 
-      track_element_prio = max_prio;
-    }
-    _set_priority0 (child, track_element_prio);
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from "
+        "priority %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " because "
+        "the duration-limit cannot be adjusted", GES_ARGS (child),
+        _PRIORITY (child), priority);
+    return FALSE;
   }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
-  _compute_height (container);
 
   return TRUE;
 }
 
-static guint32
-_get_layer_priority (GESTimelineElement * element)
+static void
+_child_priority_changed (GESContainer * container, GESTimelineElement * child)
 {
-  GESClip *clip = GES_CLIP (element);
-
-  if (clip->priv->layer == NULL)
-    return GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY;
-
-  return ges_layer_get_priority (clip->priv->layer);
+  /* we do not change the rest of the clip in response to a change in
+   * the child priority */
+  GST_DEBUG_OBJECT (container, "TimelineElement %" GES_FORMAT
+      " priority changed to %u", GES_ARGS (child), _PRIORITY (child));
+
+  if (!(GES_CLIP (container))->priv->prevent_resort) {
+    _ges_container_sort_children (container);
+    _compute_height (container);
+  }
 }
 
 /****************************************************
- *                                                  *
- *  GESContainer virtual methods implementation     *
- *                                                  *
+ *                    in-point                      *
  ****************************************************/
 
-static void
-_compute_height (GESContainer * container)
+GstClockTime
+ges_clip_duration_limit_with_new_children_inpoints (GESClip * clip,
+    GHashTable * child_inpoints)
 {
-  GList *tmp;
-  guint32 min_prio = G_MAXUINT32, max_prio = 0;
-
-  if (container->children == NULL) {
-    /* FIXME Why not 0! */
-    _ges_container_set_height (container, 1);
-    return;
-  }
-
-  /* Go over all childs and check if height has changed */
-  for (tmp = container->children; tmp; tmp = tmp->next) {
-    guint tck_priority = _PRIORITY (tmp->data);
-
-    if (tck_priority < min_prio)
-      min_prio = tck_priority;
-    if (tck_priority > max_prio)
-      max_prio = tck_priority;
+  GHashTableIter iter;
+  gpointer key, value;
+  GList *child_data = NULL;
+
+  g_hash_table_iter_init (&iter, child_inpoints);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    GESTrackElement *child = key;
+    GstClockTime *inpoint_p = value;
+    DurationLimitData *data = _duration_limit_data_new (child);
+    data->inpoint = *inpoint_p;
+    child_data = g_list_prepend (child_data, data);
   }
 
-  _ges_container_set_height (container, max_prio - min_prio + 1);
+  return _calculate_duration_limit (clip, child_data);
 }
 
 static gboolean
-_add_child (GESContainer * container, GESTimelineElement * element)
+_can_set_inpoint_of_core_children (GESClip * clip, GstClockTime inpoint,
+    GError ** error)
 {
   GList *tmp;
-  guint max_prio, min_prio;
-  GESClipPrivate *priv = GES_CLIP (container)->priv;
+  GList *child_data = NULL;
 
-  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
+  if (GES_TIMELINE_ELEMENT_BEING_EDITED (clip))
+    return TRUE;
 
-  /* First make sure we work with a sorted list of GESTimelineElement-s */
-  _ges_container_sort_children (container);
+  /* setting the in-point of a core child will shift the in-point of all
+   * core children with an internal source */
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+    DurationLimitData *data =
+        _duration_limit_data_new (GES_TRACK_ELEMENT (child));
+
+    if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) {
+      if (GES_CLOCK_TIME_IS_LESS (child->maxduration, inpoint)) {
+        GST_INFO_OBJECT (clip, "Cannot set the in-point from %"
+            GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because it would "
+            "cause the in-point of its core child %" GES_FORMAT
+            " to exceed its max-duration", GST_TIME_ARGS (_INPOINT (clip)),
+            GST_TIME_ARGS (inpoint), GES_ARGS (child));
+        g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
+            "Cannot set the in-point of \"%s\" to %" GST_TIME_FORMAT
+            " because it would exceed the max-duration of %" GST_TIME_FORMAT
+            " for the child \"%s\"", GES_TIMELINE_ELEMENT_NAME (clip),
+            GST_TIME_ARGS (inpoint), GST_TIME_ARGS (child->maxduration),
+            child->name);
+
+        _duration_limit_data_free (data);
+        g_list_free_full (child_data, _duration_limit_data_free);
+        return FALSE;
+      }
 
-  /* If the TrackElement is an effect:
-   *  - We add it on top of the list of TrackEffect
-   *  - We put all TrackElements present in the Clip
-   *    which are not BaseEffect on top of them
-   * FIXME: Let the full control over priorities to the user
-   */
-  _get_priority_range (container, &min_prio, &max_prio);
-  if (GES_IS_BASE_EFFECT (element)) {
-    GESChildrenControlMode mode = container->children_control_mode;
-
-    GST_DEBUG_OBJECT (container, "Adding %ith effect: %" GST_PTR_FORMAT
-        " Priority %i", priv->nb_effects + 1, element,
-        min_prio + priv->nb_effects);
-
-    tmp = g_list_nth (GES_CONTAINER_CHILDREN (container), priv->nb_effects);
-    container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
-    for (; tmp; tmp = tmp->next) {
-      ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
-          GES_TIMELINE_ELEMENT_PRIORITY (tmp->data) + 1);
+      data->inpoint = inpoint;
     }
 
-    _set_priority0 (element, min_prio + priv->nb_effects);
-    container->children_control_mode = mode;
-    priv->nb_effects++;
-  } else {
-    /* We add the track element on top of the effect list */
-    _set_priority0 (element, min_prio + priv->nb_effects);
+    child_data = g_list_prepend (child_data, data);
   }
 
-  /* We set the timing value of the child to ours, we avoid infinite loop
-   * making sure the container ignore notifies from the child */
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  _set_start0 (element, GES_TIMELINE_ELEMENT_START (container));
-  _set_inpoint0 (element, GES_TIMELINE_ELEMENT_INPOINT (container));
-  _set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (container));
-  container->children_control_mode = GES_CHILDREN_UPDATE;
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot set the in-point from %" GST_TIME_FORMAT
+        " to %" GST_TIME_FORMAT " because the duration-limit cannot be "
+        "adjusted", GST_TIME_ARGS (_INPOINT (clip)), GST_TIME_ARGS (inpoint));
+    return FALSE;
+  }
 
   return TRUE;
 }
 
-static gboolean
-_remove_child (GESContainer * container, GESTimelineElement * element)
+/* Whether @clip can have its in-point set to @inpoint because none of
+ * its children have a max-duration below it */
+gboolean
+ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child,
+    GstClockTime inpoint, GError ** error)
 {
-  if (GES_IS_BASE_EFFECT (element)) {
-    GES_CLIP (container)->priv->nb_effects--;
-  }
+  /* don't bother checking if we are setting the value */
+  if (clip->priv->setting_inpoint)
+    return TRUE;
 
-  GST_FIXME_OBJECT (container, "We should set other children prios");
+  if (GES_TIMELINE_ELEMENT_BEING_EDITED (child))
+    return TRUE;
 
-  return TRUE;
-}
+  if (!_IS_CORE_CHILD (child)) {
+    /* no other sibling will move */
+    GList *child_data;
+    DurationLimitData *data = _duration_limit_data_new (child);
+    data->inpoint = inpoint;
 
-static void
-_child_added (GESContainer * container, GESTimelineElement * element)
-{
-  g_signal_connect (G_OBJECT (element), "notify::priority",
-      G_CALLBACK (_child_priority_changed_cb), container);
+    child_data = _duration_limit_data_list_with_data (clip, data);
+
+    if (!_can_update_duration_limit (clip, child_data, error)) {
+      GST_INFO_OBJECT (clip, "Cannot set the in-point of non-core child %"
+          GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
+          " because the duration-limit cannot be adjusted", GES_ARGS (child),
+          GST_TIME_ARGS (_INPOINT (child)), GST_TIME_ARGS (inpoint));
+      return FALSE;
+    }
 
-  _child_priority_changed_cb (element, NULL, container);
-  _compute_height (container);
+    return TRUE;
+  }
+
+  /* setting the in-point of a core child will shift the in-point of all
+   * core children with an internal source */
+  return _can_set_inpoint_of_core_children (clip, inpoint, error);
 }
 
-static void
-_child_removed (GESContainer * container, GESTimelineElement * element)
+/* returns TRUE if duration-limit needs to be updated */
+static gboolean
+_child_inpoint_changed (GESClip * self, GESTimelineElement * child)
 {
-  g_signal_handlers_disconnect_by_func (element, _child_priority_changed_cb,
-      container);
-
-  if (GES_IS_BASE_EFFECT (element)) {
-    GList *tmp;
-    guint32 priority;
-    GESChildrenControlMode mode = container->children_control_mode;
-
-    GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
+  if (self->priv->setting_inpoint)
+    return FALSE;
 
-    container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
-    tmp = GES_CONTAINER_CHILDREN (container);
-    priority =
-        ges_timeline_element_get_priority (GES_TIMELINE_ELEMENT (element));
-    for (; tmp; tmp = tmp->next) {
-      if (ges_timeline_element_get_priority (GES_TIMELINE_ELEMENT (tmp->data)) >
-          priority) {
-        ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
-            GES_TIMELINE_ELEMENT_PRIORITY (tmp->data) - 1);
-      }
-    }
+  /* if we have a non-core child, then we do not need the in-point of the
+   * clip to change. Similarly, if the track element is core but has no
+   * internal content, then this means its in-point has been set (back) to
+   * 0, which means we do not need to update the in-point of the clip. */
+  if (!_IS_CORE_INTERNAL_SOURCE_CHILD (child))
+    return TRUE;
 
-    container->children_control_mode = mode;
-  }
+  /* if setting the in-point of the clip, this will handle the change in
+   * the duration-limit */
 
-  _compute_height (container);
+  /* If the child->inpoint is the same as our own, set_inpoint will do
+   * nothing. For example, when we set them in add_child (the notifies for
+   * this are released after child_added is called because
+   * ges_container_add freezes them) */
+  _set_inpoint0 (GES_TIMELINE_ELEMENT (self), child->inpoint);
+  return FALSE;
 }
 
+/****************************************************
+ *                  max-duration                    *
+ ****************************************************/
+
 static void
-add_clip_to_list (gpointer key, gpointer clip, GList ** list)
+_update_max_duration (GESContainer * container)
 {
-  *list = g_list_prepend (*list, gst_object_ref (clip));
+  GList *tmp;
+  GstClockTime min = GST_CLOCK_TIME_NONE;
+  GESClipPrivate *priv = GES_CLIP (container)->priv;
+
+  if (priv->setting_max_duration)
+    return;
+
+  for (tmp = container->children; tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+    if (_IS_CORE_CHILD (child))
+      min = _MIN_CLOCK_TIME (min, child->maxduration);
+  }
+  priv->updating_max_duration = TRUE;
+  ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (container), min);
+  priv->updating_max_duration = FALSE;
 }
 
-static GList *
-_ungroup (GESContainer * container, gboolean recursive)
+gboolean
+ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child,
+    GstClockTime max_duration, GError ** error)
 {
-  GESClip *tmpclip;
-  GESTrackType track_type;
-  GESTrackElement *track_element;
+  GList *child_data;
+  DurationLimitData *data;
 
-  gboolean first_obj = TRUE;
-  GList *tmp, *children, *ret = NULL;
-  GESClip *clip = GES_CLIP (container);
-  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
-  GESLayer *layer = clip->priv->layer;
-  GHashTable *_tracktype_clip = g_hash_table_new (g_int_hash, g_int_equal);
+  if (clip->priv->setting_max_duration)
+    return TRUE;
 
-  /* If there is no TrackElement, just return @container in a list */
-  if (GES_CONTAINER_CHILDREN (container) == NULL) {
-    GST_DEBUG ("No TrackElement, simply returning");
-    return g_list_prepend (ret, container);
+  data = _duration_limit_data_new (child);
+  data->max_duration = max_duration;
+
+  child_data = _duration_limit_data_list_with_data (clip, data);
+
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot set the max-duration of child %"
+        GES_FORMAT " from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
+        " because the duration-limit cannot be adjusted", GES_ARGS (child),
+        GST_TIME_ARGS (_MAXDURATION (child)), GST_TIME_ARGS (max_duration));
+    return FALSE;
   }
 
-  /* We need a copy of the current list of tracks */
-  children = ges_container_get_children (container, FALSE);
-  for (tmp = children; tmp; tmp = tmp->next) {
-    track_element = GES_TRACK_ELEMENT (tmp->data);
-    track_type = ges_track_element_get_track_type (track_element);
+  return TRUE;
+}
 
-    tmpclip = g_hash_table_lookup (_tracktype_clip, &track_type);
-    if (tmpclip == NULL) {
-      if (G_UNLIKELY (first_obj == TRUE)) {
-        tmpclip = clip;
-        first_obj = FALSE;
-      } else {
-        tmpclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
-        if (layer) {
-          /* Add new container to the same layer as @container */
-          ges_clip_set_moving_from_layer (tmpclip, TRUE);
-          ges_layer_add_clip (layer, tmpclip);
-          ges_clip_set_moving_from_layer (tmpclip, FALSE);
-        }
-      }
+gboolean
+ges_clip_can_set_max_duration_of_all_core (GESClip * clip,
+    GstClockTime max_duration, GError ** error)
+{
+  GList *tmp;
+  GList *child_data = NULL;
 
-      g_hash_table_insert (_tracktype_clip, &track_type, tmpclip);
-      ges_clip_set_supported_formats (tmpclip, track_type);
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+    DurationLimitData *data =
+        _duration_limit_data_new (GES_TRACK_ELEMENT (child));
+
+    if (_IS_CORE_CHILD (child)) {
+      /* don't check that it has an internal-source, since we are assuming
+       * we will have one if the max-duration is valid */
+      if (GES_CLOCK_TIME_IS_LESS (max_duration, child->inpoint)) {
+        GST_INFO_OBJECT (clip, "Cannot set the max-duration from %"
+            GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because it would "
+            "cause the in-point of its core child %" GES_FORMAT
+            " to exceed its max-duration",
+            GST_TIME_ARGS (child->maxduration),
+            GST_TIME_ARGS (max_duration), GES_ARGS (child));
+        g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
+            "Cannot set the max-duration of the child \"%s\" under the "
+            "clip \"%s\" to %" GST_TIME_FORMAT " because it would be "
+            "below the in-point of %" GST_TIME_FORMAT " of the child",
+            child->name, GES_TIMELINE_ELEMENT_NAME (clip),
+            GST_TIME_ARGS (max_duration), GST_TIME_ARGS (child->inpoint));
+
+        _duration_limit_data_free (data);
+        g_list_free_full (child_data, _duration_limit_data_free);
+        return FALSE;
+      }
+      data->max_duration = max_duration;
     }
 
-    /* Move trackelement to the container it is supposed to land into */
-    if (tmpclip != clip) {
-      /* We need to bump the refcount to avoid the object to be destroyed */
-      gst_object_ref (track_element);
-      ges_container_remove (container, GES_TIMELINE_ELEMENT (track_element));
-      ges_container_add (GES_CONTAINER (tmpclip),
-          GES_TIMELINE_ELEMENT (track_element));
-      gst_object_unref (track_element);
-    }
+    child_data = g_list_prepend (child_data, data);
   }
-  g_list_free_full (children, gst_object_unref);
-  g_hash_table_foreach (_tracktype_clip, (GHFunc) add_clip_to_list, &ret);
-  g_hash_table_unref (_tracktype_clip);
 
-  return ret;
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot set the max-duration of the core "
+        "children to %" GST_TIME_FORMAT " because the duration-limit "
+        "cannot be adjusted", GST_TIME_ARGS (max_duration));
+    return FALSE;
+  }
+
+  return TRUE;
 }
 
-static GESContainer *
-_group (GList * containers)
+static void
+_child_max_duration_changed (GESContainer * container,
+    GESTimelineElement * child)
 {
-  CheckTrack *tracks = NULL;
-  GESTimeline *timeline = NULL;
-  GESTrackType supported_formats;
-  GESLayer *layer = NULL;
-  GList *tmp, *tmpclip, *tmpelement;
-  GstClockTime start, inpoint, duration;
+  /* ignore non-core */
+  if (!_IS_CORE_CHILD (child))
+    return;
 
-  GESAsset *asset = NULL;
-  GESContainer *ret = NULL;
-  guint nb_tracks = 0, i = 0;
+  _update_max_duration (container);
+}
 
-  start = inpoint = duration = GST_CLOCK_TIME_NONE;
+/****************************************************
+ *               has-internal-source                *
+ ****************************************************/
 
-  if (!containers)
-    return NULL;
+static void
+_child_has_internal_source_changed (GESClip * self, GESTimelineElement * child)
+{
+  /* ignore non-core */
+  /* if the track element is now registered to have no internal content,
+   * we don't have to do anything
+   * Note that the change in max-duration and in-point will already trigger
+   * a change in the duration-limit, which can only increase since the
+   * max-duration is now GST_CLOCK_TIME_NONE */
+  if (!_IS_CORE_INTERNAL_SOURCE_CHILD (child))
+    return;
 
-  /* First check if all the containers are clips, if they
-   * all have the same start/inpoint/duration and are in the same
-   * layer.
-   *
-   * We also need to make sure that all source have been created by the
-   * same asset, keep the information */
-  for (tmp = containers; tmp; tmp = tmp->next) {
-    GESClip *clip;
-    GESTimeline *tmptimeline;
-    GESContainer *tmpcontainer;
-    GESTimelineElement *element;
+  /* otherwise, we need to make its in-point match ours
+   * Note that the duration-limit will be GST_CLOCK_TIME_NONE, so this
+   * should not change the duration-limit */
+  _set_inpoint0 (child, _INPOINT (self));
+}
 
-    tmpcontainer = GES_CONTAINER (tmp->data);
-    element = GES_TIMELINE_ELEMENT (tmp->data);
-    if (GES_IS_CLIP (element) == FALSE) {
-      GST_DEBUG ("Can only work with clips");
-      goto done;
-    }
-    clip = GES_CLIP (tmp->data);
-    tmptimeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
-    if (!timeline) {
-      GList *tmptrack;
-
-      start = _START (tmpcontainer);
-      inpoint = _INPOINT (tmpcontainer);
-      duration = _DURATION (tmpcontainer);
-      timeline = tmptimeline;
-      layer = clip->priv->layer;
-      nb_tracks = g_list_length (GES_TIMELINE_GET_TRACKS (timeline));
-      tracks = g_new0 (CheckTrack, nb_tracks);
-
-      for (tmptrack = GES_TIMELINE_GET_TRACKS (timeline); tmptrack;
-          tmptrack = tmptrack->next) {
-        tracks[i].track = tmptrack->data;
-        i++;
-      }
-    } else {
-      if (start != _START (tmpcontainer) ||
-          inpoint != _INPOINT (tmpcontainer) ||
-          duration != _DURATION (tmpcontainer) || clip->priv->layer != layer) {
-        GST_INFO ("All children must have the same start, inpoint, duration "
-            " and be in the same layer");
+/****************************************************
+ *                     active                       *
+ ****************************************************/
 
-        goto done;
-      } else {
-        GList *tmp2;
-
-        for (tmp2 = GES_CONTAINER_CHILDREN (tmp->data); tmp2; tmp2 = tmp2->next) {
-          GESTrackElement *track_element = GES_TRACK_ELEMENT (tmp2->data);
-
-          if (GES_IS_SOURCE (track_element)) {
-            guint i;
-
-            for (i = 0; i < nb_tracks; i++) {
-              if (tracks[i].track ==
-                  ges_track_element_get_track (track_element)) {
-                if (tracks[i].source) {
-                  GST_INFO ("Can not link clips with various source for a "
-                      "same track");
-
-                  goto done;
-                }
-                tracks[i].source = track_element;
-                break;
-              }
-            }
-          }
-        }
-      }
-    }
-  }
+gboolean
+ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child,
+    gboolean active, GError ** error)
+{
+  GESTrack *track = ges_track_element_get_track (child);
+  gboolean is_core = _IS_CORE_CHILD (child);
+  GList *child_data = NULL;
+  DurationLimitData *data;
 
+  if (clip->priv->setting_active)
+    return TRUE;
 
-  /* Then check that all sources have been created by the same asset,
-   * otherwise we can not group */
-  for (i = 0; i < nb_tracks; i++) {
-    if (tracks[i].source == NULL) {
-      GST_FIXME ("Check what to do here as we might end up having a mess");
+  /* We want to ensure that each active non-core element has a
+   * corresponding active core element in the same track */
+  if (!track || is_core == active) {
+    /* only the one child will change */
+    data = _duration_limit_data_new (child);
+    data->active = active;
+    child_data = _duration_limit_data_list_with_data (clip, data);
+  } else {
+    GList *tmp;
+    /* If we are core, make all the non-core elements in-active
+     * If we are non-core, make the core element active */
+    for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+      GESTrackElement *sibling = tmp->data;
+      data = _duration_limit_data_new (sibling);
 
-      continue;
-    }
+      if (sibling == child)
+        data->active = active;
 
-    /* FIXME Check what to do if we have source that have no assets */
-    if (!asset) {
-      asset =
-          ges_extractable_get_asset (GES_EXTRACTABLE
-          (ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (tracks
-                      [i].source))));
-      continue;
-    }
-    if (asset !=
-        ges_extractable_get_asset (GES_EXTRACTABLE
-            (ges_timeline_element_get_parent (GES_TIMELINE_ELEMENT (tracks
-                        [i].source))))) {
-      GST_INFO ("Can not link clips with source coming from different assets");
+      if (ges_track_element_get_track (sibling) == track
+          && _IS_CORE_CHILD (sibling) != is_core
+          && ges_track_element_is_active (sibling) != active)
+        data->active = active;
 
-      goto done;
+      child_data = g_list_prepend (child_data, data);
     }
   }
 
-  /* And now pass all TrackElements to the first clip,
-   * and remove others from the layer (updating the supported formats) */
-  ret = containers->data;
-  supported_formats = GES_CLIP (ret)->priv->supportedformats;
-  for (tmpclip = containers->next; tmpclip; tmpclip = tmpclip->next) {
-    GESClip *cclip = tmpclip->data;
-    GList *children = ges_container_get_children (GES_CONTAINER (cclip), FALSE);
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot set the active of child %" GES_FORMAT
+        " from %i to %i because the duration-limit cannot be adjusted",
+        GES_ARGS (child), ges_track_element_is_active (child), active);
+    return FALSE;
+  }
 
-    for (tmpelement = children; tmpelement; tmpelement = tmpelement->next) {
-      GESTimelineElement *celement = GES_TIMELINE_ELEMENT (tmpelement->data);
+  return TRUE;
+}
 
-      ges_container_remove (GES_CONTAINER (cclip), celement);
-      ges_container_add (ret, celement);
+static void
+_child_active_changed (GESClip * self, GESTrackElement * child)
+{
+  GList *tmp;
+  GESTrack *track = ges_track_element_get_track (child);
+  gboolean active = ges_track_element_is_active (child);
+  gboolean is_core = _IS_CORE_CHILD (child);
+  gboolean prev_prevent = self->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update;
+
+  /* We want to ensure that each active non-core element has a
+   * corresponding active core element in the same track */
+  if (self->priv->setting_active || !track || is_core == active)
+    return;
 
-      supported_formats = supported_formats |
-          ges_track_element_get_track_type (GES_TRACK_ELEMENT (celement));
-    }
-    g_list_free_full (children, gst_object_unref);
+  self->priv->setting_active = TRUE;
+  self->priv->prevent_duration_limit_update = TRUE;
+  self->priv->prevent_children_outpoint_update = TRUE;
 
-    ges_layer_remove_clip (layer, tmpclip->data);
-  }
+  /* If we are core, make all the non-core elements in-active
+   * If we are non-core, make the core element active (should only be one) */
+  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
+    GESTrackElement *sibling = tmp->data;
 
-  ges_clip_set_supported_formats (GES_CLIP (ret), supported_formats);
+    if (ges_track_element_get_track (sibling) == track
+        && _IS_CORE_CHILD (sibling) != is_core
+        && ges_track_element_is_active (sibling) != active) {
 
-done:
-  if (tracks)
-    g_free (tracks);
+      GST_INFO_OBJECT (self, "Setting active to %i for child %" GES_FORMAT
+          " since the sibling %" GES_FORMAT " in the same track %"
+          GST_PTR_FORMAT " has been set to %i", active, GES_ARGS (sibling),
+          GES_ARGS (child), track, active);
+
+      if (!ges_track_element_set_active (sibling, active))
+        GST_ERROR_OBJECT (self, "Failed to set active for child %"
+            GES_FORMAT, GES_ARGS (sibling));
+    }
+  }
 
+  self->priv->setting_active = FALSE;
+  self->priv->prevent_duration_limit_update = prev_prevent;
+  self->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
+}
 
-  return ret;
+/****************************************************
+ *                      track                       *
+ ****************************************************/
 
+static GESTrackElement *
+_find_core_in_track (GESClip * clip, GESTrack * track)
+{
+  GList *tmp;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    if (_IS_CORE_CHILD (child) && ges_track_element_get_track (child) == track)
+      return child;
+  }
+  return NULL;
 }
 
 static gboolean
-_edit (GESContainer * container, GList * layers,
-    gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
+_track_contains_non_core (GESClip * clip, GESTrack * track)
 {
-  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container);
-  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
-
-  if (!G_UNLIKELY (GES_CONTAINER_CHILDREN (container))) {
-    GST_WARNING_OBJECT (container, "Trying to edit, but not containing"
-        "any TrackElement yet.");
-    return FALSE;
+  GList *tmp;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    if (!_IS_CORE_CHILD (child) && ges_track_element_get_track (child) == track)
+      return TRUE;
   }
+  return FALSE;
+}
 
-  if (!timeline) {
-    GST_WARNING_OBJECT (container, "Trying to edit, but not in any"
-        "timeline.");
-    return FALSE;
-  }
+gboolean
+ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
+    GESTrack * track, GError ** error)
+{
+  GList *child_data;
+  DurationLimitData *data;
+  GESTrack *current_track = ges_track_element_get_track (child);
+  GESTrackElement *core = NULL;
+
+  if (clip->priv->allow_any_track)
+    return TRUE;
+
+  if (current_track == track)
+    return TRUE;
 
-  switch (mode) {
-    case GES_EDIT_MODE_RIPPLE:
-      return timeline_ripple_object (timeline, element,
-          new_layer_priority <
-          0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) :
-          new_layer_priority, layers, edge, position);
-    case GES_EDIT_MODE_TRIM:
-      return timeline_trim_object (timeline, element,
-          new_layer_priority <
-          0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) :
-          new_layer_priority, layers, edge, position);
-    case GES_EDIT_MODE_NORMAL:
-      return timeline_move_object (timeline, element,
-          new_layer_priority <
-          0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) :
-          new_layer_priority, layers, edge, position);
-    case GES_EDIT_MODE_ROLL:
-      return timeline_roll_object (timeline, element, layers, edge, position);
-    case GES_EDIT_MODE_SLIDE:
-      GST_ERROR ("Sliding not implemented.");
+  /* NOTE: we consider the following error cases programming errors by
+   * the user */
+  if (current_track) {
+    /* can not remove a core element from a track if a non-core one sits
+     * above it */
+    if (_IS_CORE_CHILD (child)
+        && _track_contains_non_core (clip, current_track)) {
+      GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
+          " to the track %" GST_PTR_FORMAT " because it has non-core "
+          "siblings above it in its current track %" GST_PTR_FORMAT,
+          GES_ARGS (child), track, current_track);
       return FALSE;
+    }
+    /* otherwise can remove */
   }
-  return FALSE;
-}
+  if (track) {
+    GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+    const GESTimeline *track_timeline = ges_track_get_timeline (track);
+    if (track_timeline == NULL) {
+      GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT
+          " to the track %" GST_PTR_FORMAT " because it is not part "
+          "of a timeline", GES_ARGS (child), track);
+      return FALSE;
+    }
+    if (track_timeline != clip_timeline) {
+      GST_WARNING_OBJECT (clip, "Cannot move the child %" GES_FORMAT
+          " to the track %" GST_PTR_FORMAT " because its timeline %"
+          GST_PTR_FORMAT " does not match the clip's timeline %"
+          GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline,
+          clip_timeline);
+      return FALSE;
+    }
 
-static void
-_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
-{
-  GList *tmp;
-  GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
+    core = _find_core_in_track (clip, track);
+    /* one core child per track, and other children (effects) can only be
+     * placed in a track that already has a core child */
+    if (_IS_CORE_CHILD (child)) {
+      if (core) {
+        GST_WARNING_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
+            " to the track %" GST_PTR_FORMAT " because it contains a "
+            "core sibling %" GES_FORMAT, GES_ARGS (child), track,
+            GES_ARGS (core));
+        return FALSE;
+      }
+    } else {
+      if (!core) {
+        GST_WARNING_OBJECT (clip, "Cannot move the non-core child %"
+            GES_FORMAT " to the track %" GST_PTR_FORMAT " because it "
+            " does not contain a core sibling", GES_ARGS (child), track);
+        return FALSE;
+      }
+    }
+  }
 
-  if (!self->priv->layer)
-    return;
+  data = _duration_limit_data_new (child);
+  gst_clear_object (&data->track);
+  data->track = track ? gst_object_ref (track) : NULL;
+  if (core && !ges_track_element_is_active (core)) {
+    /* if core is set, then we are adding a non-core to a track containing
+     * a core track element. If this happens, but the core is in-active
+     * then we will make the non-core element also inactive upon setting
+     * its track */
+    data->active = FALSE;
+  }
 
-  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
-    ccopy->priv->copied_track_elements =
-        g_list_append (ccopy->priv->copied_track_elements,
-        ges_timeline_element_copy (tmp->data, TRUE));
+  child_data = _duration_limit_data_list_with_data (clip, data);
+
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT " from "
+        "track %" GST_PTR_FORMAT " to track %" GST_PTR_FORMAT " because "
+        "the duration-limit cannot be adjusted", GES_ARGS (child),
+        current_track, track);
+    return FALSE;
   }
 
-  ccopy->priv->copied_layer = g_object_ref (self->priv->layer);
+  return TRUE;
 }
 
-static GESTimelineElement *
-_paste (GESTimelineElement * element, GESTimelineElement * ref,
-    GstClockTime paste_position)
+static void
+_update_active_for_track (GESClip * self, GESTrackElement * child)
 {
-  GList *tmp;
-  GESClip *self = GES_CLIP (element);
-  GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
-
-  if (self->priv->copied_layer)
-    nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer);
+  GESTrack *track = ges_track_element_get_track (child);
+  GESTrackElement *core;
+  gboolean active;
+  gboolean prev_prevent = self->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = self->priv->prevent_children_outpoint_update;
 
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
-  if (self->priv->copied_layer) {
-    if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) {
-      GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
-          GES_ARGS (element), GST_TIME_ARGS (paste_position));
+  if (self->priv->allow_any_track || _IS_CORE_CHILD (child) || !track)
+    return;
 
-      return NULL;
-    }
+  /* if we add a non-core to a track, but the core child is inactive, we
+   * also need the non-core to be inactive */
+  core = _find_core_in_track (self, track);
 
+  if (!core) {
+    GST_ERROR_OBJECT (self, "The non-core child %" GES_FORMAT " is in "
+        "the track %" GST_PTR_FORMAT " with no core sibling",
+        GES_ARGS (child), track);
+    active = FALSE;
+  } else {
+    active = ges_track_element_is_active (core);
   }
 
-  for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) {
-    GESTrackElement *new_trackelement, *trackelement =
-        GES_TRACK_ELEMENT (tmp->data);
+  if (!active && ges_track_element_is_active (child)) {
 
-    new_trackelement =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (trackelement), FALSE));
-    if (new_trackelement == NULL) {
-      GST_WARNING_OBJECT (trackelement, "Could not create a copy");
-      continue;
-    }
+    GST_INFO_OBJECT (self, "De-activating non-core child %" GES_FORMAT
+        " since the core child in the same track %" GST_PTR_FORMAT " is "
+        "not active", GES_ARGS (child), track);
 
-    ges_container_add (GES_CONTAINER (nclip),
-        GES_TIMELINE_ELEMENT (new_trackelement));
+    self->priv->setting_active = TRUE;
+    self->priv->prevent_duration_limit_update = TRUE;
+    self->priv->prevent_children_outpoint_update = TRUE;
 
-    ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
-        GES_TIMELINE_ELEMENT (new_trackelement));
+    if (!ges_track_element_set_active (child, FALSE))
+      GST_ERROR_OBJECT (self, "Failed to de-activate child %" GES_FORMAT,
+          GES_ARGS (child));
 
-    ges_track_element_copy_bindings (trackelement, new_trackelement,
-        GST_CLOCK_TIME_NONE);
+    self->priv->setting_active = FALSE;
+    self->priv->prevent_duration_limit_update = prev_prevent;
+    self->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
   }
-
-  return GES_TIMELINE_ELEMENT (nclip);
 }
 
-static gboolean
-_lookup_child (GESTimelineElement * self, const gchar * prop_name,
-    GObject ** child, GParamSpec ** pspec)
-{
-  GList *tmp;
-
-  if (GES_TIMELINE_ELEMENT_CLASS (ges_clip_parent_class)->lookup_child (self,
-          prop_name, child, pspec))
-    return TRUE;
+#define _IS_PROP(prop) (g_strcmp0 (name, prop) == 0)
 
-  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
-    if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec))
-      return TRUE;
+static void
+_child_property_changed_cb (GESTimelineElement * child, GParamSpec * pspec,
+    GESClip * self)
+{
+  gboolean update_limit = FALSE;
+  gboolean update_outpoint = FALSE;
+  const gchar *name = pspec->name;
+
+  if (_IS_PROP ("track")) {
+    update_limit = TRUE;
+    update_outpoint = TRUE;
+    _update_active_for_track (self, GES_TRACK_ELEMENT (child));
+  } else if (_IS_PROP ("active")) {
+    update_limit = TRUE;
+    update_outpoint = TRUE;
+    _child_active_changed (self, GES_TRACK_ELEMENT (child));
+  } else if (_IS_PROP ("priority")) {
+    update_limit = TRUE;
+    update_outpoint = TRUE;
+    _child_priority_changed (GES_CONTAINER (self), child);
+  } else if (_IS_PROP ("in-point")) {
+    /* update outpoint already handled by the track element */
+    update_limit = _child_inpoint_changed (self, child);
+  } else if (_IS_PROP ("max-duration")) {
+    update_limit = TRUE;
+    _child_max_duration_changed (GES_CONTAINER (self), child);
+  } else if (_IS_PROP ("has-internal-source")) {
+    _child_has_internal_source_changed (self, child);
   }
 
-  return FALSE;
+  if (update_limit)
+    _update_duration_limit (self);
+  if (update_outpoint)
+    _update_children_outpoints (self);
 }
 
 /****************************************************
- *                                                  *
- *    GObject virtual methods implementation        *
- *                                                  *
+ *                time properties                   *
  ****************************************************/
-static void
-ges_clip_get_property (GObject * object, guint property_id,
-    GValue * value, GParamSpec * pspec)
-{
-  GESClip *clip = GES_CLIP (object);
 
-  switch (property_id) {
-    case PROP_LAYER:
-      g_value_set_object (value, clip->priv->layer);
-      break;
-    case PROP_SUPPORTED_FORMATS:
-      g_value_set_flags (value, clip->priv->supportedformats);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+gboolean
+ges_clip_can_set_time_property_of_child (GESClip * clip,
+    GESTrackElement * child, GObject * child_prop_object, GParamSpec * pspec,
+    const GValue * value, GError ** error)
+{
+  if (_IS_TOP_EFFECT (child)) {
+    gchar *prop_name =
+        ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child),
+        child_prop_object, pspec);
+
+    if (prop_name) {
+      GList *child_data;
+      DurationLimitData *data = _duration_limit_data_new (child);
+      GValue *copy = g_new0 (GValue, 1);
+
+      g_value_init (copy, pspec->value_type);
+      g_value_copy (value, copy);
+
+      g_hash_table_insert (data->time_property_values, prop_name, copy);
+
+      child_data = _duration_limit_data_list_with_data (clip, data);
+
+      if (!_can_update_duration_limit (clip, child_data, error)) {
+        gchar *val_str = gst_value_serialize (value);
+        GST_INFO_OBJECT (clip, "Cannot set the child-property %s of "
+            "child %" GES_FORMAT " to %s because the duration-limit "
+            "cannot be adjusted", prop_name, GES_ARGS (child), val_str);
+        g_free (val_str);
+        return FALSE;
+      }
+    }
+  }
+  return TRUE;
+}
+
+static void
+_child_time_property_changed_cb (GESTimelineElement * child,
+    GObject * prop_object, GParamSpec * pspec, GESClip * self)
+{
+  gchar *time_prop =
+      ges_base_effect_get_time_property_name (GES_BASE_EFFECT (child),
+      prop_object, pspec);
+  if (time_prop) {
+    g_free (time_prop);
+    _update_duration_limit (self);
+    _update_children_outpoints (self);
+  }
+}
+
+/*****************************************************
+ *                                                   *
+ * GESTimelineElement virtual methods implementation *
+ *                                                   *
+ *****************************************************/
+
+static gboolean
+_set_start (GESTimelineElement * element, GstClockTime start)
+{
+  GList *tmp, *children;
+  GESContainer *container = GES_CONTAINER (element);
+
+  GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %"
+      GST_PTR_FORMAT ")", container->initiated_move);
+
+  /* get copy of children, since GESContainer may resort the clip */
+  children = ges_container_get_children (container, FALSE);
+  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+  for (tmp = children; tmp; tmp = g_list_next (tmp)) {
+    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
+
+    if (child != container->initiated_move)
+      _set_start0 (GES_TIMELINE_ELEMENT (child), start);
+  }
+  container->children_control_mode = GES_CHILDREN_UPDATE;
+  g_list_free_full (children, gst_object_unref);
+
+  return TRUE;
+}
+
+/* returns TRUE if we did not break early */
+static gboolean
+_set_childrens_inpoint (GESTimelineElement * element, GstClockTime inpoint,
+    gboolean break_on_failure)
+{
+  GESClip *self = GES_CLIP (element);
+  GList *tmp;
+  GESClipPrivate *priv = self->priv;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
+
+  priv->setting_inpoint = TRUE;
+  priv->prevent_duration_limit_update = TRUE;
+  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+
+    if (_IS_CORE_INTERNAL_SOURCE_CHILD (child)) {
+      if (!_set_inpoint0 (child, inpoint)) {
+        GST_ERROR_OBJECT ("Could not set the in-point of child %"
+            GES_FORMAT " to %" GST_TIME_FORMAT, GES_ARGS (child),
+            GST_TIME_ARGS (inpoint));
+        if (break_on_failure) {
+          priv->setting_inpoint = FALSE;
+          priv->prevent_duration_limit_update = prev_prevent;
+          return FALSE;
+        }
+      }
+    }
+  }
+  priv->setting_inpoint = FALSE;
+  priv->prevent_duration_limit_update = prev_prevent;
+
+  _update_duration_limit (self);
+
+  return TRUE;
+}
+
+static gboolean
+_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
+{
+  if (!_can_set_inpoint_of_core_children (GES_CLIP (element), inpoint, NULL)) {
+    GST_WARNING_OBJECT (element, "Cannot set the in-point to %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (inpoint));
+    return FALSE;
+  }
+
+  if (!_set_childrens_inpoint (element, inpoint, TRUE)) {
+    _set_childrens_inpoint (element, element->inpoint, FALSE);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static gboolean
+_set_duration (GESTimelineElement * element, GstClockTime duration)
+{
+  GList *tmp, *children;
+  GESContainer *container = GES_CONTAINER (element);
+
+  /* get copy of children, since GESContainer may resort the clip */
+  children = ges_container_get_children (container, FALSE);
+  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
+    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
+
+    if (child != container->initiated_move)
+      _set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
+  }
+  container->children_control_mode = GES_CHILDREN_UPDATE;
+  g_list_free_full (children, gst_object_unref);
+
+  return TRUE;
+}
+
+static gboolean
+_set_max_duration (GESTimelineElement * element, GstClockTime maxduration)
+{
+  GList *tmp;
+  GList *child_data = NULL;
+  GESClip *self = GES_CLIP (element);
+  GESClipPrivate *priv = self->priv;
+  GstClockTime new_min = GST_CLOCK_TIME_NONE;
+  gboolean has_core = FALSE;
+  gboolean res = FALSE;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
+
+  /* if we are setting based on a change in the minimum */
+  if (priv->updating_max_duration)
+    return TRUE;
+
+  /* else, we set every core child to have the same max duration */
+
+  /* check that the duration-limit can be changed */
+  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    DurationLimitData *data = _duration_limit_data_new (child);
+
+    if (_IS_CORE_INTERNAL_SOURCE_CHILD (child))
+      data->max_duration = maxduration;
+
+    child_data = g_list_prepend (child_data, data);
+  }
+
+  if (!_can_update_duration_limit (self, child_data, NULL)) {
+    GST_WARNING_OBJECT (self, "Cannot set the max-duration from %"
+        GST_TIME_FORMAT " to %" GST_TIME_FORMAT " because the "
+        "duration-limit cannot be adjusted",
+        GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (maxduration));
+    return FALSE;
+  }
+
+  priv->prevent_duration_limit_update = TRUE;
+  priv->setting_max_duration = TRUE;
+  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+
+    if (_IS_CORE_CHILD (child)) {
+      has_core = TRUE;
+      if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT (child))) {
+        if (!ges_timeline_element_set_max_duration (child, maxduration))
+          GST_ERROR_OBJECT ("Could not set the max-duration of child %"
+              GES_FORMAT " to %" GST_TIME_FORMAT, GES_ARGS (child),
+              GST_TIME_ARGS (maxduration));
+        new_min = _MIN_CLOCK_TIME (new_min, child->maxduration);
+      }
+    }
+  }
+  priv->setting_max_duration = FALSE;
+  priv->prevent_duration_limit_update = prev_prevent;
+
+  if (!has_core) {
+    /* allow max-duration to be set arbitrarily when we have no
+     * core children, even though there is no actual minimum max-duration
+     * when it has no core children */
+    if (GST_CLOCK_TIME_IS_VALID (maxduration))
+      GST_INFO_OBJECT (element,
+          "Allowing max-duration of the clip to be set to %" GST_TIME_FORMAT
+          " because it has no core children", GST_TIME_ARGS (maxduration));
+    res = TRUE;
+    goto done;
+  }
+
+  if (new_min != maxduration) {
+    if (GST_CLOCK_TIME_IS_VALID (new_min))
+      GST_WARNING_OBJECT (element, "Failed to set the max-duration of the "
+          "clip to %" GST_TIME_FORMAT " because it was not possible to "
+          "match this with the actual minimum of %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (maxduration), GST_TIME_ARGS (new_min));
+    else
+      GST_WARNING_OBJECT (element, "Failed to set the max-duration of the "
+          "clip to %" GST_TIME_FORMAT " because it has no core children "
+          "whose max-duration could be set to anything other than "
+          "GST_CLOCK_TIME_NONE", GST_TIME_ARGS (maxduration));
+    priv->updating_max_duration = TRUE;
+    ges_timeline_element_set_max_duration (element, new_min);
+    priv->updating_max_duration = FALSE;
+    goto done;
+  }
+
+  res = TRUE;
+
+done:
+  _update_duration_limit (self);
+
+  return res;
+}
+
+static gboolean
+_set_priority (GESTimelineElement * element, guint32 priority)
+{
+  GESClipPrivate *priv = GES_CLIP (element)->priv;
+  GList *tmp;
+  guint32 min_prio, max_prio, min_child_prio = G_MAXUINT32;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
+  GESContainer *container = GES_CONTAINER (element);
+
+  for (tmp = container->children; tmp; tmp = g_list_next (tmp))
+    min_child_prio = MIN (min_child_prio, _PRIORITY (tmp->data));
+
+  /* send the new 'priority' to determine what the new 'min_prio' should
+   * be for the clip */
+  _get_priority_range_full (container, &min_prio, &max_prio, priority);
+
+  /* offsets will remain constant for the children */
+  priv->prevent_resort = TRUE;
+  priv->prevent_duration_limit_update = TRUE;
+  priv->prevent_children_outpoint_update = TRUE;
+  priv->setting_priority = TRUE;
+  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
+    GESTimelineElement *child = tmp->data;
+    guint32 track_element_prio = min_prio + (child->priority - min_child_prio);
+
+    if (track_element_prio > max_prio) {
+      GST_WARNING_OBJECT (container, "%s priority of %i, is outside of its "
+          "containing layer space. (%d/%d) setting it to the maximum it can be",
+          child->name, priority, min_prio, max_prio);
+
+      track_element_prio = max_prio;
+    }
+    _set_priority0 (child, track_element_prio);
+  }
+  /* no need to re-sort the container since we maintained the relative
+   * offsets. As such, the height and duration-limit remains the same as
+   * well. */
+  priv->prevent_resort = FALSE;
+  priv->setting_priority = FALSE;
+  priv->prevent_duration_limit_update = prev_prevent;
+  priv->prevent_children_outpoint_update = prev_prevent_outpoint;
+
+  return TRUE;
+}
+
+static guint32
+_get_layer_priority (GESTimelineElement * element)
+{
+  GESClip *clip = GES_CLIP (element);
+
+  if (clip->priv->layer == NULL)
+    return GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY;
+
+  return ges_layer_get_priority (clip->priv->layer);
+}
+
+static gboolean
+_get_natural_framerate (GESTimelineElement * self, gint * framerate_n,
+    gint * framerate_d)
+{
+  GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
+
+  if (!asset) {
+    GST_WARNING_OBJECT (self, "No asset set?");
+
+    return FALSE;
+  }
+
+  return ges_clip_asset_get_natural_framerate (GES_CLIP_ASSET (asset),
+      framerate_n, framerate_d);
+}
+
+/****************************************************
+ *                                                  *
+ *  GESContainer virtual methods implementation     *
+ *                                                  *
+ ****************************************************/
+
+static void
+_compute_height (GESContainer * container)
+{
+  GList *tmp;
+  guint32 min_prio = G_MAXUINT32, max_prio = 0;
+
+  if (container->children == NULL) {
+    /* FIXME Why not 0! */
+    _ges_container_set_height (container, 1);
+    return;
+  }
+
+  /* Go over all childs and check if height has changed */
+  for (tmp = container->children; tmp; tmp = tmp->next) {
+    guint tck_priority = _PRIORITY (tmp->data);
+
+    if (tck_priority < min_prio)
+      min_prio = tck_priority;
+    if (tck_priority > max_prio)
+      max_prio = tck_priority;
+  }
+
+  _ges_container_set_height (container, max_prio - min_prio + 1);
+}
+
+void
+ges_clip_take_add_error (GESClip * clip, GError ** error)
+{
+  GESClipPrivate *priv = clip->priv;
+
+  g_clear_error (error);
+  if (error) {
+    if (*error) {
+      GST_ERROR_OBJECT (clip, "Error not handled: %s", (*error)->message);
+      g_error_free (*error);
+    }
+    *error = priv->add_error;
+  } else {
+    g_clear_error (&priv->add_error);
+  }
+  priv->add_error = NULL;
+}
+
+void
+ges_clip_set_add_error (GESClip * clip, GError * error)
+{
+  GESClipPrivate *priv = clip->priv;
+
+  g_clear_error (&priv->add_error);
+  priv->add_error = error;
+}
+
+static gboolean
+_add_child (GESContainer * container, GESTimelineElement * element)
+{
+  gboolean ret = FALSE;
+  GESClip *self = GES_CLIP (container);
+  GESTrackElement *track_el = GES_TRACK_ELEMENT (element);
+  GESClipClass *klass = GES_CLIP_GET_CLASS (self);
+  guint32 min_prio, max_prio, new_prio;
+  GESTrack *track;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container);
+  GESClipPrivate *priv = self->priv;
+  GESAsset *asset, *creator_asset;
+  gboolean prev_prevent = priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
+  GList *tmp;
+  GError *error = NULL;
+
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
+
+  if (element->timeline && element->timeline != timeline) {
+    GST_WARNING_OBJECT (self, "Cannot add %" GES_FORMAT " as a child "
+        "because its timeline is %" GST_PTR_FORMAT " rather than the "
+        "clip's timeline %" GST_PTR_FORMAT, GES_ARGS (element),
+        element->timeline, timeline);
+    goto done;
+  }
+
+  asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
+  creator_asset = ges_track_element_get_creator_asset (track_el);
+  if (creator_asset && asset != creator_asset) {
+    GST_WARNING_OBJECT (self,
+        "Cannot add the track element %" GES_FORMAT " as a child "
+        "because it is a core element created by another clip with a "
+        "different asset to the current clip's asset", GES_ARGS (element));
+    goto done;
+  }
+
+  track = ges_track_element_get_track (track_el);
+
+  if (track && ges_track_get_timeline (track) != timeline) {
+    /* really, an element in a track should have the same timeline as
+     * the track, so we would have checked this with the
+     * element->timeline check. But technically a user could get around
+     * this, so we double check here. */
+    GST_WARNING_OBJECT (self, "Cannot add %" GES_FORMAT " as a child "
+        "because its track %" GST_PTR_FORMAT " is part of the timeline %"
+        GST_PTR_FORMAT " rather than the clip's timeline %" GST_PTR_FORMAT,
+        GES_ARGS (element), track, ges_track_get_timeline (track), timeline);
+    goto done;
+  }
+
+  /* NOTE: notifies are currently frozen by ges_container_add */
+
+  _get_priority_range (container, &min_prio, &max_prio);
+
+  if (creator_asset) {
+    /* NOTE: Core track elements that are base effects are added like any
+     * other core elements. In particular, they are *not* added to the
+     * list of added effects, so we do not increase nb_effects. */
+
+    /* Set the core element to have the same in-point, which we don't
+     * apply to effects */
+    GstClockTime new_inpoint;
+    if (ges_track_element_has_internal_source (track_el))
+      new_inpoint = _INPOINT (self);
+    else
+      new_inpoint = 0;
+
+    /* new priority is that of the lowest priority core child. Usually
+     * each core child has the same priority.
+     * Also must be lower than all effects */
+
+    new_prio = min_prio;
+    for (tmp = container->children; tmp; tmp = tmp->next) {
+      if (_IS_CORE_CHILD (tmp->data))
+        new_prio = MAX (new_prio, _PRIORITY (tmp->data));
+      else if (_IS_TOP_EFFECT (tmp->data))
+        new_prio = MAX (new_prio, _PRIORITY (tmp->data) + 1);
+    }
+
+    if (track && !priv->allow_any_track) {
+      GList *child_data;
+      DurationLimitData *data;
+      GESTrackElement *core = _find_core_in_track (self, track);
+
+      if (core) {
+        GST_WARNING_OBJECT (self, "Cannot add the core child %" GES_FORMAT
+            " because it is in the same track %" GST_PTR_FORMAT " as an "
+            "existing core child %" GES_FORMAT, GES_ARGS (element), track,
+            GES_ARGS (core));
+        goto done;
+      }
+
+      data = _duration_limit_data_new (track_el);
+      data->inpoint = new_inpoint;
+      data->priority = new_prio;
+
+      child_data = _duration_limit_data_list_with_data (self, data);
+
+      if (!_can_update_duration_limit (self, child_data, &error)) {
+        GST_INFO_OBJECT (self, "Cannot add core %" GES_FORMAT " as a "
+            "child because the duration-limit cannot be adjusted",
+            GES_ARGS (element));
+        goto done;
+      }
+    }
+
+    if (GES_CLOCK_TIME_IS_LESS (element->maxduration, new_inpoint)) {
+      GST_INFO_OBJECT (self, "Can not set the in-point of the "
+          "element %" GES_FORMAT " to %" GST_TIME_FORMAT " because its "
+          "max-duration is %" GST_TIME_FORMAT, GES_ARGS (element),
+          GST_TIME_ARGS (new_inpoint), GST_TIME_ARGS (element->maxduration));
+
+      g_set_error (&error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
+          "Cannot add the child \"%s\" to clip \"%s\" because its max-"
+          "duration is %" GST_TIME_FORMAT ", which is less than the in-"
+          "point of the clip %" GST_TIME_FORMAT, element->name,
+          GES_TIMELINE_ELEMENT_NAME (self),
+          GST_TIME_ARGS (element->maxduration), GST_TIME_ARGS (new_inpoint));
+
+      goto done;
+    }
+
+    /* adding can fail if the max-duration of the element is smaller
+     * than the current in-point of the clip */
+    if (!_set_inpoint0 (element, new_inpoint)) {
+      GST_WARNING_OBJECT (self, "Could not set the in-point of the "
+          "element %" GES_FORMAT " to %" GST_TIME_FORMAT ". Not adding "
+          "as a child", GES_ARGS (element), GST_TIME_ARGS (new_inpoint));
+      goto done;
+    }
+
+    _set_priority0 (element, new_prio);
+
+  } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) && _IS_TOP_EFFECT (element)) {
+    /* Add the effect at the lowest priority among effects (just after
+     * the core elements). Need to shift the core elements up by 1
+     * to make room. */
+
+    /* new priority is the lowest priority effect */
+    if (priv->use_effect_priority) {
+      new_prio = priv->effect_priority;
+    } else {
+      new_prio = min_prio;
+      for (tmp = container->children; tmp; tmp = tmp->next) {
+        if (_IS_TOP_EFFECT (tmp->data))
+          new_prio = MAX (new_prio, _PRIORITY (tmp->data) + 1);
+      }
+    }
+    /* make sure higher than core */
+    for (tmp = container->children; tmp; tmp = tmp->next) {
+      if (_IS_CORE_CHILD (tmp->data))
+        new_prio = MIN (new_prio, _PRIORITY (tmp->data));
+    }
+
+    if (track && !priv->allow_any_track) {
+      GList *child_data, *tmp;
+      DurationLimitData *data;
+      GESTrackElement *core = _find_core_in_track (self, track);
+
+      if (!core) {
+        GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT
+            " because its track %" GST_PTR_FORMAT " does not contain one "
+            "of the clip's core children", GES_ARGS (element), track);
+        goto done;
+      }
+
+      data = _duration_limit_data_new (track_el);
+      data->priority = new_prio;
+      if (!ges_track_element_is_active (core))
+        data->active = FALSE;
+
+      child_data = _duration_limit_data_list_with_data (self, data);
+
+      for (tmp = child_data; tmp; tmp = tmp->next) {
+        data = tmp->data;
+        if (data->priority >= new_prio)
+          data->priority++;
+      }
+
+      if (!_can_update_duration_limit (self, child_data, &error)) {
+        GST_INFO_OBJECT (self, "Cannot add effect %" GES_FORMAT " as "
+            "a child because the duration-limit cannot be adjusted",
+            GES_ARGS (element));
+        goto done;
+      }
+    }
+
+    _update_active_for_track (self, track_el);
+
+    priv->nb_effects++;
+    GST_DEBUG_OBJECT (self, "Adding %ith effect: %" GES_FORMAT
+        " Priority %i", priv->nb_effects, GES_ARGS (element), new_prio);
+
+    /* changing priorities, and updating their offset */
+    priv->prevent_resort = TRUE;
+    priv->setting_priority = TRUE;
+    priv->prevent_duration_limit_update = TRUE;
+    priv->prevent_children_outpoint_update = TRUE;
+
+    /* increase the priority of anything with a lower priority */
+    for (tmp = container->children; tmp; tmp = tmp->next) {
+      GESTimelineElement *child = tmp->data;
+      if (child->priority >= new_prio)
+        ges_timeline_element_set_priority (child, child->priority + 1);
+    }
+    _set_priority0 (element, new_prio);
+
+    priv->prevent_resort = FALSE;
+    priv->setting_priority = FALSE;
+    priv->prevent_duration_limit_update = prev_prevent;
+    priv->prevent_children_outpoint_update = prev_prevent_outpoint;
+    /* no need to call _ges_container_sort_children (container) since
+     * there is no change to the ordering yet (this happens after the
+     * child is actually added) */
+    /* The height has already changed (increased by 1) */
+    _compute_height (container);
+    /* update duration limit in _child_added */
+  } else {
+    if (_IS_TOP_EFFECT (element))
+      GST_WARNING_OBJECT (self, "Cannot add the effect %" GES_FORMAT
+          " because it is not a core element created by the clip itself "
+          "and the %s class does not allow for adding extra effects",
+          GES_ARGS (element), G_OBJECT_CLASS_NAME (klass));
+    else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass))
+      GST_WARNING_OBJECT (self, "Cannot add the track element %"
+          GES_FORMAT " because it is neither a core element created by "
+          "the clip itself, nor a GESBaseEffect", GES_ARGS (element));
+    else
+      GST_WARNING_OBJECT (self, "Cannot add the track element %"
+          GES_FORMAT " because it is not a core element created by the "
+          "clip itself", GES_ARGS (element));
+    goto done;
+  }
+
+  _set_start0 (element, GES_TIMELINE_ELEMENT_START (self));
+  _set_duration0 (element, GES_TIMELINE_ELEMENT_DURATION (self));
+
+  ret = TRUE;
+
+done:
+  if (error)
+    ges_clip_set_add_error (self, error);
+
+  return ret;
+}
+
+void
+ges_clip_take_remove_error (GESClip * clip, GError ** error)
+{
+  GESClipPrivate *priv = clip->priv;
+
+  g_clear_error (error);
+  if (error) {
+    if (*error) {
+      GST_ERROR ("Error not handled: %s", (*error)->message);
+      g_error_free (*error);
+    }
+    *error = priv->remove_error;
+  } else {
+    g_clear_error (&priv->remove_error);
+  }
+  priv->remove_error = NULL;
+}
+
+void
+ges_clip_set_remove_error (GESClip * clip, GError * error)
+{
+  GESClipPrivate *priv = clip->priv;
+
+  g_clear_error (&priv->remove_error);
+  priv->remove_error = error;
+}
+
+static gboolean
+_remove_child (GESContainer * container, GESTimelineElement * element)
+{
+  GESTrackElement *el = GES_TRACK_ELEMENT (element);
+  GESClip *self = GES_CLIP (container);
+  GESClipPrivate *priv = self->priv;
+
+  /* check that the duration-limit can be changed */
+  /* If we are removing a core child, then all other children in the
+   * same track will be removed from the track, which will make the
+   * duration-limit increase, which is safe
+   * Similarly, if it has no track, the duration-limit will not change */
+  if (!priv->allow_any_remove && !_IS_CORE_CHILD (element) &&
+      ges_track_element_get_track (el)) {
+    GList *child_data = NULL;
+    GList *tmp;
+    GError *error = NULL;
+
+    for (tmp = container->children; tmp; tmp = tmp->next) {
+      GESTrackElement *child = tmp->data;
+      if (child == el)
+        continue;
+      child_data =
+          g_list_prepend (child_data, _duration_limit_data_new (child));
+    }
+
+    if (!_can_update_duration_limit (self, child_data, &error)) {
+      ges_clip_set_remove_error (self, error);
+      GST_INFO_OBJECT (self, "Cannot remove the child %" GES_FORMAT
+          " because the duration-limit cannot be adjusted", GES_ARGS (el));
+      return FALSE;
+    }
+  }
+
+  /* NOTE: notifies are currently frozen by ges_container_add */
+  if (_IS_TOP_EFFECT (element)) {
+    GList *tmp;
+    gboolean prev_prevent = priv->prevent_duration_limit_update;
+    gboolean prev_prevent_outpoint = priv->prevent_children_outpoint_update;
+    GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
+
+    /* changing priorities, so preventing a re-sort */
+    priv->prevent_resort = TRUE;
+    priv->setting_priority = TRUE;
+    priv->prevent_duration_limit_update = TRUE;
+    priv->prevent_children_outpoint_update = TRUE;
+    for (tmp = container->children; tmp; tmp = tmp->next) {
+      guint32 sibling_prio = GES_TIMELINE_ELEMENT_PRIORITY (tmp->data);
+      if (sibling_prio > element->priority)
+        ges_timeline_element_set_priority (GES_TIMELINE_ELEMENT (tmp->data),
+            sibling_prio - 1);
+    }
+    priv->nb_effects--;
+    priv->prevent_resort = FALSE;
+    priv->setting_priority = FALSE;
+    priv->prevent_duration_limit_update = prev_prevent;
+    priv->prevent_children_outpoint_update = prev_prevent_outpoint;
+    /* no need to re-sort the children since the rest keep the same
+     * relative priorities */
+    /* height may have changed */
+    _compute_height (container);
+  }
+  /* duration-limit updated in _child_removed */
+  return TRUE;
+}
+
+static void
+_child_added (GESContainer * container, GESTimelineElement * element)
+{
+  GESClip *self = GES_CLIP (container);
+
+  g_signal_connect (element, "notify", G_CALLBACK (_child_property_changed_cb),
+      self);
+
+  if (GES_IS_TIME_EFFECT (element))
+    g_signal_connect (element, "deep-notify",
+        G_CALLBACK (_child_time_property_changed_cb), self);
+
+  if (_IS_CORE_CHILD (element))
+    _update_max_duration (container);
+
+  _update_duration_limit (self);
+  _update_children_outpoints (self);
+}
+
+static void
+_child_removed (GESContainer * container, GESTimelineElement * element)
+{
+  GESClip *self = GES_CLIP (container);
+
+  g_signal_handlers_disconnect_by_func (element, _child_property_changed_cb,
+      self);
+  /* NOTE: we do not test if the effect is a time effect since technically
+   * it can stop being a time effect, although this would be rare */
+  g_signal_handlers_disconnect_by_func (element,
+      _child_time_property_changed_cb, self);
+
+  if (_IS_CORE_CHILD (element))
+    _update_max_duration (container);
+
+  _update_duration_limit (self);
+  _update_children_outpoints (self);
+  ges_track_element_update_outpoint (GES_TRACK_ELEMENT (element));
+}
+
+static void
+add_clip_to_list (gpointer key, gpointer clip, GList ** list)
+{
+  *list = g_list_prepend (*list, gst_object_ref (clip));
+}
+
+/* NOTE: Since this does not change the track of @child, this should
+ * only be called if it is guaranteed that neither @from_clip nor @to_clip
+ * will not break the track rules:
+ * + no more than one core child per track
+ * + every non-core child must be in the same track as a core child
+ * NOTE: Since this does not change the creator asset of the child, this
+ * should only be called for transferring children between clips with the
+ * same asset.
+ * NOTE: This also prevents the update of the duration-limit, so you
+ * should ensure that you call _update_duration_limit on both clips when
+ * transferring has completed.
+ */
+static void
+_transfer_child (GESClip * from_clip, GESClip * to_clip,
+    GESTrackElement * child)
+{
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip);
+  gboolean prev_prevent_from = from_clip->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_to = to_clip->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint_from =
+      from_clip->priv->prevent_children_outpoint_update;
+  gboolean prev_prevent_outpoint_to =
+      to_clip->priv->prevent_children_outpoint_update;
+
+  /* We need to bump the refcount to avoid the object to be destroyed */
+  gst_object_ref (child);
+
+  /* don't want to change tracks */
+  ges_timeline_set_moving_track_elements (timeline, TRUE);
+
+  from_clip->priv->prevent_duration_limit_update = TRUE;
+  to_clip->priv->prevent_duration_limit_update = TRUE;
+  from_clip->priv->prevent_children_outpoint_update = TRUE;
+  to_clip->priv->prevent_children_outpoint_update = TRUE;
+
+  from_clip->priv->allow_any_remove = TRUE;
+  ges_container_remove (GES_CONTAINER (from_clip),
+      GES_TIMELINE_ELEMENT (child));
+  from_clip->priv->allow_any_remove = FALSE;
+
+  to_clip->priv->allow_any_track = TRUE;
+  if (!ges_container_add (GES_CONTAINER (to_clip),
+          GES_TIMELINE_ELEMENT (child)))
+    GST_ERROR ("%" GES_FORMAT " could not add child %p while"
+        " transfering, this should never happen", GES_ARGS (to_clip), child);
+  to_clip->priv->allow_any_track = FALSE;
+  ges_timeline_set_moving_track_elements (timeline, FALSE);
+
+  from_clip->priv->prevent_duration_limit_update = prev_prevent_from;
+  to_clip->priv->prevent_duration_limit_update = prev_prevent_to;
+  from_clip->priv->prevent_children_outpoint_update =
+      prev_prevent_outpoint_from;
+  to_clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint_to;
+
+  gst_object_unref (child);
+}
+
+static GList *
+_ungroup (GESContainer * container, gboolean recursive)
+{
+  GESClip *tmpclip;
+  GESTrackType track_type;
+  GESTrackElement *track_element;
+
+  gboolean first_obj = TRUE;
+  GList *tmp, *children, *ret = NULL;
+  GESClip *clip = GES_CLIP (container);
+  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
+  GESLayer *layer = clip->priv->layer;
+  GHashTable *_tracktype_clip = g_hash_table_new (g_int_hash, g_int_equal);
+
+  /* If there is no TrackElement, just return @container in a list */
+  if (GES_CONTAINER_CHILDREN (container) == NULL) {
+    GST_DEBUG ("No TrackElement, simply returning");
+    return g_list_prepend (ret, container);
+  }
+
+  children = ges_container_get_children (container, FALSE);
+  /* _add_child will add core elements at the lowest priority and new
+   * non-core effects at the lowest effect priority, so we need to add
+   * the highest priority children first to preserve the effect order.
+   * @children is already ordered by highest priority first */
+  for (tmp = children; tmp; tmp = tmp->next) {
+    track_element = GES_TRACK_ELEMENT (tmp->data);
+    track_type = ges_track_element_get_track_type (track_element);
+
+    tmpclip = g_hash_table_lookup (_tracktype_clip, &track_type);
+    if (tmpclip == NULL) {
+      if (G_UNLIKELY (first_obj == TRUE)) {
+        tmpclip = clip;
+        first_obj = FALSE;
+      } else {
+        tmpclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
+        if (layer) {
+          /* Add new container to the same layer as @container */
+          ges_clip_set_moving_from_layer (tmpclip, TRUE);
+          /* adding to the same layer should not fail when moving */
+          ges_layer_add_clip (layer, tmpclip);
+          ges_clip_set_moving_from_layer (tmpclip, FALSE);
+        }
+      }
+
+      g_hash_table_insert (_tracktype_clip, &track_type, tmpclip);
+      ges_clip_set_supported_formats (tmpclip, track_type);
+    }
+
+    /* Move trackelement to the container it is supposed to land into */
+    /* Note: it is safe to transfer the element whilst not changing tracks
+     * because all track elements in the same track will stay in the
+     * same clip */
+    if (tmpclip != clip)
+      _transfer_child (clip, tmpclip, track_element);
+  }
+  g_list_free_full (children, gst_object_unref);
+  g_hash_table_foreach (_tracktype_clip, (GHFunc) add_clip_to_list, &ret);
+  g_hash_table_unref (_tracktype_clip);
+
+  /* Need to update the duration limit.
+   * Since we have divided the clip by its tracks, the duration-limit,
+   * which is a minimum value calculated per track, can only increase in
+   * value, which means the duration of the clip should not change, which
+   * means updating should always be possible */
+  for (tmp = ret; tmp; tmp = tmp->next)
+    _update_duration_limit (tmp->data);
+
+  return ret;
+}
+
+static gboolean
+_group_test_share_track (GESClip * clip1, GESClip * clip2)
+{
+  GList *tmp1, *tmp2;
+  GESTrackElement *child1, *child2;
+  for (tmp1 = GES_CONTAINER_CHILDREN (clip1); tmp1; tmp1 = tmp1->next) {
+    child1 = tmp1->data;
+    for (tmp2 = GES_CONTAINER_CHILDREN (clip2); tmp2; tmp2 = tmp2->next) {
+      child2 = tmp2->data;
+      if (ges_track_element_get_track (child1)
+          == ges_track_element_get_track (child2)) {
+        GST_INFO_OBJECT (clip1, "Cannot group with clip %" GES_FORMAT
+            " because its child %" GES_FORMAT " shares the same "
+            "track with our child %" GES_FORMAT, GES_ARGS (clip2),
+            GES_ARGS (child2), GES_ARGS (child1));
+        return TRUE;
+      }
+    }
+  }
+  return FALSE;
+}
+
+#define _GROUP_TEST_EQUAL(val, expect, format) \
+if (val != expect) { \
+  GST_INFO_OBJECT (clip, "Cannot group with other clip %" GES_FORMAT \
+      " because the clip's " #expect " is %" format " rather than the " \
+      #expect " of the other clip %" format, GES_ARGS (first_clip), val, \
+      expect); \
+  return NULL; \
+}
+
+static GESContainer *
+_group (GList * containers)
+{
+  GESClip *first_clip = NULL;
+  GESTimeline *timeline;
+  GESTrackType supported_formats;
+  GESLayer *layer;
+  GList *tmp, *tmp2, *tmpclip;
+  GstClockTime start, inpoint, duration;
+  GESTimelineElement *element;
+
+  GESAsset *asset;
+  GESContainer *ret = NULL;
+
+  if (!containers)
+    return NULL;
+
+  for (tmp = containers; tmp; tmp = tmp->next) {
+    if (GES_IS_CLIP (tmp->data) == FALSE) {
+      GST_DEBUG ("Can only work with clips");
+      return NULL;
+    }
+    if (!first_clip) {
+      first_clip = tmp->data;
+      element = GES_TIMELINE_ELEMENT (first_clip);
+      start = element->start;
+      inpoint = element->inpoint;
+      duration = element->duration;
+      timeline = element->timeline;
+      layer = first_clip->priv->layer;
+      asset = ges_extractable_get_asset (GES_EXTRACTABLE (first_clip));
+    }
+  }
+
+  for (tmp = containers; tmp; tmp = tmp->next) {
+    GESClip *clip;
+    GESAsset *cmp_asset;
+
+    element = GES_TIMELINE_ELEMENT (tmp->data);
+    clip = GES_CLIP (element);
+
+    _GROUP_TEST_EQUAL (element->start, start, G_GUINT64_FORMAT);
+    _GROUP_TEST_EQUAL (element->duration, duration, G_GUINT64_FORMAT);
+    _GROUP_TEST_EQUAL (element->inpoint, inpoint, G_GUINT64_FORMAT);
+    _GROUP_TEST_EQUAL (element->timeline, timeline, GST_PTR_FORMAT);
+    _GROUP_TEST_EQUAL (clip->priv->layer, layer, GST_PTR_FORMAT);
+    cmp_asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
+    if (cmp_asset != asset) {
+      GST_INFO_OBJECT (clip, "Cannot group with other clip %"
+          GES_FORMAT " because the clip's asset is %s rather than "
+          " the asset of the other clip %s", GES_ARGS (first_clip),
+          cmp_asset ? ges_asset_get_id (cmp_asset) : NULL,
+          asset ? ges_asset_get_id (asset) : NULL);
+      return NULL;
+    }
+    /* make sure we don't share the same track */
+    for (tmp2 = tmp->next; tmp2; tmp2 = tmp2->next) {
+      if (_group_test_share_track (clip, tmp2->data))
+        return NULL;
+    }
+  }
+
+  /* And now pass all TrackElements to the first clip,
+   * and remove others from the layer (updating the supported formats) */
+  ret = containers->data;
+  supported_formats = GES_CLIP (ret)->priv->supportedformats;
+  for (tmpclip = containers->next; tmpclip; tmpclip = tmpclip->next) {
+    GESClip *cclip = tmpclip->data;
+    GList *children = ges_container_get_children (GES_CONTAINER (cclip), FALSE);
+
+    /* _add_child will add core elements at the lowest priority and new
+     * non-core effects at the lowest effect priority, so we need to add
+     * the highest priority children first to preserve the effect order.
+     * @children is already ordered by highest priority first.
+     * Priorities between children in different tracks (as tmpclips are)
+     * is not important */
+    for (tmp = children; tmp; tmp = tmp->next) {
+      GESTrackElement *celement = GES_TRACK_ELEMENT (tmp->data);
+      /* Note: it is safe to transfer the element whilst not changing
+       * tracks because the elements from different clips will have
+       * children in separate tracks. So it should not be possible for
+       * two core children to appear in the same track */
+      _transfer_child (cclip, GES_CLIP (ret), celement);
+      supported_formats |= ges_track_element_get_track_type (celement);
+    }
+    g_list_free_full (children, gst_object_unref);
+    /* duration-limit should be GST_CLOCK_TIME_NONE now that we have no
+     * children */
+    _update_duration_limit (cclip);
+
+    if (layer)
+      ges_layer_remove_clip (layer, cclip);
+  }
+
+  /* Need to update the duration limit.
+   * Each received clip C_i that has been grouped may have had a different
+   * duration-limit L_i. In each case the duration must be less than
+   * this limit, and since each clip shares the same duration, we have
+   * for each clip C_i:
+   *   duration <= L_i
+   * Thus:
+   *   duration <= min_i (L_i)
+   *
+   * Now, upon grouping each clip C_i into C, we have not changed the
+   * children properties that affect the duration-limit. And since the
+   * duration-limit is calculated as the minimum amongst the tracks of C,
+   * this means that the duration-limit for C should be
+   *   L = min_i (L_i) >= duration
+   * Therefore, we can safely set the duration-limit of C to L without
+   * changing the duration of C. */
+  _update_duration_limit (GES_CLIP (ret));
+
+  ges_clip_set_supported_formats (GES_CLIP (ret), supported_formats);
+
+  return ret;
+}
+
+void
+ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
+{
+  GList *tmp;
+  gboolean prev_prevent = clip->priv->prevent_duration_limit_update;
+  gboolean prev_prevent_outpoint = clip->priv->prevent_children_outpoint_update;
+
+  if (track == NULL)
+    return;
+  /* allow us to remove in any order */
+  clip->priv->allow_any_track = TRUE;
+  clip->priv->prevent_duration_limit_update = TRUE;
+  clip->priv->prevent_children_outpoint_update = TRUE;
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    if (ges_track_element_get_track (child) == track) {
+      if (!ges_track_remove_element (track, child))
+        GST_ERROR_OBJECT (clip, "Failed to remove child %" GES_FORMAT
+            " from the track %" GST_PTR_FORMAT, GES_ARGS (child), track);
+    }
+  }
+  clip->priv->allow_any_track = FALSE;
+  clip->priv->prevent_duration_limit_update = prev_prevent;
+  clip->priv->prevent_children_outpoint_update = prev_prevent_outpoint;
+  _update_duration_limit (clip);
+  _update_children_outpoints (clip);
+}
+
+static GESTrackElement *
+_copy_track_element_to (GESTrackElement * orig, GESClip * to_clip,
+    GstClockTime position)
+{
+  GESTrackElement *copy;
+  GESTimelineElement *el_copy, *el_orig;
+
+  /* NOTE: we do not deep copy the track element, we instead call
+   * ges_track_element_copy_properties explicitly, which is the
+   * deep_copy for the GESTrackElementClass. */
+  el_orig = GES_TIMELINE_ELEMENT (orig);
+  el_copy = ges_timeline_element_copy (el_orig, FALSE);
+
+  if (el_copy == NULL)
+    return NULL;
+
+  copy = GES_TRACK_ELEMENT (el_copy);
+  ges_track_element_copy_properties (el_orig, el_copy);
+  /* NOTE: control bindings that are not registered in GES are not
+   * handled */
+  ges_track_element_copy_bindings (orig, copy, position);
+
+  ges_track_element_set_creator_asset (copy,
+      ges_track_element_get_creator_asset (orig));
+
+  return copy;
+}
+
+static GESTrackElement *
+ges_clip_copy_track_element_into (GESClip * clip, GESTrackElement * orig,
+    GstClockTime position)
+{
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+  GESTrackElement *copy;
+
+  copy = _copy_track_element_to (orig, clip, position);
+  if (copy == NULL) {
+    GST_ERROR_OBJECT (clip, "Failed to create a copy of the "
+        "element %" GES_FORMAT " for the clip", GES_ARGS (orig));
+    return NULL;
+  }
+
+  gst_object_ref (copy);
+  ges_timeline_set_moving_track_elements (timeline, TRUE);
+  if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (copy))) {
+    GST_ERROR_OBJECT (clip, "Failed to add the copied child track "
+        "element %" GES_FORMAT " to the clip", GES_ARGS (copy));
+    ges_timeline_set_moving_track_elements (timeline, FALSE);
+    gst_object_unref (copy);
+    return NULL;
+  }
+  ges_timeline_set_moving_track_elements (timeline, FALSE);
+  /* now owned by the clip */
+  gst_object_unref (copy);
+
+  return copy;
+}
+
+static void
+_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
+{
+  GList *tmp;
+  GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
+  GESTrackElement *el, *el_copy;
+
+  /* NOTE: this should only be called on a newly created @copy, so
+   * its copied_track_elements, and copied_layer, should be free to set
+   * without disposing of the previous values */
+  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+    el = GES_TRACK_ELEMENT (tmp->data);
+
+    el_copy = _copy_track_element_to (el, ccopy, GST_CLOCK_TIME_NONE);
+    if (!el_copy) {
+      GST_ERROR_OBJECT (element, "Failed to copy the track element %"
+          GES_FORMAT " for pasting", GES_ARGS (el));
+      continue;
+    }
+    /* owned by copied_track_elements */
+    gst_object_ref_sink (el_copy);
+
+    /* _add_child will add core elements at the lowest priority and new
+     * non-core effects at the lowest effect priority, so we need to add
+     * the highest priority children first to preserve the effect order.
+     * The clip's children are already ordered by highest priority first.
+     * So we order copied_track_elements in the same way */
+    ccopy->priv->copied_track_elements =
+        g_list_append (ccopy->priv->copied_track_elements, el_copy);
+  }
+
+  ccopy->priv->copied_layer = g_object_ref (self->priv->layer);
+  ccopy->priv->copied_timeline = self->priv->layer->timeline;
+}
+
+static GESTimelineElement *
+_paste (GESTimelineElement * element, GESTimelineElement * ref,
+    GstClockTime paste_position)
+{
+  GList *tmp;
+  GESClip *self = GES_CLIP (element);
+  GESLayer *layer = self->priv->copied_layer;
+  GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
+
+  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
+
+  /* paste in order of priority (highest first) */
+  for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next)
+    ges_clip_copy_track_element_into (nclip, tmp->data, GST_CLOCK_TIME_NONE);
+
+  if (layer) {
+    if (layer->timeline != self->priv->copied_timeline) {
+      GST_WARNING_OBJECT (self, "Cannot be pasted into the layer %"
+          GST_PTR_FORMAT " because its timeline has changed", layer);
+      gst_object_ref_sink (nclip);
+      gst_object_unref (nclip);
+      return NULL;
+    }
+
+    /* adding the clip to the layer will add it to the tracks, but not
+     * necessarily the same ones depending on select-tracks-for-object */
+    if (!ges_layer_add_clip (layer, nclip)) {
+      GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
+          GES_ARGS (element), GST_TIME_ARGS (paste_position));
+
+      return NULL;
+    }
+  }
+
+  /* NOTE: self should not be used and be freed after this call, so we can
+   * leave the freeing of copied_layer and copied_track_elements to the
+   * dispose method */
+
+  return GES_TIMELINE_ELEMENT (nclip);
+}
+
+static gboolean
+_lookup_child (GESTimelineElement * self, const gchar * prop_name,
+    GObject ** child, GParamSpec ** pspec)
+{
+  GList *tmp;
+
+  if (GES_TIMELINE_ELEMENT_CLASS (ges_clip_parent_class)->lookup_child (self,
+          prop_name, child, pspec))
+    return TRUE;
+
+  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
+    if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+/****************************************************
+ *                                                  *
+ *    GObject virtual methods implementation        *
+ *                                                  *
+ ****************************************************/
+static void
+ges_clip_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GESClip *clip = GES_CLIP (object);
+
+  switch (property_id) {
+    case PROP_LAYER:
+      g_value_set_object (value, clip->priv->layer);
+      break;
+    case PROP_SUPPORTED_FORMATS:
+      g_value_set_flags (value, clip->priv->supportedformats);
+      break;
+    case PROP_DURATION_LIMIT:
+      g_value_set_uint64 (value, clip->priv->duration_limit);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
 }
 
@@ -769,817 +2498,2022 @@ static void
 ges_clip_set_property (GObject * object, guint property_id,
     const GValue * value, GParamSpec * pspec)
 {
-  GESClip *clip = GES_CLIP (object);
+  GESClip *clip = GES_CLIP (object);
+
+  switch (property_id) {
+    case PROP_SUPPORTED_FORMATS:
+      ges_clip_set_supported_formats (clip, g_value_get_flags (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+ges_clip_dispose (GObject * object)
+{
+  GESClip *self = GES_CLIP (object);
+
+  self->priv->allow_any_remove = TRUE;
+
+  g_list_free_full (self->priv->copied_track_elements, gst_object_unref);
+  self->priv->copied_track_elements = NULL;
+  g_clear_object (&self->priv->copied_layer);
+
+  g_clear_error (&self->priv->add_error);
+  self->priv->add_error = NULL;
+  g_clear_error (&self->priv->remove_error);
+  self->priv->remove_error = NULL;
+
+  G_OBJECT_CLASS (ges_clip_parent_class)->dispose (object);
+}
+
+
+static void
+ges_clip_class_init (GESClipClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
+  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
+
+  object_class->get_property = ges_clip_get_property;
+  object_class->set_property = ges_clip_set_property;
+  object_class->dispose = ges_clip_dispose;
+  klass->create_track_elements = ges_clip_create_track_elements_func;
+  klass->create_track_element = NULL;
+
+  /**
+   * GESClip:supported-formats:
+   *
+   * The #GESTrackType-s that the clip supports, which it can create
+   * #GESTrackElement-s for. Note that this can be a combination of
+   * #GESTrackType flags to indicate support for several
+   * #GESTrackElement:track-type elements.
+   */
+  properties[PROP_SUPPORTED_FORMATS] = g_param_spec_flags ("supported-formats",
+      "Supported formats", "Formats supported by the clip",
+      GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO,
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+  g_object_class_install_property (object_class, PROP_SUPPORTED_FORMATS,
+      properties[PROP_SUPPORTED_FORMATS]);
+
+  /**
+   * GESClip:layer:
+   *
+   * The layer this clip lies in.
+   *
+   * If you want to connect to this property's #GObject::notify signal,
+   * you should connect to it with g_signal_connect_after() since the
+   * signal emission may be stopped internally.
+   */
+  properties[PROP_LAYER] = g_param_spec_object ("layer", "Layer",
+      "The GESLayer where this clip is being used.",
+      GES_TYPE_LAYER, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_property (object_class, PROP_LAYER,
+      properties[PROP_LAYER]);
+
+  /**
+   * GESClip:duration-limit:
+   *
+   * The maximum #GESTimelineElement:duration that can be *currently* set
+   * for the clip, taking into account the #GESTimelineElement:in-point,
+   * #GESTimelineElement:max-duration, #GESTrackElement:active, and
+   * #GESTrackElement:track properties of its children, as well as any
+   * time effects. If there is no limit, this will be set to
+   * #GST_CLOCK_TIME_NONE.
+   *
+   * Note that whilst a clip has no children in any tracks, the limit will
+   * be unknown, and similarly set to #GST_CLOCK_TIME_NONE.
+   *
+   * If the duration-limit would ever go below the current
+   * #GESTimelineElement:duration of the clip due to a change in the above
+   * variables, its #GESTimelineElement:duration will be set to the new
+   * limit.
+   *
+   * Since: 1.18
+   */
+  properties[PROP_DURATION_LIMIT] =
+      g_param_spec_uint64 ("duration-limit", "Duration Limit",
+      "A limit on the duration of the clip", 0, G_MAXUINT64,
+      GST_CLOCK_TIME_NONE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_property (object_class, PROP_DURATION_LIMIT,
+      properties[PROP_DURATION_LIMIT]);
+
+  element_class->set_start = _set_start;
+  element_class->set_duration = _set_duration;
+  element_class->set_inpoint = _set_inpoint;
+  element_class->set_priority = _set_priority;
+  element_class->set_max_duration = _set_max_duration;
+  element_class->paste = _paste;
+  element_class->deep_copy = _deep_copy;
+  element_class->lookup_child = _lookup_child;
+  element_class->get_layer_priority = _get_layer_priority;
+  element_class->get_natural_framerate = _get_natural_framerate;
+
+  container_class->add_child = _add_child;
+  container_class->remove_child = _remove_child;
+  container_class->child_removed = _child_removed;
+  container_class->child_added = _child_added;
+  container_class->ungroup = _ungroup;
+  container_class->group = _group;
+  container_class->grouping_priority = G_MAXUINT;
+}
+
+static void
+ges_clip_init (GESClip * self)
+{
+  self->priv = ges_clip_get_instance_private (self);
+  self->priv->duration_limit = GST_CLOCK_TIME_NONE;
+}
+
+/**
+ * ges_clip_create_track_element:
+ * @clip: A #GESClip
+ * @type: The track to create an element for
+ *
+ * Creates the core #GESTrackElement of the clip, of the given track type.
+ *
+ * Returns: (transfer floating) (nullable): The element created
+ * by @clip, or %NULL if @clip can not provide a track element for the
+ * given @type or an error occurred.
+ */
+GESTrackElement *
+ges_clip_create_track_element (GESClip * clip, GESTrackType type)
+{
+  GESClipClass *class;
+  GESTrackElement *res;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+
+  GST_DEBUG_OBJECT (clip, "Creating track element for %s",
+      ges_track_type_name (type));
+  if (!(type & clip->priv->supportedformats)) {
+    GST_DEBUG_OBJECT (clip, "We don't support this track type %i", type);
+    return NULL;
+  }
+
+  class = GES_CLIP_GET_CLASS (clip);
+
+  if (G_UNLIKELY (class->create_track_element == NULL)) {
+    GST_ERROR ("No 'create_track_element' implementation available fo type %s",
+        G_OBJECT_TYPE_NAME (clip));
+    return NULL;
+  }
+
+  res = class->create_track_element (clip, type);
+  return res;
+
+}
+
+/**
+ * ges_clip_create_track_elements:
+ * @clip: A #GESClip
+ * @type: The track-type to create elements for
+ *
+ * Creates the core #GESTrackElement-s of the clip, of the given track
+ * type.
+ *
+ * Returns: (transfer container) (element-type GESTrackElement): A list of
+ * the #GESTrackElement-s created by @clip for the given @type, or %NULL
+ * if no track elements are created or an error occurred.
+ */
+
+GList *
+ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
+{
+  GList *tmp, *ret;
+  GESClipClass *klass;
+  gboolean already_created = FALSE;
+  GESAsset *asset;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+
+  if ((clip->priv->supportedformats & type) == 0)
+    return NULL;
+
+  klass = GES_CLIP_GET_CLASS (clip);
+
+  if (!(klass->create_track_elements)) {
+    GST_WARNING ("no GESClip::create_track_elements implentation");
+    return NULL;
+  }
+
+  GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s",
+      ges_track_type_name (type));
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data);
+
+    if (_IS_CORE_CHILD (child)
+        && ges_track_element_get_track_type (child) & type) {
+      /* assume the core track elements have all been created if we find
+       * at least one core child with the same type */
+      already_created = TRUE;
+      break;
+    }
+  }
+  if (already_created)
+    return NULL;
+
+  ret = klass->create_track_elements (clip, type);
+  asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
+  for (tmp = ret; tmp; tmp = tmp->next)
+    ges_track_element_set_creator_asset (tmp->data, asset);
+  return ret;
+}
+
+/*
+ * default implementation of GESClipClass::create_track_elements
+ */
+GList *
+ges_clip_create_track_elements_func (GESClip * clip, GESTrackType type)
+{
+  GESTrackElement *result;
+
+  GST_DEBUG_OBJECT (clip, "Creating trackelement for track: %s",
+      ges_track_type_name (type));
+  result = ges_clip_create_track_element (clip, type);
+  if (!result) {
+    GST_DEBUG ("Did not create track element");
+    return NULL;
+  }
+
+  return g_list_append (NULL, result);
+}
+
+void
+ges_clip_set_layer (GESClip * clip, GESLayer * layer)
+{
+  if (layer == clip->priv->layer)
+    return;
+
+  clip->priv->layer = layer;
+
+  GST_DEBUG ("clip:%p, layer:%p", clip, layer);
+
+  /* We do not want to notify the setting of layer = NULL when
+   * it is actually the result of a move between layer (as we know
+   * that it will be added to another layer right after, and this
+   * is what imports here.) */
+  if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING))
+    g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
+}
+
+/**
+ * ges_clip_set_moving_from_layer:
+ * @clip: A #GESClip
+ * @is_moving: %TRUE if you want to start moving @clip to another layer
+ * %FALSE when you finished moving it
+ *
+ * Sets the clip in a moving to layer state. You might rather use the
+ * ges_clip_move_to_layer function to move #GESClip-s
+ * from a layer to another.
+ **/
+void
+ges_clip_set_moving_from_layer (GESClip * clip, gboolean is_moving)
+{
+  g_return_if_fail (GES_IS_CLIP (clip));
+
+  if (is_moving)
+    ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
+  else
+    ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
+}
+
+/**
+ * ges_clip_is_moving_from_layer:
+ * @clip: A #GESClip
+ *
+ * Tells you if the clip is currently moving from a layer to another.
+ * You might rather use the ges_clip_move_to_layer function to
+ * move #GESClip-s from a layer to another.
+ *
+ * Returns: %TRUE if @clip is currently moving from its current layer
+ * %FALSE otherwize.
+ **/
+gboolean
+ges_clip_is_moving_from_layer (GESClip * clip)
+{
+  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+
+  return ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING);
+}
+
+/**
+ * ges_clip_move_to_layer_full:
+ * @clip: A #GESClip
+ * @layer: The new layer
+ * @error: (nullable): Return location for an error
+ *
+ * Moves a clip to a new layer. If the clip already exists in a layer, it
+ * is first removed from its current layer before being added to the new
+ * layer.
+ *
+ * Returns: %TRUE if @clip was successfully moved to @layer.
+ * Since: 1.18
+ */
+gboolean
+ges_clip_move_to_layer_full (GESClip * clip, GESLayer * layer, GError ** error)
+{
+  gboolean ret = FALSE;
+  GESLayer *current_layer;
+  GESTimelineElement *element;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
+
+  element = GES_TIMELINE_ELEMENT (clip);
+  current_layer = clip->priv->layer;
+
+  if (current_layer == layer) {
+    GST_INFO_OBJECT (clip, "Already in the layer %" GST_PTR_FORMAT, layer);
+    return TRUE;
+  }
+
+
+  if (current_layer == NULL) {
+    GST_DEBUG ("Not moving %p, only adding it to %p", clip, layer);
+
+    return ges_layer_add_clip (layer, clip);
+  }
+
+  if (element->timeline != layer->timeline) {
+    /* make sure we can perform the can_move_element_check in the timeline
+     * of the layer */
+    GST_WARNING_OBJECT (layer, "Cannot move clip %" GES_FORMAT " into "
+        "the layer because its timeline %" GST_PTR_FORMAT " does not "
+        "match the timeline of the layer %" GST_PTR_FORMAT,
+        GES_ARGS (clip), element->timeline, layer->timeline);
+    return FALSE;
+  }
+
+  if (layer->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (clip)) {
+    /* move to new layer, also checks moving of toplevel */
+    return timeline_tree_move (timeline_get_tree (layer->timeline),
+        element, (gint64) ges_layer_get_priority (current_layer) -
+        (gint64) ges_layer_get_priority (layer), 0, GES_EDGE_NONE, 0, error);
+  }
+
+  gst_object_ref (clip);
+  ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
+
+  GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer,
+      ges_layer_get_priority (layer));
+
+  ret = ges_layer_remove_clip (current_layer, clip);
+
+  if (!ret) {
+    goto done;
+  }
+
+  ret = ges_layer_add_clip (layer, clip);
+
+  if (ret) {
+    g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
+  } else {
+    /* try and move back into the original layer */
+    ges_layer_add_clip (current_layer, clip);
+  }
+
+done:
+  ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
+  gst_object_unref (clip);
+
+  return ret && (clip->priv->layer == layer);
+}
+
+/**
+ * ges_clip_move_to_layer:
+ * @clip: A #GESClip
+ * @layer: The new layer
+ *
+ * See ges_clip_move_to_layer_full(), which also gives an error.
+ *
+ * Returns: %TRUE if @clip was successfully moved to @layer.
+ */
+gboolean
+ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
+{
+  return ges_clip_move_to_layer_full (clip, layer, NULL);
+}
 
-  switch (property_id) {
-    case PROP_SUPPORTED_FORMATS:
-      ges_clip_set_supported_formats (clip, g_value_get_flags (value));
+/**
+ * ges_clip_find_track_element:
+ * @clip: A #GESClip
+ * @track: (allow-none): The track to search in, or %NULL to search in
+ * all tracks
+ * @type: The type of track element to search for, or `G_TYPE_NONE` to
+ * match any type
+ *
+ * Finds an element controlled by the clip. If @track is given,
+ * then only the track elements in @track are searched for. If @type is
+ * given, then this function searches for a track element of the given
+ * @type.
+ *
+ * Note, if multiple track elements in the clip match the given criteria,
+ * this will return the element amongst them with the highest
+ * #GESTimelineElement:priority (numerically, the smallest). See
+ * ges_clip_find_track_elements() if you wish to find all such elements.
+ *
+ * Returns: (transfer full) (nullable): The element controlled by
+ * @clip, in @track, and of the given @type, or %NULL if no such element
+ * could be found.
+ */
+
+GESTrackElement *
+ges_clip_find_track_element (GESClip * clip, GESTrack * track, GType type)
+{
+  GList *tmp;
+  GESTrackElement *otmp;
+
+  GESTrackElement *ret = NULL;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE), NULL);
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) {
+    otmp = (GESTrackElement *) tmp->data;
+
+    if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type))
+      continue;
+
+    if ((track == NULL) || (ges_track_element_get_track (otmp) == track)) {
+      ret = GES_TRACK_ELEMENT (tmp->data);
+      gst_object_ref (ret);
       break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
   }
+
+  return ret;
 }
 
-static void
-ges_clip_dispose (GObject * object)
+/**
+ * ges_clip_get_layer:
+ * @clip: A #GESClip
+ *
+ * Gets the #GESClip:layer of the clip.
+ *
+ * Returns: (transfer full) (nullable): The layer @clip is in, or %NULL if
+ * @clip is not in any layer.
+ */
+GESLayer *
+ges_clip_get_layer (GESClip * clip)
 {
-  GESClip *self = GES_CLIP (object);
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
 
-  g_list_free_full (self->priv->copied_track_elements, g_object_unref);
-  self->priv->copied_track_elements = NULL;
-  g_clear_object (&self->priv->copied_layer);
+  if (clip->priv->layer != NULL)
+    gst_object_ref (G_OBJECT (clip->priv->layer));
 
-  G_OBJECT_CLASS (ges_clip_parent_class)->dispose (object);
+  return clip->priv->layer;
 }
 
+/**
+ * ges_clip_get_duration_limit:
+ * @clip: A #GESClip
+ *
+ * Gets the #GESClip:duration-limit of the clip.
+ *
+ * Returns: The duration-limit of @clip.
+ * Since: 1.18
+ */
+GstClockTime
+ges_clip_get_duration_limit (GESClip * clip)
+{
+  g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
 
-static void
-ges_clip_class_init (GESClipClass * klass)
+  return clip->priv->duration_limit;
+}
+
+static gint
+_cmp_children_by_priority (gconstpointer a_p, gconstpointer b_p)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
-  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
+  const GESTimelineElement *a = a_p, *b = b_p;
+  if (a->priority > b->priority)
+    return 1;
+  else if (a->priority < b->priority)
+    return -1;
+  return 0;
+}
 
-  object_class->get_property = ges_clip_get_property;
-  object_class->set_property = ges_clip_set_property;
-  object_class->dispose = ges_clip_dispose;
-  klass->create_track_elements = ges_clip_create_track_elements_func;
-  klass->create_track_element = NULL;
+/**
+ * ges_clip_add_top_effect:
+ * @clip: A #GESClip
+ * @effect: A top effect to add
+ * @index: The index to add @effect at, or -1 to add at the highest
+ * @error: (nullable): Return location for an error
+ *
+ * Add a top effect to a clip at the given index.
+ *
+ * Unlike using ges_container_add(), this allows you to set the index
+ * in advance. It will also check that no error occurred during the track
+ * selection for the effect.
+ *
+ * Note, only subclasses of #GESClipClass that have
+ * #GES_CLIP_CLASS_CAN_ADD_EFFECTS set to %TRUE (such as #GESSourceClip
+ * and #GESBaseEffectClip) can have additional top effects added.
+ *
+ * Note, if the effect is a time effect, this may be refused if the clip
+ * would not be able to adapt itself once the effect is added.
+ *
+ * Returns: %TRUE if @effect was successfully added to @clip at @index.
+ * Since: 1.18
+ */
+gboolean
+ges_clip_add_top_effect (GESClip * clip, GESBaseEffect * effect, gint index,
+    GError ** error)
+{
+  GESClipPrivate *priv;
+  GList *top_effects;
+  GESTimelineElement *replace;
+  GESTimeline *timeline;
+  gboolean res;
 
-  /**
-   * GESClip:supported-formats:
-   *
-   * The formats supported by the clip.
-   */
-  properties[PROP_SUPPORTED_FORMATS] = g_param_spec_flags ("supported-formats",
-      "Supported formats", "Formats supported by the file",
-      GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO,
-      G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
 
-  g_object_class_install_property (object_class, PROP_SUPPORTED_FORMATS,
-      properties[PROP_SUPPORTED_FORMATS]);
+  priv = clip->priv;
 
-  /**
-   * GESClip:layer:
-   *
-   * The GESLayer where this clip is being used. If you want to connect to its
-   * notify signal you should connect to it with g_signal_connect_after as the
-   * signal emission can be stop in the first fase.
-   */
-  properties[PROP_LAYER] = g_param_spec_object ("layer", "Layer",
-      "The GESLayer where this clip is being used.",
-      GES_TYPE_LAYER, G_PARAM_READABLE);
-  g_object_class_install_property (object_class, PROP_LAYER,
-      properties[PROP_LAYER]);
+  if (index >= 0) {
+    top_effects = ges_clip_get_top_effects (clip);
+    replace = g_list_nth_data (top_effects, index);
+    if (replace) {
+      priv->use_effect_priority = TRUE;
+      priv->effect_priority = replace->priority;
+    }
+    g_list_free_full (top_effects, gst_object_unref);
+  }
+  /* otherwise the default _add_child will place it at the lowest
+   * priority / highest index */
 
-  element_class->ripple = _ripple;
-  element_class->ripple_end = _ripple_end;
-  element_class->roll_start = _roll_start;
-  element_class->roll_end = _roll_end;
-  element_class->trim = _trim;
-  element_class->set_start = _set_start;
-  element_class->set_duration = _set_duration;
-  element_class->set_inpoint = _set_inpoint;
-  element_class->set_priority = _set_priority;
-  element_class->set_max_duration = _set_max_duration;
-  element_class->paste = _paste;
-  element_class->deep_copy = _deep_copy;
-  element_class->lookup_child = _lookup_child;
-  element_class->get_layer_priority = _get_layer_priority;
 
-  container_class->add_child = _add_child;
-  container_class->remove_child = _remove_child;
-  container_class->child_removed = _child_removed;
-  container_class->child_added = _child_added;
-  container_class->ungroup = _ungroup;
-  container_class->group = _group;
-  container_class->grouping_priority = G_MAXUINT;
-  container_class->edit = _edit;
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+  if (timeline)
+    ges_timeline_set_track_selection_error (timeline, FALSE, NULL);
+
+  /* note, if several tracks are selected, this may lead to several
+   * effects being added to the clip.
+   * The first effect we are adding will use the set effect_priority.
+   * The error on the timeline could be from any of the copies */
+  ges_clip_set_add_error (clip, NULL);
+  res = ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect));
+
+  priv->use_effect_priority = FALSE;
+
+  if (!res) {
+    /* if adding fails, there should have been no track selection, which
+     * means no other elements were added so the clip, so the adding error
+     * for the effect, if any, should still be available on the clip */
+    ges_clip_take_add_error (clip, error);
+    return FALSE;
+  }
+
+  if (timeline && ges_timeline_take_track_selection_error (timeline, error))
+    goto remove;
+
+  return TRUE;
+
+remove:
+  if (!ges_container_remove (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (effect)))
+    GST_ERROR_OBJECT (clip, "Failed to remove effect %" GES_FORMAT,
+        GES_ARGS (effect));
+
+  return FALSE;
 }
 
-static void
-ges_clip_init (GESClip * self)
+static gboolean
+_is_added_effect (GESClip * clip, GESBaseEffect * effect)
 {
-  self->priv = ges_clip_get_instance_private (self);
-  self->priv->layer = NULL;
-  self->priv->nb_effects = 0;
+  if (GES_TIMELINE_ELEMENT_PARENT (effect) != GES_TIMELINE_ELEMENT (clip)) {
+    GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT
+        " does not belong to this clip", GES_ARGS (effect));
+    return FALSE;
+  }
+  if (!_IS_TOP_EFFECT (effect)) {
+    GST_WARNING_OBJECT (clip, "The effect %" GES_FORMAT " is not a top "
+        "effect of this clip (it is a core element of the clip)",
+        GES_ARGS (effect));
+    return FALSE;
+  }
+  return TRUE;
 }
 
 /**
- * ges_clip_create_track_element:
- * @clip: The origin #GESClip
- * @type: The #GESTrackType to create a #GESTrackElement for.
+ * ges_clip_remove_top_effect:
+ * @clip: A #GESClip
+ * @effect: The top effect to remove
+ * @error: (nullable): Return location for an error
  *
- * Creates a #GESTrackElement for the provided @type. The clip
- * keep a reference to the newly created trackelement, you therefore need to
- * call @ges_container_remove when you are done with it.
+ * Remove a top effect from the clip.
  *
- * Returns: (transfer none) (nullable): A #GESTrackElement. Returns NULL if
- * the #GESTrackElement could not be created.
+ * Note, if the effect is a time effect, this may be refused if the clip
+ * would not be able to adapt itself once the effect is removed.
+ *
+ * Returns: %TRUE if @effect was successfully added to @clip at @index.
+ * Since: 1.18
  */
-GESTrackElement *
-ges_clip_create_track_element (GESClip * clip, GESTrackType type)
+gboolean
+ges_clip_remove_top_effect (GESClip * clip, GESBaseEffect * effect,
+    GError ** error)
 {
-  GESClipClass *class;
-  GESTrackElement *res;
+  gboolean res;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
+  if (!_is_added_effect (clip, effect))
+    return FALSE;
+
+  ges_clip_set_remove_error (clip, NULL);
+  res = ges_container_remove (GES_CONTAINER (clip),
+      GES_TIMELINE_ELEMENT (effect));
+  if (!res)
+    ges_clip_take_remove_error (clip, error);
+
+  return res;
+}
+
+/**
+ * ges_clip_get_top_effects:
+ * @clip: A #GESClip
+ *
+ * Gets the #GESBaseEffect-s that have been added to the clip. The
+ * returned list is ordered by their internal index in the clip. See
+ * ges_clip_get_top_effect_index().
+ *
+ * Returns: (transfer full) (element-type GESTrackElement): A list of all
+ * #GESBaseEffect-s that have been added to @clip.
+ */
+GList *
+ges_clip_get_top_effects (GESClip * clip)
+{
+  GList *tmp, *ret;
+  GESTimelineElement *child;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
 
-  GST_DEBUG_OBJECT (clip, "Creating track element for %s",
-      ges_track_type_name (type));
-  if (!(type & clip->priv->supportedformats)) {
-    GST_DEBUG_OBJECT (clip, "We don't support this track type %i", type);
-    return NULL;
+  GST_DEBUG_OBJECT (clip, "Getting the %i top effects", clip->priv->nb_effects);
+  ret = NULL;
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    child = tmp->data;
+    if (_IS_TOP_EFFECT (child))
+      ret = g_list_append (ret, gst_object_ref (child));
   }
 
-  class = GES_CLIP_GET_CLASS (clip);
+  return g_list_sort (ret, _cmp_children_by_priority);
+}
 
-  if (G_UNLIKELY (class->create_track_element == NULL)) {
-    GST_ERROR ("No 'create_track_element' implementation available fo type %s",
-        G_OBJECT_TYPE_NAME (clip));
-    return NULL;
+/**
+ * ges_clip_get_top_effect_index:
+ * @clip: A #GESClip
+ * @effect: The effect we want to get the index of
+ *
+ * Gets the internal index of an effect in the clip. The index of effects
+ * in a clip will run from 0 to n-1, where n is the total number of
+ * effects. If two effects share the same #GESTrackElement:track, the
+ * effect with the numerically lower index will be applied to the source
+ * data **after** the other effect, i.e. output data will always flow from
+ * a higher index effect to a lower index effect.
+ *
+ * Returns: The index of @effect in @clip, or -1 if something went wrong.
+ */
+gint
+ges_clip_get_top_effect_index (GESClip * clip, GESBaseEffect * effect)
+{
+  GList *top_effects;
+  gint ret;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), -1);
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), -1);
+  if (!_is_added_effect (clip, effect))
+    return -1;
+
+  top_effects = ges_clip_get_top_effects (clip);
+  ret = g_list_index (top_effects, effect);
+  g_list_free_full (top_effects, gst_object_unref);
+
+  return ret;
+}
+
+/* TODO 2.0 remove as it is Deprecated */
+gint
+ges_clip_get_top_effect_position (GESClip * clip, GESBaseEffect * effect)
+{
+  return ges_clip_get_top_effect_index (clip, effect);
+}
+
+/* TODO 2.0 remove as it is Deprecated */
+gboolean
+ges_clip_set_top_effect_priority (GESClip * clip,
+    GESBaseEffect * effect, guint newpriority)
+{
+  return ges_clip_set_top_effect_index (clip, effect, newpriority);
+}
+
+/**
+ * ges_clip_set_top_effect_index_full:
+ * @clip: A #GESClip
+ * @effect: An effect within @clip to move
+ * @newindex: The index for @effect in @clip
+ * @error: (nullable): Return location for an error
+ *
+ * Set the index of an effect within the clip. See
+ * ges_clip_get_top_effect_index(). The new index must be an existing
+ * index of the clip. The effect is moved to the new index, and the other
+ * effects may be shifted in index accordingly to otherwise maintain the
+ * ordering.
+ *
+ * Returns: %TRUE if @effect was successfully moved to @newindex.
+ * Since: 1.18
+ */
+gboolean
+ges_clip_set_top_effect_index_full (GESClip * clip, GESBaseEffect * effect,
+    guint newindex, GError ** error)
+{
+  gint inc;
+  GList *top_effects, *tmp;
+  GList *child_data = NULL;
+  guint32 current_prio, new_prio;
+  GESTimelineElement *element, *replace;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
+
+  if (!_is_added_effect (clip, effect))
+    return FALSE;
+
+  element = GES_TIMELINE_ELEMENT (effect);
+
+  top_effects = ges_clip_get_top_effects (clip);
+  replace = g_list_nth_data (top_effects, newindex);
+  g_list_free_full (top_effects, gst_object_unref);
+
+  if (!replace) {
+    GST_WARNING_OBJECT (clip, "Does not contain %u effects", newindex + 1);
+    return FALSE;
   }
 
-  res = class->create_track_element (clip, type);
-  return res;
+  if (replace == element)
+    return TRUE;
+
+  current_prio = element->priority;
+  new_prio = replace->priority;
+
+  if (current_prio < new_prio)
+    inc = -1;
+  else
+    inc = +1;
+
+  /* check that the duration-limit can be changed */
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+    guint32 priority = child->priority;
+    DurationLimitData *data =
+        _duration_limit_data_new (GES_TRACK_ELEMENT (child));
+
+    if (child == element)
+      data->priority = new_prio;
+    else if ((inc == +1 && priority >= new_prio && priority < current_prio)
+        || (inc == -1 && priority <= new_prio && priority > current_prio))
+      data->priority = child->priority + inc;
+
+    child_data = g_list_prepend (child_data, data);
+  }
+
+  if (!_can_update_duration_limit (clip, child_data, error)) {
+    GST_INFO_OBJECT (clip, "Cannot move top effect %" GES_FORMAT
+        " to index %i because the duration-limit cannot adjust",
+        GES_ARGS (effect), newindex);
+    return FALSE;
+  }
+
+  GST_DEBUG_OBJECT (clip, "Setting top effect %" GST_PTR_FORMAT "priority: %i",
+      effect, new_prio);
+
+  /* prevent a re-sort of the list whilst we are traversing it! */
+  clip->priv->prevent_resort = TRUE;
+  clip->priv->setting_priority = TRUE;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+    guint32 priority = child->priority;
+
+    if (child == element)
+      continue;
+
+    /* only need to change the priority for those between the new and old
+     * index */
+    if ((inc == +1 && priority >= new_prio && priority < current_prio)
+        || (inc == -1 && priority <= new_prio && priority > current_prio))
+      _set_priority0 (child, priority + inc);
+  }
+  _set_priority0 (element, new_prio);
 
+  clip->priv->prevent_resort = FALSE;
+  clip->priv->setting_priority = FALSE;
+  _ges_container_sort_children (GES_CONTAINER (clip));
+  /* height should have stayed the same */
+
+  return TRUE;
 }
 
 /**
- * ges_clip_create_track_elements:
- * @clip: The origin #GESClip
- * @type: The #GESTrackType to create each #GESTrackElement for.
+ * ges_clip_set_top_effect_index:
+ * @clip: A #GESClip
+ * @effect: An effect within @clip to move
+ * @newindex: The index for @effect in @clip
  *
- * Creates all #GESTrackElements supported by this clip for the track type.
+ * See ges_clip_set_top_effect_index_full(), which also gives an error.
  *
- * Returns: (element-type GESTrackElement) (transfer full): A #GList of
- * newly created #GESTrackElement-s
+ * Returns: %TRUE if @effect was successfully moved to @newindex.
  */
+gboolean
+ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
+    guint newindex)
+{
+  return ges_clip_set_top_effect_index_full (clip, effect, newindex, NULL);
+}
 
-GList *
-ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
+/**
+ * ges_clip_split_full:
+ * @clip: The #GESClip to split
+ * @position: The timeline position at which to perform the split, between
+ * the start and end of the clip
+ * @error: (nullable): Return location for an error
+ *
+ * Splits a clip at the given timeline position into two clips. The clip
+ * must already have a #GESClip:layer.
+ *
+ * The original clip's #GESTimelineElement:duration is reduced such that
+ * its end point matches the split position. Then a new clip is created in
+ * the same layer, whose #GESTimelineElement:start matches the split
+ * position and #GESTimelineElement:duration will be set such that its end
+ * point matches the old end point of the original clip. Thus, the two
+ * clips together will occupy the same positions in the timeline as the
+ * original clip did.
+ *
+ * The children of the new clip will be new copies of the original clip's
+ * children, so it will share the same sources and use the same
+ * operations.
+ *
+ * The new clip will also have its #GESTimelineElement:in-point set so
+ * that any internal data will appear in the timeline at the same time.
+ * Thus, when the timeline is played, the playback of data should
+ * appear the same. This may be complicated by any additional
+ * #GESEffect-s that have been placed on the original clip that depend on
+ * the playback time or change the data consumption rate of sources. This
+ * method will attempt to translate these effects such that the playback
+ * appears the same. In such complex situations, you may get a better
+ * result if you place the clip in a separate sub #GESProject, which only
+ * contains this clip (and its effects), and in the original layer
+ * create two neighbouring #GESUriClip-s that reference this sub-project,
+ * but at a different #GESTimelineElement:in-point.
+ *
+ * Returns: (transfer none) (nullable): The newly created clip resulting
+ * from the splitting @clip, or %NULL if @clip can't be split.
+ * Since: 1.18
+ */
+GESClip *
+ges_clip_split_full (GESClip * clip, guint64 position, GError ** error)
 {
-  GList *result = NULL, *tmp, *children;
-  GESClipClass *klass;
-  guint max_prio, min_prio;
-  gboolean readding_effects_only = TRUE;
+  GList *tmp, *transitions = NULL;
+  GESClip *new_object;
+  gboolean no_core = FALSE;
+  GstClockTime start, duration, old_duration, new_duration, new_inpoint;
+  GESTimelineElement *element;
+  GESTimeline *timeline;
+  GHashTable *track_for_copy;
+  guint32 layer_prio;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  g_return_val_if_fail (clip->priv->layer, NULL);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL);
+  g_return_val_if_fail (!error || !*error, NULL);
 
-  klass = GES_CLIP_GET_CLASS (clip);
+  element = GES_TIMELINE_ELEMENT (clip);
+  timeline = element->timeline;
 
-  if (!(klass->create_track_elements)) {
-    GST_WARNING ("no GESClip::create_track_elements implentation");
+  duration = element->duration;
+  start = element->start;
+
+  if (position >= start + duration || position <= start) {
+    GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT
+        " out of boundaries", GST_TIME_ARGS (position));
     return NULL;
   }
 
-  GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s",
-      ges_track_type_name (type));
-  children = ges_container_get_children (GES_CONTAINER (clip), TRUE);
-  for (tmp = children; tmp; tmp = tmp->next) {
-    GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data);
+  layer_prio = ges_timeline_element_get_layer_priority (element);
 
-    if (!ges_track_element_get_track (child)
-        && ges_track_element_get_track_type (child) & type) {
+  old_duration = position - start;
+  new_duration = duration + start - position;
+  /* convert the split position into an internal core time */
+  new_inpoint = _convert_core_time (clip, position, FALSE, &no_core, error);
 
-      GST_DEBUG_OBJECT (clip, "Removing for reusage: %" GST_PTR_FORMAT, child);
-      result = g_list_append (result, g_object_ref (child));
-      ges_container_remove (GES_CONTAINER (clip), tmp->data);
-      if (!GES_IS_BASE_EFFECT (child))
-        readding_effects_only = FALSE;
-    }
+  /* if the split clip does not contain any active core elements with
+   * an internal source, just set the in-point to 0 for the new_object */
+  if (no_core)
+    new_inpoint = 0;
+
+  if (!GST_CLOCK_TIME_IS_VALID (new_inpoint))
+    return NULL;
+
+  if (timeline
+      && !timeline_tree_can_move_element (timeline_get_tree (timeline), element,
+          layer_prio, start, old_duration, error)) {
+    GST_INFO_OBJECT (clip,
+        "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
+        " as timeline would be in an illegal state.", GES_ARGS (clip),
+        GST_TIME_ARGS (position));
+    return NULL;
   }
-  g_list_free_full (children, gst_object_unref);
 
-  if (readding_effects_only) {
-    result = g_list_concat (result, klass->create_track_elements (clip, type));
+  if (timeline
+      && !timeline_tree_can_move_element (timeline_get_tree (timeline), element,
+          layer_prio, position, new_duration, error)) {
+    GST_INFO_OBJECT (clip,
+        "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
+        " as timeline would be in an illegal state.", GES_ARGS (clip),
+        GST_TIME_ARGS (position));
+    return NULL;
   }
 
-  _get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
-  for (tmp = result; tmp; tmp = tmp->next) {
-    GESTimelineElement *elem = tmp->data;
+  GST_DEBUG_OBJECT (clip, "Spliting at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (position));
+
+  /* Create the new Clip */
+  new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE));
+  new_object->priv->prevent_duration_limit_update = TRUE;
+  new_object->priv->prevent_children_outpoint_update = TRUE;
+
+  GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
+  /* Set new timing properties on the Clip */
+  _set_start0 (GES_TIMELINE_ELEMENT (new_object), position);
+  _set_inpoint0 (GES_TIMELINE_ELEMENT (new_object), new_inpoint);
+  _set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
+
+  /* NOTE: it is technically possible that the new_object may shrink
+   * later on in this method if the clip contains any non-linear time
+   * effects, which cause the duration-limit to drop. However, this
+   * should be safe since we have already checked with timeline-tree
+   * that the split position is not in the middle of an overlap. This
+   * means that the new_object should only be overlapping another
+   * element on its end, which makes shrinking safe.
+   *
+   * The original clip, however, should not shrink if the time effects
+   * obey the property that they do not depend on how much data they
+   * receive, which should be true for the time effects supported by GES.
+   */
+
+  /* split binding before duration changes since shrinking can destroy
+   * binding values */
+  track_for_copy = g_hash_table_new_full (NULL, NULL,
+      gst_object_unref, gst_object_unref);
+
+  /* _add_child will add core elements at the lowest priority and new
+   * non-core effects at the lowest effect priority, so we need to add the
+   * highest priority children first to preserve the effect order. The
+   * clip's children are already ordered by highest priority first. */
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *copy, *orig = tmp->data;
+    GESTrack *track = ges_track_element_get_track (orig);
+    GESAutoTransition *trans;
+    gchar *meta;
 
-    _set_start0 (elem, GES_TIMELINE_ELEMENT_START (clip));
-    _set_inpoint0 (elem, GES_TIMELINE_ELEMENT_INPOINT (clip));
-    _set_duration0 (elem, GES_TIMELINE_ELEMENT_DURATION (clip));
+    copy = ges_clip_copy_track_element_into (new_object, orig, new_inpoint);
 
-    if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_MAX_DURATION (clip)))
-      ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (elem),
-          GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
+    if (!copy)
+      continue;
+
+    if (track)
+      g_hash_table_insert (track_for_copy, gst_object_ref (copy),
+          gst_object_ref (track));
 
-    _set_priority0 (elem, min_prio + clip->priv->nb_effects);
+    meta = ges_meta_container_metas_to_string (GES_META_CONTAINER (orig));
+    ges_meta_container_add_metas_from_string (GES_META_CONTAINER (copy), meta);
+    g_free (meta);
 
-    ges_container_add (GES_CONTAINER (clip), elem);
+    trans = timeline ?
+        ges_timeline_get_auto_transition_at_edge (timeline, orig,
+        GES_EDGE_END) : NULL;
+
+    if (trans) {
+      trans->frozen = TRUE;
+      ges_auto_transition_set_source (trans, copy, GES_EDGE_START);
+      transitions = g_list_append (transitions, trans);
+    }
   }
 
-  return result;
-}
+  GES_TIMELINE_ELEMENT_SET_BEING_EDITED (clip);
+  _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
+  GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (clip);
 
-/*
- * default implementation of GESClipClass::create_track_elements
- */
-GList *
-ges_clip_create_track_elements_func (GESClip * clip, GESTrackType type)
-{
-  GESTrackElement *result;
+  /* We do not want the timeline to create again TrackElement-s */
+  ges_clip_set_moving_from_layer (new_object, TRUE);
+  /* adding to the same layer should not fail when moving */
+  ges_layer_add_clip (clip->priv->layer, new_object);
+  ges_clip_set_moving_from_layer (new_object, FALSE);
 
-  GST_DEBUG_OBJECT (clip, "Creating trackelement for track: %s",
-      ges_track_type_name (type));
-  result = ges_clip_create_track_element (clip, type);
-  if (!result) {
-    GST_DEBUG ("Did not create track element");
-    return NULL;
+  /* add to the track after the duration change so we don't overlap! */
+  for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) {
+    GESTrackElement *copy = tmp->data;
+    GESTrack *track = g_hash_table_lookup (track_for_copy, copy);
+    if (track) {
+      new_object->priv->allow_any_track = TRUE;
+      ges_track_add_element (track, copy);
+      new_object->priv->allow_any_track = FALSE;
+    }
   }
 
-  return g_list_append (NULL, result);
-}
+  for (tmp = transitions; tmp; tmp = tmp->next) {
+    GESAutoTransition *trans = tmp->data;
+    trans->frozen = FALSE;
+    ges_auto_transition_update (trans);
+  }
 
-void
-ges_clip_set_layer (GESClip * clip, GESLayer * layer)
-{
-  if (layer == clip->priv->layer)
-    return;
+  g_hash_table_unref (track_for_copy);
+  g_list_free_full (transitions, gst_object_unref);
 
-  clip->priv->layer = layer;
+  new_object->priv->prevent_duration_limit_update = FALSE;
+  new_object->priv->prevent_children_outpoint_update = FALSE;
+  _update_duration_limit (new_object);
+  _update_children_outpoints (new_object);
 
-  GST_DEBUG ("clip:%p, layer:%p", clip, layer);
+  return new_object;
+}
 
-  /* We do not want to notify the setting of layer = NULL when
-   * it is actually the result of a move between layer (as we know
-   * that it will be added to another layer right after, and this
-   * is what imports here.) */
-  if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING))
-    g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
+/**
+ * ges_clip_split:
+ * @clip: The #GESClip to split
+ * @position: The timeline position at which to perform the split
+ *
+ * See ges_clip_split_full(), which also gives an error.
+ *
+ * Returns: (transfer none) (nullable): The newly created clip resulting
+ * from the splitting @clip, or %NULL if @clip can't be split.
+ */
+GESClip *
+ges_clip_split (GESClip * clip, guint64 position)
+{
+  return ges_clip_split_full (clip, position, NULL);
 }
 
 /**
- * ges_clip_set_moving_from_layer:
- * @clip: a #GESClip
- * @is_moving: %TRUE if you want to start moving @clip to another layer
- * %FALSE when you finished moving it.
+ * ges_clip_set_supported_formats:
+ * @clip: A #GESClip
+ * @supportedformats: The #GESTrackType-s supported by @clip
  *
- * Sets the clip in a moving to layer state. You might rather use the
- * ges_clip_move_to_layer function to move #GESClip-s
- * from a layer to another.
- **/
+ * Sets the #GESClip:supported-formats of the clip. This should normally
+ * only be called by subclasses, which should be responsible for updating
+ * its value, rather than the user.
+ */
 void
-ges_clip_set_moving_from_layer (GESClip * clip, gboolean is_moving)
+ges_clip_set_supported_formats (GESClip * clip, GESTrackType supportedformats)
 {
   g_return_if_fail (GES_IS_CLIP (clip));
 
-  if (is_moving)
-    ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
-  else
-    ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
+  clip->priv->supportedformats = supportedformats;
 }
 
 /**
- * ges_clip_is_moving_from_layer:
- * @clip: a #GESClip
+ * ges_clip_get_supported_formats:
+ * @clip: A #GESClip
  *
- * Tells you if the clip is currently moving from a layer to another.
- * You might rather use the ges_clip_move_to_layer function to
- * move #GESClip-s from a layer to another.
+ * Gets the #GESClip:supported-formats of the clip.
  *
- * Returns: %TRUE if @clip is currently moving from its current layer
- * %FALSE otherwize
- **/
-gboolean
-ges_clip_is_moving_from_layer (GESClip * clip)
+ * Returns: The #GESTrackType-s supported by @clip.
+ */
+GESTrackType
+ges_clip_get_supported_formats (GESClip * clip)
 {
-  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (GES_IS_CLIP (clip), GES_TRACK_TYPE_UNKNOWN);
 
-  return ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING);
+  return clip->priv->supportedformats;
 }
 
 /**
- * ges_clip_move_to_layer:
- * @clip: a #GESClip
- * @layer: the new #GESLayer
+ * ges_clip_add_asset:
+ * @clip: A #GESClip
+ * @asset: An asset with #GES_TYPE_TRACK_ELEMENT as its
+ * #GESAsset:extractable-type
  *
- * Moves @clip to @layer. If @clip is not in any layer, it adds it to
- * @layer, else, it removes it from its current layer, and adds it to @layer.
+ * Extracts a #GESTrackElement from an asset and adds it to the clip.
+ * This can be used to add effects that derive from the asset to the
+ * clip, but this method is not intended to be used to create the core
+ * elements of the clip.
  *
- * Returns: %TRUE if @clip could be moved %FALSE otherwize
+ * Returns: (transfer none)(allow-none): The newly created element, or
+ * %NULL if an error occurred.
  */
-gboolean
-ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
+/* FIXME: this is not used elsewhere in the GES library */
+GESTrackElement *
+ges_clip_add_asset (GESClip * clip, GESAsset * asset)
 {
-  gboolean ret;
-  GESLayer *current_layer;
+  GESTrackElement *element;
 
-  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
-  g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
+  g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type
+          (asset), GES_TYPE_TRACK_ELEMENT), NULL);
 
-  ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
-  if (layer->timeline
-      && !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
-          GES_TIMELINE_ELEMENT (clip),
-          ges_layer_get_priority (layer),
-          GES_TIMELINE_ELEMENT_START (clip),
-          GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
-    GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT " can't move to layer %d",
-        GES_ARGS (clip), ges_layer_get_priority (layer));
-    ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
-    return FALSE;
-  }
+  element = GES_TRACK_ELEMENT (ges_asset_extract (asset, NULL));
 
-  current_layer = clip->priv->layer;
+  if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (element)))
+    return NULL;
 
-  if (current_layer == NULL) {
-    GST_DEBUG ("Not moving %p, only adding it to %p", clip, layer);
+  return element;
 
-    return ges_layer_add_clip (layer, clip);
-  }
+}
 
-  GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer,
-      ges_layer_get_priority (layer));
+/**
+ * ges_clip_find_track_elements:
+ * @clip: A #GESClip
+ * @track: (allow-none): The track to search in, or %NULL to search in
+ * all tracks
+ * @track_type: The track-type of the track element to search for, or
+ * #GES_TRACK_TYPE_UNKNOWN to match any track type
+ * @type: The type of track element to search for, or %G_TYPE_NONE to
+ * match any type
+ *
+ * Finds the #GESTrackElement-s controlled by the clip that match the
+ * given criteria. If @track is given as %NULL and @track_type is given as
+ * #GES_TRACK_TYPE_UNKNOWN, then the search will match all elements in any
+ * track, including those with no track, and of any
+ * #GESTrackElement:track-type. Otherwise, if @track is not %NULL, but
+ * @track_type is #GES_TRACK_TYPE_UNKNOWN, then only the track elements in
+ * @track are searched for. Otherwise, if @track_type is not
+ * #GES_TRACK_TYPE_UNKNOWN, but @track is %NULL, then only the track
+ * elements whose #GESTrackElement:track-type matches @track_type are
+ * searched for. Otherwise, when both are given, the track elements that
+ * match **either** criteria are searched for. Therefore, if you wish to
+ * only find elements in a specific track, you should give the track as
+ * @track, but you should not give the track's #GESTrack:track-type as
+ * @track_type because this would also select elements from other tracks
+ * of the same type.
+ *
+ * You may also give @type to _further_ restrict the search to track
+ * elements of the given @type.
+ *
+ * Returns: (transfer full) (element-type GESTrackElement): A list of all
+ * the #GESTrackElement-s controlled by @clip, in @track or of the given
+ * @track_type, and of the given @type.
+ */
 
-  gst_object_ref (clip);
-  ret = ges_layer_remove_clip (current_layer, clip);
+GList *
+ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
+    GESTrackType track_type, GType type)
+{
+  GList *tmp;
+  GESTrackElement *otmp;
 
-  if (!ret) {
-    ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
-    gst_object_unref (clip);
-    return FALSE;
-  }
+  GList *ret = NULL;
 
-  ret = ges_layer_add_clip (layer, clip);
-  ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE &&
+          track_type == GES_TRACK_TYPE_UNKNOWN), NULL);
 
-  gst_object_unref (clip);
-  g_object_notify_by_pspec (G_OBJECT (clip), properties[PROP_LAYER]);
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) {
+    otmp = (GESTrackElement *) tmp->data;
+
+    if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type))
+      continue;
 
+    /* TODO 2.0: an AND condition, using a condition like the above type
+     * check would have made more sense here. Especially when both
+     * track != NULL and track_type != GES_TRACK_TYPE_UNKNOWN are given */
+    if ((track == NULL && track_type == GES_TRACK_TYPE_UNKNOWN) ||
+        (track != NULL && ges_track_element_get_track (otmp) == track) ||
+        (track_type != GES_TRACK_TYPE_UNKNOWN
+            && ges_track_element_get_track_type (otmp) == track_type))
+      ret = g_list_append (ret, gst_object_ref (otmp));
+  }
 
-  return ret && (clip->priv->layer == layer);
+  return ret;
 }
 
-/**
- * ges_clip_find_track_element:
- * @clip: a #GESClip
- * @track: (allow-none): a #GESTrack or NULL
- * @type: a #GType indicating the type of track element you are looking
- * for or %G_TYPE_NONE if you do not care about the track type.
+/* Convert from an internal time of a child within a clip to a
+ * ===========================================================
+ * timeline time
+ * =============
+ *
+ * Given an internal time T for some child in a clip, we want to know
+ * what the corresponding time in the timeline is.
+ *
+ * If the time T is between the in-point and out-point of the child,
+ * then we can convert to the timeline coordinates by answering:
+ *
+ * a) "What is the timeline time at which the internal data from the child
+ * found at time T appears in the timeline output?"
+ *
+ * If the time T is after the out-point of the child, we instead want to
+ * answer:
+ *
+ * b) "If we extended the clip indefinetly in the timeline, what would be
+ * the timeline time at which the internal data from the child found at
+ * time T would appear in the timeline output?"
+ *
+ * However, if the time T is before the in-point of the child, we instead
+ * want to answer a more subtle question:
+ *
+ * c) "If we set the 'in-point' of the child to T, what would we need to
+ * set the 'start' of the clip to such that the internal data from the
+ * child currently found at the *beginning* of the clip would then appear
+ * at the same timeline time?"
+ *
+ * E.g. consider the following children of a clip, all in the same track,
+ * and all active:
+ *                                T
+ *                                :
+ *          +=====================:======+
+ *          |                   _/ \_    |
+ *          |         source   ~(o_o)~   |
+ *          |                   / @ \    |
+ *          +=====================:======+
+ *          i                     :
+ *                                :
+ *          +=====================:======+
+ *          |       time-effect0  :      |  | g0
+ *          +=====================:======+  v
+ *                                :
+ *          +=====================:======+
+ *          |         overlay     :      |
+ *          +=====================:======+
+ *          i'                    :
+ *                                :
+ *          +=====================:======+
+ *          |       time-effect1  :      |  | g1
+ *          +=====================:======+  v
+ *                                :
+ * -------------------------------:-------------------timeline
+ *          S                     X
+ *
+ * where i is the in-point of the source and i' is the in-point of the
+ * overlay. Also, g0 is the sink_to_source translation function for the
+ * first time effect, and g1 is the same for the second. S is the start of
+ * the clip. The ~(o_o)~ figure is the data that appears in the source at
+ * T.
+ *
+ * Essentially, question a) wants us to convert from the time T, where the
+ * data is, which is in the internal time coordinates of the source, to
+ * the timeline time X. First, we subtract i to convert from the internal
+ * source coordinates of the source to the external source coordinates of
+ * the source, then we apply the sink_to_source translation functions,
+ * which act on external source coordinates, then add 'start' to finally
+ * convert to the timeline coordinates. So overall we have
+ *
+ *   X = S + g1(g0(T - i))
+ *
+ * To answer b), T would be beyond the end of the clip. Since g1 and g0
+ * can convert beyond the end time, we similarly compute
+ *
+ *   X = S + g1(g0(T - i))
+ *
+ * The user themselves should note that this could exceed the max-duration
+ * of any of the children.
+ *
+ * Now consider
+ *
+ *    T
+ *    :
+ *    :     +============================+
+ *    :      \_                          |
+ *    :     _o)~        source           |
+ *    :     @ \                          |
+ *    :     +============================+
+ *    :     i
+ *    :
+ *    :     +============================+
+ *    :     |       time-effect0         |  | g0
+ *    :     +============================+  v
+ *    :
+ *    :     +============================+
+ *    :     |           overlay          |
+ *    :     +============================+
+ *    :     i'
+ *    :
+ *    :     +============================+
+ *    :     |       time-effect1         |  | g1
+ *    :     +============================+  v
+ *    :
+ * ---:-----------------------------------------------timeline
+ *    X     S
+ *
+ * To do the same as a), we would need to be able to convert from T to X,
+ * but this isn't defined since the children do not extend to here. More
+ * specifically, the functions g0 and g1 are not defined for negative
+ * times. Instead, we want to answer question c). That is, we want to know
+ * what we should set the start of the clip to to keep the figure at the
+ * same timeline position if we change the in-point of the source to T.
+ *
+ * First, if we set the in-point to T, then we would have
+ *
+ *          T
+ *          :
+ *          +============================+
+ *          |   _/ \_                    |
+ *          |  ~(o_o)~        source     |
+ *          |   / @ \                    |
+ *          +============================+
+ *          :     i
+ *          :     :
+ *          +=====:======================+
+ *          |     :       time-effect0   |  | g0
+ *          +=====:======================+  v
+ *          :     :
+ *          +=====:======================+
+ *          |     :           overlay    |
+ *          +=====:======================+
+ *          :     :
+ *          +=====:======================+
+ *          |     :       time-effect1   |  | g1
+ *          +=====:======================+  v
+ *          :     :
+ * ---:-----:-----:-----------------------------------timeline
+ *    X     S     Y
+ *
+ * In order to make the figure appear at 'start' again, we would need to
+ * reduce the start of the clip by the difference between S and Y, where
+ * Y is the conversion of the previous in-point i to the timeline time.
+ *
+ * Thus,
+ *
+ *   X = S - (Y - S)
+ *     = S - (S + g1(g0(i - T)) - S)
+ *     = S - g1(g0(i - T))
+ *
+ * If this would be negative, the conversion will not be possible.
+ *
+ * Note, we are relying on the *assumption* that the translation functions
+ * *do not* change when we change the in-point. GESBaseEffect only claims
+ * to support such time effects.
+ *
+ * Note that if g0 and g1 are simply identities, and we translate the
+ * internal time using a) and b), we calculate
+ *
+ *   S + (T - i)
+ *
+ * and for c), we calculate
+ *
+ *   S - (i - T) = S + (T - i)
+ *
+ * In summary, if we are converting from internal time T to a timeline
+ * time the return is
+ *
+ *   G(T) = {  S + g1(g0(T - i))   if T >= i,
+ *          {  S - g1(g0(i - T))   otherwise.
+ *
+ * Note that the overlay did not play a role since it overall translates
+ * all received times by the identity. Note that we could similarly want
+ * to convert from an internal time in the overlay to the timeline time.
+ * This would be given by
+ *
+ *   S + g1(T - i')   if T >= i',
+ *   S - g1(i' - T)   otherwise.
+ *
+ *
+ * Convert from a timeline time to an internal time of a child
+ * ===========================================================
+ * in a clip
+ * =========
+ *
+ * We basically want to reverse the previous conversion. Specifically,
+ * when the timeline time X is between the start and end of the clip we
+ * want to answer:
+ *
+ * d) "What is the internal time at which the data from the child that
+ * appears in the timeline at time X is created in the child?"
+ *
+ * If the time X is after the end of the clip, we instead want to answer:
+ *
+ * e) "If we extended the clip indefinetly in the timeline, what would be
+ * the internal time at which the data from the child that appears in the
+ * timeline at time T would be created in the child?"
+ *
+ * However, if the time X is before the start of the child, we instead
+ * want to answer:
+ *
+ * f) "If we set the 'start' of the clip to X, what would we need to
+ * set the 'in-point' of the clip to such that the internal data from the
+ * child currently found at the *beginning* of the clip would then appear
+ * at the same timeline time?"
+ *
+ * Following the same arguments, these would all be answered by
+ *
+ *   F(X) = {  i + f0(f1(X - S))   if X >= S,
+ *          {  i - f0(f1(S - X))   otherwise.
+ *
+ * where f0 and f1 are the corresponding source_to_sink translation
+ * functions, which should be close reverses of g0 and g1, respectively.
+ *
+ * Note that this does indeed reverse the internal to timeline conversion:
+ *
+ *   F(G(T)) = {  i + f0(f1(G(T) - S))   if G(T) >= S,
+ *             {  i - f0(f1(S - G(T)))   otherwise.
+ *
+ * but, since g1 and g0 map from [0,inf) to [0,inf),
+ *
+ *   G(T) - S = {  + g1(g0(T - i))   if T >= i,
+ *              {  - g1(g0(i - T))   otherwise.
+ *            { >= 0                 if T >= i,
+ *            { = 0                  if (T < i and g1(g0(i - T)) = 0)
+ *            { < 0                  otherwise.
+ *
+ * =>   ( G(T) >= S  <==>  T >= i or (T < i and g1(g0(i - T)) = 0) )
+ *
+ * therefore
+ *   F(G(T)) = {  i + f0(f1(g1(g0(T - i))))   if T >= i,
+ *             {  i + f0(f1(0))               if T < i
+ *             {                              and g1(g0(i - T)) = 0,
+ *             {  i - f0(f1(g1(g0(i - T))))   otherwise
  *
- * Finds the #GESTrackElement controlled by @clip that is used in @track. You
- * may optionally specify a GType to further narrow search criteria.
+ *           = {  i + f0(f1(g1(g0(T - i))))   if T >= i,
+ *             {  i - f0(f1(g1(g0(i - T))))   otherwise
  *
- * Note: If many objects match, then the one with the highest priority will be
- * returned.
+ *           = T
  *
- * Returns: (transfer full) (nullable): The #GESTrackElement used by @track,
- * else %NULL. Unref after usage
+ * because f1 reverses g1, and f0 reverses g0.
  */
 
-GESTrackElement *
-ges_clip_find_track_element (GESClip * clip, GESTrack * track, GType type)
+/* returns higher priority first */
+static GList *
+_active_time_effects_in_track_after_priority (GESClip * clip,
+    GESTrack * track, guint32 priority)
 {
-  GList *tmp;
-  GESTrackElement *otmp;
-
-  GESTrackElement *ret = NULL;
-
-  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
-  g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE), NULL);
-
-  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) {
-    otmp = (GESTrackElement *) tmp->data;
+  GList *tmp, *list = NULL;
 
-    if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type))
-      continue;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
 
-    if ((track == NULL) || (ges_track_element_get_track (otmp) == track)) {
-      ret = GES_TRACK_ELEMENT (tmp->data);
-      gst_object_ref (ret);
-      break;
-    }
+    if (GES_IS_TIME_EFFECT (child)
+        && ges_track_element_get_track (child) == track
+        && ges_track_element_is_active (child)
+        && _PRIORITY (child) < priority)
+      list = g_list_prepend (list, child);
   }
 
-  return ret;
+  return g_list_sort (list, _cmp_children_by_priority);
 }
 
 /**
- * ges_clip_get_layer:
- * @clip: a #GESClip
+ * ges_clip_get_timeline_time_from_internal_time:
+ * @clip: A #GESClip
+ * @child: An #GESTrackElement:active child of @clip with a
+ * #GESTrackElement:track
+ * @internal_time: A time in the internal time coordinates of @child
+ * @error: (nullable): Return location for an error
+ *
+ * Convert the internal source time from the child to a timeline time.
+ * This will take any time effects placed on the clip into account (see
+ * #GESBaseEffect for what time effects are supported, and how to
+ * declare them in GES).
  *
- * Get the #GESLayer to which this clip belongs.
+ * When @internal_time is above the #GESTimelineElement:in-point of
+ * @child, this will return the timeline time at which the internal
+ * content found at @internal_time appears in the output of the timeline's
+ * track. For example, this would let you know where in the timeline a
+ * particular scene in a media file would appear.
  *
- * Returns: (transfer full) (nullable): The #GESLayer where this @clip is being
- * used, or %NULL if it is not used on any layer. The caller should unref it
- * usage.
+ * This will be done assuming the clip has an indefinite end, so the
+ * timeline time may be beyond the end of the clip, or even breaking its
+ * #GESClip:duration-limit.
+ *
+ * If, instead, @internal_time is below the current
+ * #GESTimelineElement:in-point of @child, this will return what you would
+ * need to set the #GESTimelineElement:start of @clip to if you set the
+ * #GESTimelineElement:in-point of @child to @internal_time and wanted to
+ * keep the content of @child currently found at the current
+ * #GESTimelineElement:start of @clip at the same timeline position. If
+ * this would be negative, the conversion fails. This is useful for
+ * determining what position to use in a #GES_EDIT_MODE_TRIM if you wish
+ * to trim to a specific point in the internal content, such as a
+ * particular scene in a media file.
+ *
+ * Note that whilst a clip has no time effects, this second return is
+ * equivalent to finding the timeline time at which the content of @child
+ * at @internal_time would be found in the timeline if it had indefinite
+ * extent in both directions. However, with non-linear time effects this
+ * second return will be more distinct.
+ *
+ * In either case, the returned time would be appropriate to use in
+ * ges_timeline_element_edit() for #GES_EDIT_MODE_TRIM, and similar, if
+ * you wish to use a particular internal point as a reference. For
+ * example, you could choose to end a clip at a certain internal
+ * 'out-point', similar to the #GESTimelineElement:in-point, by
+ * translating the desired end time into the timeline coordinates, and
+ * using this position to trim the end of a clip.
+ *
+ * See ges_clip_get_internal_time_from_timeline_time(), which performs the
+ * reverse, or ges_clip_get_timeline_time_from_source_frame() which does
+ * the same conversion, but using frame numbers.
+ *
+ * Returns: The time in the timeline coordinates corresponding to
+ * @internal_time, or #GST_CLOCK_TIME_NONE if the conversion could not be
+ * performed.
+ *
+ * Since: 1.18
  */
-GESLayer *
-ges_clip_get_layer (GESClip * clip)
+GstClockTime
+ges_clip_get_timeline_time_from_internal_time (GESClip * clip,
+    GESTrackElement * child, GstClockTime internal_time, GError ** error)
 {
-  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  GstClockTime inpoint, start, external_time;
+  gboolean decrease;
+  GESTrack *track;
+  GList *tmp, *time_effects;
 
-  if (clip->priv->layer != NULL)
-    gst_object_ref (G_OBJECT (clip->priv->layer));
+  g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE);
 
-  return clip->priv->layer;
-}
+  if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
+    GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
+        "a child of the clip", GES_ARGS (child));
+    return GST_CLOCK_TIME_NONE;
+  }
 
-/**
- * ges_clip_get_top_effects:
- * @clip: The origin #GESClip
- *
- * Get effects applied on @clip
- *
- * Returns: (transfer full) (element-type GESTrackElement): a #GList of the
- * #GESBaseEffect that are applied on @clip order by ascendant priorities.
- * The refcount of the objects will be increased. The user will have to
- * unref each #GESBaseEffect and free the #GList.
- */
-GList *
-ges_clip_get_top_effects (GESClip * clip)
-{
-  GList *tmp, *ret;
-  guint i;
+  track = ges_track_element_get_track (child);
 
-  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  if (!track) {
+    GST_WARNING_OBJECT (clip, "Cannot convert the internal time of the "
+        "child %" GES_FORMAT " to a timeline time because it is not part "
+        "of a track", GES_ARGS (child));
+    return GST_CLOCK_TIME_NONE;
+  }
 
-  GST_DEBUG_OBJECT (clip, "Getting the %i top effects", clip->priv->nb_effects);
-  ret = NULL;
+  if (!ges_track_element_is_active (child)) {
+    GST_WARNING_OBJECT (clip, "Cannot convert the internal time of the "
+        "child %" GES_FORMAT " to a timeline time because it is not "
+        "active in its track", GES_ARGS (child));
+    return GST_CLOCK_TIME_NONE;
+  }
+
+  if (internal_time == GST_CLOCK_TIME_NONE)
+    return GST_CLOCK_TIME_NONE;
 
-  for (tmp = GES_CONTAINER_CHILDREN (clip), i = 0;
-      i < clip->priv->nb_effects; tmp = tmp->next, i++) {
-    ret = g_list_append (ret, gst_object_ref (tmp->data));
+  inpoint = _INPOINT (child);
+  if (inpoint <= internal_time) {
+    decrease = FALSE;
+    external_time = internal_time - inpoint;
+  } else {
+    decrease = TRUE;
+    external_time = inpoint - internal_time;
   }
 
-  return g_list_sort (ret, (GCompareFunc) element_start_compare);
-}
+  time_effects = _active_time_effects_in_track_after_priority (clip, track,
+      _PRIORITY (child));
 
-/**
- * ges_clip_get_top_effect_index:
- * @clip: The origin #GESClip
- * @effect: The #GESBaseEffect we want to get the top index from
- *
- * Gets the index position of an effect.
- *
- * Returns: The top index of the effect, -1 if something went wrong.
- */
-gint
-ges_clip_get_top_effect_index (GESClip * clip, GESBaseEffect * effect)
-{
-  guint max_prio, min_prio;
+  /* currently ordered with highest priority (closest to the timeline)
+   * first, with @child being at the *end* of the list.
+   * Want to reverse this so we can convert from the child towards the
+   * timeline */
+  time_effects = g_list_reverse (time_effects);
 
-  g_return_val_if_fail (GES_IS_CLIP (clip), -1);
-  g_return_val_if_fail (GES_IS_BASE_EFFECT (effect), -1);
+  for (tmp = time_effects; tmp; tmp = tmp->next) {
+    GESBaseEffect *effect = tmp->data;
+    GHashTable *values = ges_base_effect_get_time_property_values (effect);
 
-  _get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
+    external_time = ges_base_effect_translate_sink_to_source_time (effect,
+        external_time, values);
+    g_hash_table_unref (values);
+  }
 
-  return GES_TIMELINE_ELEMENT_PRIORITY (effect) - min_prio;
-}
+  g_list_free (time_effects);
 
-/* TODO 2.0 remove as it is Deprecated */
-gint
-ges_clip_get_top_effect_position (GESClip * clip, GESBaseEffect * effect)
-{
-  return ges_clip_get_top_effect_index (clip, effect);
-}
+  if (!GST_CLOCK_TIME_IS_VALID (external_time))
+    return GST_CLOCK_TIME_NONE;
 
-/* TODO 2.0 remove as it is Deprecated */
-gboolean
-ges_clip_set_top_effect_priority (GESClip * clip,
-    GESBaseEffect * effect, guint newpriority)
-{
-  return ges_clip_set_top_effect_index (clip, effect, newpriority);
+  start = _START (clip);
+
+  if (!decrease)
+    return start + external_time;
+
+  if (external_time > start) {
+    GST_INFO_OBJECT (clip, "Cannot convert the internal time %"
+        GST_TIME_FORMAT " of the child %" GES_FORMAT " to a timeline "
+        "time because it would lie before the start of the timeline",
+        GST_TIME_ARGS (internal_time), GES_ARGS (child));
+
+    g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+        "The internal time %" GST_TIME_FORMAT " of child \"%s\" "
+        "would correspond to a negative start of -%" GST_TIME_FORMAT
+        " for the clip \"%s\"", GST_TIME_ARGS (internal_time),
+        GES_TIMELINE_ELEMENT_NAME (child),
+        GST_TIME_ARGS (external_time - start),
+        GES_TIMELINE_ELEMENT_NAME (clip));
+
+    return GST_CLOCK_TIME_NONE;
+  }
+
+  return start - external_time;
 }
 
 /**
- * ges_clip_set_top_effect_index:
- * @clip: The origin #GESClip
- * @effect: The #GESBaseEffect to move
- * @newindex: the new index at which to move the @effect inside this
- * #GESClip
+ * ges_clip_get_internal_time_from_timeline_time:
+ * @clip: A #GESClip
+ * @child: An #GESTrackElement:active child of @clip with a
+ * #GESTrackElement:track
+ * @timeline_time: A time in the timeline time coordinates
+ * @error: (nullable): Return location for an error
  *
- * This is a convenience method that lets you set the index of a top effect.
+ * Convert the timeline time to an internal source time of the child.
+ * This will take any time effects placed on the clip into account (see
+ * #GESBaseEffect for what time effects are supported, and how to
+ * declare them in GES).
  *
- * Returns: %TRUE if @effect was successfuly moved, %FALSE otherwise.
+ * When @timeline_time is above the #GESTimelineElement:start of @clip,
+ * this will return the internal time at which the content that appears at
+ * @timeline_time in the output of the timeline is created in @child. For
+ * example, if @timeline_time corresponds to the current seek position,
+ * this would let you know which part of a media file is being read.
+ *
+ * This will be done assuming the clip has an indefinite end, so the
+ * internal time may be beyond the current out-point of the child, or even
+ * its #GESTimelineElement:max-duration.
+ *
+ * If, instead, @timeline_time is below the current
+ * #GESTimelineElement:start of @clip, this will return what you would
+ * need to set the #GESTimelineElement:in-point of @child to if you set
+ * the #GESTimelineElement:start of @clip to @timeline_time and wanted
+ * to keep the content of @child currently found at the current
+ * #GESTimelineElement:start of @clip at the same timeline position. If
+ * this would be negative, the conversion fails. This is useful for
+ * determining what #GESTimelineElement:in-point would result from a
+ * #GES_EDIT_MODE_TRIM to @timeline_time.
+ *
+ * Note that whilst a clip has no time effects, this second return is
+ * equivalent to finding the internal time at which the content that
+ * appears at @timeline_time in the timeline can be found in @child if it
+ * had indefinite extent in both directions. However, with non-linear time
+ * effects this second return will be more distinct.
+ *
+ * In either case, the returned time would be appropriate to use for the
+ * #GESTimelineElement:in-point or #GESTimelineElement:max-duration of the
+ * child.
+ *
+ * See ges_clip_get_timeline_time_from_internal_time(), which performs the
+ * reverse.
+ *
+ * Returns: The time in the internal coordinates of @child corresponding
+ * to @timeline_time, or #GST_CLOCK_TIME_NONE if the conversion could not
+ * be performed.
+ * Since: 1.18
  */
-gboolean
-ges_clip_set_top_effect_index (GESClip * clip, GESBaseEffect * effect,
-    guint newindex)
+GstClockTime
+ges_clip_get_internal_time_from_timeline_time (GESClip * clip,
+    GESTrackElement * child, GstClockTime timeline_time, GError ** error)
 {
-  gint inc;
-  GList *tmp;
-  guint current_prio, min_prio, max_prio;
-  GESTrackElement *track_element;
-
-  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
-
-  track_element = GES_TRACK_ELEMENT (effect);
-  if (G_UNLIKELY (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (track_element)) !=
-          clip))
-    return FALSE;
+  GstClockTime inpoint, start, external_time;
+  gboolean decrease;
+  GESTrack *track;
+  GList *tmp, *time_effects;
 
-  current_prio = _PRIORITY (track_element);
+  g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE);
 
-  _get_priority_range (GES_CONTAINER (clip), &min_prio, &max_prio);
+  if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
+    GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
+        "a child of the clip", GES_ARGS (child));
+    return GST_CLOCK_TIME_NONE;
+  }
 
-  newindex = newindex + min_prio;
-  /*  We don't change the priority */
-  if (current_prio == newindex)
-    return TRUE;
+  track = ges_track_element_get_track (child);
 
-  if (newindex > (clip->priv->nb_effects - 1 + min_prio)) {
-    GST_DEBUG ("You are trying to make %p not a top effect", effect);
-    return FALSE;
+  if (!track) {
+    GST_WARNING_OBJECT (clip, "Cannot convert the timeline time to an "
+        "internal time of child %" GES_FORMAT " because it is not part "
+        "of a track", GES_ARGS (child));
+    return GST_CLOCK_TIME_NONE;
   }
 
-  if (current_prio > clip->priv->nb_effects + min_prio) {
-    GST_ERROR ("%p is not a top effect", effect);
-    return FALSE;
+  if (!ges_track_element_is_active (child)) {
+    GST_WARNING_OBJECT (clip, "Cannot convert the timeline time to an "
+        "internal time of child %" GES_FORMAT " because it is not active "
+        "in its track", GES_ARGS (child));
+    return GST_CLOCK_TIME_NONE;
   }
 
-  _ges_container_sort_children (GES_CONTAINER (clip));
-  if (_PRIORITY (track_element) < newindex)
-    inc = -1;
-  else
-    inc = +1;
+  if (timeline_time == GST_CLOCK_TIME_NONE)
+    return GST_CLOCK_TIME_NONE;
 
-  GST_DEBUG_OBJECT (clip, "Setting top effect %" GST_PTR_FORMAT "priority: %i",
-      effect, newindex);
+  start = _START (clip);
+  if (start <= timeline_time) {
+    decrease = FALSE;
+    external_time = timeline_time - start;
+  } else {
+    decrease = TRUE;
+    external_time = start - timeline_time;
+  }
 
-  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
-    GESTrackElement *tmpo = GES_TRACK_ELEMENT (tmp->data);
-    guint tck_priority = _PRIORITY (tmpo);
+  time_effects = _active_time_effects_in_track_after_priority (clip, track,
+      _PRIORITY (child));
 
-    if (tmpo == track_element)
-      continue;
+  /* currently ordered with highest priority (closest to the timeline)
+   * first, with @child being at the *end* of the list, which is what we
+   * want */
 
-    if ((inc == +1 && tck_priority >= newindex) ||
-        (inc == -1 && tck_priority <= newindex)) {
-      _set_priority0 (GES_TIMELINE_ELEMENT (tmpo), tck_priority + inc);
-    }
-  }
-  _set_priority0 (GES_TIMELINE_ELEMENT (track_element), newindex);
+  for (tmp = time_effects; tmp; tmp = tmp->next) {
+    GESBaseEffect *effect = tmp->data;
+    GHashTable *values = ges_base_effect_get_time_property_values (effect);
 
-  return TRUE;
-}
+    external_time = ges_base_effect_translate_source_to_sink_time (effect,
+        external_time, values);
+    g_hash_table_unref (values);
+  }
 
-/**
- * ges_clip_split:
- * @clip: the #GESClip to split
- * @position: a #GstClockTime representing the timeline position at which to split
- *
- * The function modifies @clip, and creates another #GESClip so we have two
- * clips at the end, splitted at the time specified by @position, as a position
- * in the timeline (not in the clip to be split). For example, if
- * ges_clip_split is called on a 4-second clip playing from 0:01.00 until
- * 0:05.00, with a split position of 0:02.00, this will result in one clip of 1
- * second and one clip of 3 seconds, not in two clips of 2 seconds.
- *
- * The newly created clip will be added to the same layer as @clip is in. This
- * implies that @clip must be in a #GESLayer for the operation to be possible.
- *
- * This method supports clips playing at a different tempo than one second per
- * second. For example, splitting a clip with a #GESEffect 'pitch tempo=1.5'
- * four seconds after it starts, will set the inpoint of the new clip to six
- * seconds after that of the clip to split. For this, the rate-changing
- * property must be registered using @ges_effect_class_register_rate_property;
- * for the 'pitch' plugin, this is already done.
- *
- * Returns: (transfer none) (nullable): The newly created #GESClip resulting
- * from the splitting or %NULL if the clip can't be split.
- */
-GESClip *
-ges_clip_split (GESClip * clip, guint64 position)
-{
-  GList *tmp;
-  GESClip *new_object;
-  GstClockTime start, inpoint, duration, old_duration, new_duration;
-  gdouble media_duration_factor;
+  g_list_free (time_effects);
 
-  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
-  g_return_val_if_fail (clip->priv->layer, NULL);
-  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL);
+  if (!GST_CLOCK_TIME_IS_VALID (external_time))
+    return GST_CLOCK_TIME_NONE;
 
-  duration = _DURATION (clip);
-  start = _START (clip);
-  inpoint = _INPOINT (clip);
+  inpoint = _INPOINT (child);
 
-  if (position >= start + duration || position <= start) {
-    GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT
-        " out of boundaries", GST_TIME_ARGS (position));
-    return NULL;
-  }
+  if (!decrease)
+    return inpoint + external_time;
 
-  GST_DEBUG_OBJECT (clip, "Spliting at %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (position));
+  if (external_time > inpoint) {
+    GST_INFO_OBJECT (clip, "Cannot convert the timeline time %"
+        GST_TIME_FORMAT " to an internal time of the child %"
+        GES_FORMAT " because it would be before the element has any "
+        "internal content", GST_TIME_ARGS (timeline_time), GES_ARGS (child));
 
-  /* Create the new Clip */
-  new_object = GES_CLIP (ges_timeline_element_copy (GES_TIMELINE_ELEMENT (clip),
-          FALSE));
+    g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+        "The timeline time %" GST_TIME_FORMAT " would correspond to "
+        "a negative in-point of -%" GST_TIME_FORMAT " for the child "
+        "\"%s\" under clip \"%s\"", GST_TIME_ARGS (timeline_time),
+        GST_TIME_ARGS (external_time - inpoint),
+        GES_TIMELINE_ELEMENT_NAME (child), GES_TIMELINE_ELEMENT_NAME (clip));
 
-  GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
-  /* Set new timing properties on the Clip */
-  media_duration_factor =
-      ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
-      (clip));
-  new_duration = duration + start - position;
-  old_duration = position - start;
-  _set_start0 (GES_TIMELINE_ELEMENT (new_object), position);
-  _set_inpoint0 (GES_TIMELINE_ELEMENT (new_object),
-      inpoint + old_duration * media_duration_factor);
-  _set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
+    return GST_CLOCK_TIME_NONE;
+  }
 
-  _DURATION (clip) = old_duration;
-  g_object_notify (G_OBJECT (clip), "duration");
+  return inpoint - external_time;
+}
 
-  /* We do not want the timeline to create again TrackElement-s */
-  ges_clip_set_moving_from_layer (new_object, TRUE);
-  ges_layer_add_clip (clip->priv->layer, new_object);
-  ges_clip_set_moving_from_layer (new_object, FALSE);
+static GstClockTime
+_convert_core_time (GESClip * clip, GstClockTime time, gboolean to_timeline,
+    gboolean * no_core, GError ** error)
+{
+  GList *tmp;
+  GstClockTime converted = GST_CLOCK_TIME_NONE;
+  GstClockTime half_frame;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+  GESClipAsset *asset =
+      GES_CLIP_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
 
-  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
-    GESTrackElement *new_trackelement, *trackelement =
-        GES_TRACK_ELEMENT (tmp->data);
-
-    new_trackelement =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (trackelement), FALSE));
-    if (new_trackelement == NULL) {
-      GST_WARNING_OBJECT (trackelement, "Could not create a copy");
-      continue;
-    }
+  if (no_core)
+    *no_core = TRUE;
 
-    /* Set 'new' track element timing propeties */
-    _set_start0 (GES_TIMELINE_ELEMENT (new_trackelement), position);
-    _set_inpoint0 (GES_TIMELINE_ELEMENT (new_trackelement),
-        inpoint + old_duration * media_duration_factor);
-    _set_duration0 (GES_TIMELINE_ELEMENT (new_trackelement), new_duration);
+  if (to_timeline)
+    half_frame = timeline ? ges_timeline_get_frame_time (timeline, 1) : 0;
+  else
+    half_frame = ges_clip_asset_get_frame_time (asset, 1);
+  half_frame = GST_CLOCK_TIME_IS_VALID (half_frame) ? half_frame / 2 : 0;
 
-    ges_container_add (GES_CONTAINER (new_object),
-        GES_TIMELINE_ELEMENT (new_trackelement));
-    ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
-        GES_TIMELINE_ELEMENT (new_trackelement));
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    GESTrack *track = ges_track_element_get_track (child);
+
+    if (_IS_CORE_CHILD (child) && track && ges_track_element_is_active (child)
+        && ges_track_element_has_internal_source (child)) {
+      GstClockTime tmp_time;
+      GError *convert_error = NULL;
+
+      if (no_core)
+        *no_core = FALSE;
+
+      if (to_timeline)
+        tmp_time =
+            ges_clip_get_timeline_time_from_internal_time (clip, child, time,
+            &convert_error);
+      else
+        tmp_time =
+            ges_clip_get_internal_time_from_timeline_time (clip, child, time,
+            &convert_error);
+
+      if (!GST_CLOCK_TIME_IS_VALID (converted)) {
+        converted = tmp_time;
+      } else if (!GST_CLOCK_TIME_IS_VALID (tmp_time)) {
+        GST_WARNING_OBJECT (clip, "The calculated %s time for %s time %"
+            GST_TIME_FORMAT " using core child %" GES_FORMAT " is not "
+            "defined, but it had a definite value of %" GST_TIME_FORMAT
+            " for another core child", to_timeline ? "timeline" : "internal",
+            to_timeline ? "internal" : "timeline", GST_TIME_ARGS (time),
+            GES_ARGS (child), GST_TIME_ARGS (converted));
+      } else if (tmp_time != converted) {
+        GstClockTime diff = (tmp_time > converted) ?
+            tmp_time - converted : converted - tmp_time;
+
+        if (diff > half_frame) {
+          GST_WARNING_OBJECT (clip, "The calculated %s time for %s time %"
+              GST_TIME_FORMAT " using core child %" GES_FORMAT " is %"
+              GST_TIME_FORMAT ", which is different from the value of %"
+              GST_TIME_FORMAT " calculated using a different core child",
+              to_timeline ? "timeline" : "internal",
+              to_timeline ? "internal" : "timeline", GST_TIME_ARGS (time),
+              GES_ARGS (child), GST_TIME_ARGS (tmp_time),
+              GST_TIME_ARGS (converted));
+        }
 
-    ges_track_element_copy_bindings (trackelement, new_trackelement,
-        position - start + inpoint);
+        /* prefer result from video tracks */
+        if (GES_IS_VIDEO_TRACK (track))
+          converted = tmp_time;
+      }
+      if (convert_error) {
+        if (error) {
+          g_clear_error (error);
+          *error = convert_error;
+        } else {
+          g_error_free (convert_error);
+        }
+      }
+    }
   }
 
-  ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  _DURATION (clip) = duration;
-  _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
-  ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-
-  return new_object;
+  return converted;
 }
 
-/**
- * ges_clip_set_supported_formats:
- * @clip: the #GESClip to set supported formats on
- * @supportedformats: the #GESTrackType defining formats supported by @clip
- *
- * Sets the formats supported by the file.
- */
-void
-ges_clip_set_supported_formats (GESClip * clip, GESTrackType supportedformats)
+GstClockTime
+ges_clip_get_core_internal_time_from_timeline_time (GESClip * clip,
+    GstClockTime timeline_time, gboolean * no_core, GError ** error)
 {
-  g_return_if_fail (GES_IS_CLIP (clip));
-
-  clip->priv->supportedformats = supportedformats;
+  return _convert_core_time (clip, timeline_time, FALSE, no_core, error);
 }
 
 /**
- * ges_clip_get_supported_formats:
- * @clip: the #GESClip
+ * ges_clip_get_timeline_time_from_source_frame:
+ * @clip: A #GESClip
+ * @frame_number: The frame number to get the corresponding timestamp of
+ * in the timeline coordinates
+ * @error: (nullable): Return location for an error
+ *
+ * Convert the source frame number to a timeline time. This acts the same
+ * as ges_clip_get_timeline_time_from_internal_time() using the core
+ * children of the clip and using the frame number to specify the internal
+ * position, rather than a timestamp.
  *
- * Get the formats supported by @clip.
+ * The returned timeline time can be used to seek or edit to a specific
+ * frame.
  *
- * Returns: The formats supported by @clip.
+ * Note that you can get the frame timestamp of a particular clip asset
+ * with ges_clip_asset_get_frame_time().
+ *
+ * Returns: The timestamp corresponding to @frame_number in the core
+ * children of @clip, in the timeline coordinates, or #GST_CLOCK_TIME_NONE
+ * if the conversion could not be performed.
+ * Since: 1.18
  */
-GESTrackType
-ges_clip_get_supported_formats (GESClip * clip)
+GstClockTime
+ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
+    GESFrameNumber frame_number, GError ** error)
 {
-  g_return_val_if_fail (GES_IS_CLIP (clip), GES_TRACK_TYPE_UNKNOWN);
+  GstClockTime timeline_time = GST_CLOCK_TIME_NONE;
+  GstClockTime frame_ts;
+  GESClipAsset *asset;
 
-  return clip->priv->supportedformats;
-}
+  g_return_val_if_fail (GES_IS_CLIP (clip), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (!error || !*error, GST_CLOCK_TIME_NONE);
 
-gboolean
-_ripple (GESTimelineElement * element, GstClockTime start)
-{
-  return ges_container_edit (GES_CONTAINER (element), NULL,
-      ges_timeline_element_get_layer_priority (element),
-      GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, start);
-}
+  if (!GES_FRAME_NUMBER_IS_VALID (frame_number))
+    return GST_CLOCK_TIME_NONE;
 
-static gboolean
-_ripple_end (GESTimelineElement * element, GstClockTime end)
-{
-  return ges_container_edit (GES_CONTAINER (element), NULL,
-      ges_timeline_element_get_layer_priority (element),
-      GES_EDIT_MODE_RIPPLE, GES_EDGE_END, end);
-}
+  asset = GES_CLIP_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
+  frame_ts = ges_clip_asset_get_frame_time (asset, frame_number);
+  if (!GST_CLOCK_TIME_IS_VALID (frame_ts))
+    return GST_CLOCK_TIME_NONE;
 
-gboolean
-_roll_start (GESTimelineElement * element, GstClockTime start)
-{
-  return ges_container_edit (GES_CONTAINER (element), NULL,
-      ges_timeline_element_get_layer_priority (element),
-      GES_EDIT_MODE_ROLL, GES_EDGE_START, start);
-}
+  timeline_time = _convert_core_time (clip, frame_ts, TRUE, NULL, error);
 
-gboolean
-_roll_end (GESTimelineElement * element, GstClockTime end)
-{
-  return ges_container_edit (GES_CONTAINER (element), NULL,
-      ges_timeline_element_get_layer_priority (element),
-      GES_EDIT_MODE_ROLL, GES_EDGE_END, end);
-}
+  if (error && *error) {
+    g_clear_error (error);
+    g_set_error (error, GES_ERROR, GES_ERROR_INVALID_FRAME_NUMBER,
+        "Requested frame %" G_GINT64_FORMAT " would be outside the "
+        "timeline.", frame_number);
+  }
 
-gboolean
-_trim (GESTimelineElement * element, GstClockTime start)
-{
-  return ges_container_edit (GES_CONTAINER (element), NULL, -1,
-      GES_EDIT_MODE_TRIM, GES_EDGE_START, start);
+  return timeline_time;
 }
 
 /**
- * ges_clip_add_asset:
- * @clip: a #GESClip
- * @asset: a #GESAsset with #GES_TYPE_TRACK_ELEMENT as extractable_type
+ * ges_clip_add_child_to_track:
+ * @clip: A #GESClip
+ * @child: A child of @clip
+ * @track: The track to add @child to
+ * @error: (nullable): Return location for an error
+ *
+ * Adds the track element child of the clip to a specific track.
+ *
+ * If the given child is already in another track, this will create a copy
+ * of the child, add it to the clip, and add this copy to the track.
+ *
+ * You should only call this whilst a clip is part of a #GESTimeline, and
+ * for tracks that are in the same timeline.
+ *
+ * This method is an alternative to using the
+ * #GESTimeline::select-tracks-for-object signal, but can be used to
+ * complement it when, say, you wish to copy a clip's children from one
+ * track into a new one.
  *
- * Extracts a #GESTrackElement from @asset and adds it to the @clip.
- * Should only be called in order to add operations to a #GESClip,
- * ni other cases TrackElement are added automatically when adding the
- * #GESClip/#GESAsset to a layer.
+ * When the child is a core child, it must be added to a track that does
+ * not already contain another core child of the same clip. If it is not a
+ * core child (an additional effect), then it must be added to a track
+ * that already contains one of the core children of the same clip.
  *
- * Takes a reference on @track_element.
+ * This method can also fail if the adding the track element to the track
+ * would break a configuration rule of the corresponding #GESTimeline,
+ * such as causing three sources to overlap at a single time, or causing
+ * a source to completely overlap another in the same track.
  *
- * Returns: (transfer none)(allow-none): Created #GESTrackElement or NULL
- * if an error happened
+ * Returns: (transfer none): The element that was added to @track, either
+ * @child or a copy of child, or %NULL if the element could not be added.
+ *
+ * Since: 1.18
  */
 GESTrackElement *
-ges_clip_add_asset (GESClip * clip, GESAsset * asset)
+ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
+    GESTrack * track, GError ** error)
 {
-  GESTrackElement *element;
+  GESTimeline *timeline;
+  GESTrackElement *el;
+  GESTrack *current_track;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
-  g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
-  g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type
-          (asset), GES_TYPE_TRACK_ELEMENT), NULL);
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL);
+  g_return_val_if_fail (GES_IS_TRACK (track), NULL);
+  g_return_val_if_fail (!error || !*error, NULL);
 
-  element = GES_TRACK_ELEMENT (ges_asset_extract (asset, NULL));
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
 
-  if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (element)))
+  if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
+    GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
+        "a child of the clip", GES_ARGS (child));
     return NULL;
+  }
 
-  return element;
-
-}
-
-/**
- * ges_clip_find_track_elements:
- * @clip: a #GESClip
- * @track: (allow-none): a #GESTrack or NULL
- * @track_type: a #GESTrackType indicating the type of tracks in which elements
- * should be searched.
- * @type: a #GType indicating the type of track element you are looking
- * for or %G_TYPE_NONE if you do not care about the track type.
- *
- * Finds all the #GESTrackElement controlled by @clip that is used in @track. You
- * may optionally specify a GType to further narrow search criteria.
- *
- * Returns: (transfer full) (element-type GESTrackElement): a #GList of the
- * #GESTrackElement contained in @clip.
- * The refcount of the objects will be increased. The user will have to
- * unref each #GESTrackElement and free the #GList.
- */
-
-GList *
-ges_clip_find_track_elements (GESClip * clip, GESTrack * track,
-    GESTrackType track_type, GType type)
-{
-  GList *tmp;
-  GESTrack *tmptrack;
-  GESTrackElement *otmp;
-  GESTrackElement *foundElement;
-
-  GList *ret = NULL;
+  if (!timeline) {
+    GST_WARNING_OBJECT (clip, "Cannot add children to tracks unless "
+        "the clip is part of a timeline");
+    return NULL;
+  }
 
-  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
-  g_return_val_if_fail (!(track == NULL && type == G_TYPE_NONE &&
-          track_type == GES_TRACK_TYPE_UNKNOWN), NULL);
+  if (timeline != ges_track_get_timeline (track)) {
+    GST_WARNING_OBJECT (clip, "Cannot add the children to the track %"
+        GST_PTR_FORMAT " because its timeline is %" GST_PTR_FORMAT
+        " rather than that of the clip %" GST_PTR_FORMAT,
+        track, ges_track_get_timeline (track), timeline);
+    return NULL;
+  }
 
-  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = g_list_next (tmp)) {
-    otmp = (GESTrackElement *) tmp->data;
+  current_track = ges_track_element_get_track (child);
 
-    if ((type != G_TYPE_NONE) && !G_TYPE_CHECK_INSTANCE_TYPE (tmp->data, type))
-      continue;
+  if (current_track == track) {
+    GST_WARNING_OBJECT (clip, "Child %s" GES_FORMAT " is already in the "
+        "track %" GST_PTR_FORMAT, GES_ARGS (child), track);
+    return NULL;
+  }
 
-    tmptrack = ges_track_element_get_track (otmp);
-    if (((track != NULL && tmptrack == track)) ||
-        (track_type != GES_TRACK_TYPE_UNKNOWN
-            && ges_track_element_get_track_type (otmp) == track_type)) {
+  /* copy if the element is already in a track */
+  if (current_track) {
+    /* TODO: rather than add the effect at the next highest priority, we
+     * want to add copied effect into the same EffectCollection, which all
+     * share the same priority/index */
+    if (_IS_TOP_EFFECT (child)) {
+      clip->priv->use_effect_priority = TRUE;
+      /* add at next lowest priority */
+      clip->priv->effect_priority = GES_TIMELINE_ELEMENT_PRIORITY (child) + 1;
+    }
 
-      foundElement = GES_TRACK_ELEMENT (tmp->data);
+    el = ges_clip_copy_track_element_into (clip, child, GST_CLOCK_TIME_NONE);
 
-      ret = g_list_append (ret, gst_object_ref (foundElement));
+    clip->priv->use_effect_priority = FALSE;
+    if (!el) {
+      GST_ERROR_OBJECT (clip, "Could not add a copy of the track element %"
+          GES_FORMAT " to the clip so cannot add it to the track %"
+          GST_PTR_FORMAT, GES_ARGS (child), track);
+      return NULL;
     }
+  } else {
+    el = child;
   }
 
-  return ret;
+  if (!ges_track_add_element_full (track, el, error)) {
+    GST_INFO_OBJECT (clip, "Could not add the track element %"
+        GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track);
+    if (el != child)
+      ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el));
+    return NULL;
+  }
+
+  /* call _child_track_changed now so that the "active" status of the
+   * child can change. Note that this is needed because this method may
+   * be called during ges_container_add, in which case "notify" for el
+   * will be frozen. Thus, _update_active_for_track may not have been
+   * called yet. It is important for us to call this now because when
+   * the elements are un-frozen, we need to ensure the "active" status
+   * is already set before the duration-limit is calculated */
+  _update_active_for_track (clip, el);
+
+  return el;
 }
index f0bbb1e..5426cfa 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_CLIP
-#define _GES_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_CLIP             ges_clip_get_type()
-#define GES_CLIP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_CLIP, GESClip))
-#define GES_CLIP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_CLIP, GESClipClass))
-#define GES_IS_CLIP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_CLIP))
-#define GES_IS_CLIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_CLIP))
-#define GES_CLIP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_CLIP, GESClipClass))
+GES_DECLARE_TYPE(Clip, clip, CLIP);
 
-typedef struct _GESClipPrivate GESClipPrivate;
+/**
+ * GES_CLIP_CLASS_CAN_ADD_EFFECTS:
+ * @klass: A #GESClipClass
+ *
+ * Whether the class allows for the user to add additional non-core
+ * #GESBaseEffect-s to clips from this class.
+ */
+#define GES_CLIP_CLASS_CAN_ADD_EFFECTS(klass) ((GES_CLIP_CLASS (klass))->ABI.abi.can_add_effects)
 
 /**
  * GESFillTrackElementFunc:
- * @clip: the #GESClip controlling the track elements
- * @track_element: the #GESTrackElement
- * @nleobj: the GNonLin object that needs to be filled.
+ * @clip: The #GESClip controlling the track elements
+ * @track_element: The #GESTrackElement
+ * @nleobj: The nleobject that needs to be filled
  *
- * A function that will be called when the GNonLin object of a corresponding
+ * A function that will be called when the nleobject of a corresponding
  * track element needs to be filled.
  *
  * The implementer of this function shall add the proper #GstElement to @nleobj
  * using gst_bin_add().
  *
- * Returns: TRUE if the implementer succesfully filled the @nleobj, else #FALSE.
+ * Deprecated: 1.18: This method type is no longer used.
+ *
+ * Returns: %TRUE if the implementer successfully filled the @nleobj.
  */
 typedef gboolean (*GESFillTrackElementFunc) (GESClip *clip, GESTrackElement *track_element,
                                              GstElement *nleobj);
 
 /**
  * GESCreateTrackElementFunc:
- * @clip: a #GESClip
- * @type: a #GESTrackType
+ * @clip: A #GESClip
+ * @type: A #GESTrackType to create a #GESTrackElement for
  *
- * Creates the 'primary' track element for this @clip.
+ * A method for creating the core #GESTrackElement of a clip, to be added
+ * to a #GESTrack of the given track type.
  *
- * Subclasses should implement this method if they only provide a
- * single #GESTrackElement per track.
+ * If a clip may produce several track elements per track type,
+ * #GESCreateTrackElementsFunc is more appropriate.
  *
- * If the subclass needs to create more than one #GESTrackElement for a
- * given track, then it should implement the 'create_track_elements'
- * method instead.
- *
- * The implementer of this function shall return the proper #GESTrackElement
- * that should be controlled by @clip for the given @track.
- *
- * The returned #GESTrackElement will be automatically added to the list
- * of objects controlled by the #GESClip.
- *
- * Returns: the #GESTrackElement to be used, or %NULL if it can't provide one
- * for the given @track.
+ * Returns: (transfer floating) (nullable): The #GESTrackElement created
+ * by @clip, or %NULL if @clip can not provide a track element for the
+ * given @type or an error occurred.
  */
 typedef GESTrackElement *(*GESCreateTrackElementFunc) (GESClip * clip, GESTrackType type);
 
 /**
  * GESCreateTrackElementsFunc:
- * @clip: a #GESClip
- * @type: a #GESTrackType
- *
- * Create all track elements this clip handles for this type of track.
+ * @clip: A #GESClip
+ * @type: A #GESTrackType to create #GESTrackElement-s for
  *
- * Subclasses should implement this method if they potentially need to
- * return more than one #GESTrackElement(s) for a given #GESTrack.
+ * A method for creating the core #GESTrackElement-s of a clip, to be
+ * added to #GESTrack-s of the given track type.
  *
- * Returns: %TRUE on success %FALSE on failure.
+ * Returns: (transfer container) (element-type GESTrackElement): A list of
+ * the #GESTrackElement-s created by @clip for the given @type, or %NULL
+ * if no track elements are created or an error occurred.
  */
-
 typedef GList * (*GESCreateTrackElementsFunc) (GESClip * clip, GESTrackType type);
 
 /**
  * GESClip:
- *
- * The #GESClip base class.
  */
 struct _GESClip
 {
@@ -114,11 +107,19 @@ struct _GESClip
 
 /**
  * GESClipClass:
- * @create_track_element: method to create a single #GESTrackElement for a given #GESTrack.
- * @create_track_elements: method to create multiple #GESTrackElements for a
- * #GESTrack.
- *
- * Subclasses can override the @create_track_element.
+ * @create_track_element: Method to create the core #GESTrackElement of a clip
+ * of this class. If a clip of this class may create several track elements per
+ * track type, this should be left as %NULL, and
+ * GESClipClass::create_track_elements should be used instead. Otherwise, you
+ * should implement this class method and leave
+ * GESClipClass::create_track_elements as the default implementation
+ * @create_track_elements: Method to create the (multiple) core
+ * #GESTrackElement-s of a clip of this class. If
+ * GESClipClass::create_track_element is implemented, this should be kept as the
+ * default implementation
+ * @can_add_effects: Whether the user can add additional non-core
+ * #GESBaseEffect-s to clips from this class, to be applied to the output data
+ * of the core elements.
  */
 struct _GESClipClass
 {
@@ -131,60 +132,115 @@ struct _GESClipClass
 
   /*< private >*/
   /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING_LARGE];
+  union {
+    gpointer _ges_reserved[GES_PADDING_LARGE];
+    struct {
+      gboolean can_add_effects;
+    } abi;
+  } ABI;
 };
 
 /****************************************************
- *                  Standard                        *
- ****************************************************/
-GES_API
-GType ges_clip_get_type (void);
-
-/****************************************************
  *                TrackElement handling             *
  ****************************************************/
 GES_API
 GESTrackType      ges_clip_get_supported_formats  (GESClip *clip);
 GES_API
-void              ges_clip_set_supported_formats  (GESClip *clip, GESTrackType       supportedformats);
+void              ges_clip_set_supported_formats  (GESClip *clip,
+                                                   GESTrackType supportedformats);
 GES_API
-GESTrackElement*  ges_clip_add_asset              (GESClip *clip, GESAsset *asset);
+GESTrackElement*  ges_clip_add_asset              (GESClip *clip,
+                                                   GESAsset *asset);
 GES_API
-GESTrackElement*  ges_clip_find_track_element     (GESClip *clip, GESTrack *track,
+GESTrackElement*  ges_clip_find_track_element     (GESClip *clip,
+                                                   GESTrack *track,
                                                    GType type);
 GES_API
-GList *           ges_clip_find_track_elements    (GESClip * clip, GESTrack * track,
-                                                   GESTrackType track_type, GType type);
+GList *           ges_clip_find_track_elements    (GESClip * clip,
+                                                   GESTrack * track,
+                                                   GESTrackType track_type,
+                                                   GType type);
+
+GES_API
+GESTrackElement * ges_clip_add_child_to_track     (GESClip * clip,
+                                                   GESTrackElement * child,
+                                                   GESTrack * track,
+                                                   GError ** error);
 
 /****************************************************
  *                     Layer                        *
  ****************************************************/
 GES_API
-GESLayer* ges_clip_get_layer              (GESClip *clip);
+GESLayer* ges_clip_get_layer              (GESClip * clip);
+GES_API
+gboolean  ges_clip_move_to_layer          (GESClip * clip,
+                                           GESLayer * layer);
 GES_API
-gboolean  ges_clip_move_to_layer          (GESClip *clip, GESLayer  *layer);
+gboolean  ges_clip_move_to_layer_full     (GESClip * clip,
+                                           GESLayer * layer,
+                                           GError ** error);
 
 /****************************************************
  *                   Effects                        *
  ****************************************************/
 GES_API
-GList*   ges_clip_get_top_effects           (GESClip *clip);
+gboolean ges_clip_add_top_effect            (GESClip * clip,
+                                             GESBaseEffect * effect,
+                                             gint index,
+                                             GError ** error);
 GES_API
-gint     ges_clip_get_top_effect_position   (GESClip *clip, GESBaseEffect *effect);
+gboolean ges_clip_remove_top_effect         (GESClip * clip,
+                                             GESBaseEffect * effect,
+                                             GError ** error);
 GES_API
-gint     ges_clip_get_top_effect_index   (GESClip *clip, GESBaseEffect *effect);
+GList*   ges_clip_get_top_effects           (GESClip * clip);
 GES_API
-gboolean ges_clip_set_top_effect_priority   (GESClip *clip, GESBaseEffect *effect,
+gint     ges_clip_get_top_effect_position   (GESClip * clip,
+                                             GESBaseEffect * effect);
+GES_API
+gint     ges_clip_get_top_effect_index      (GESClip * clip,
+                                             GESBaseEffect * effect);
+GES_API
+gboolean ges_clip_set_top_effect_priority   (GESClip * clip,
+                                             GESBaseEffect * effect,
                                              guint newpriority);
 GES_API
-gboolean ges_clip_set_top_effect_index   (GESClip *clip, GESBaseEffect *effect,
+gboolean ges_clip_set_top_effect_index      (GESClip * clip,
+                                             GESBaseEffect * effect,
                                              guint newindex);
+GES_API
+gboolean ges_clip_set_top_effect_index_full (GESClip * clip,
+                                             GESBaseEffect * effect,
+                                             guint newindex,
+                                             GError ** error);
 
 /****************************************************
  *                   Editing                        *
  ****************************************************/
 GES_API
-GESClip* ges_clip_split  (GESClip *clip, guint64  position);
+GESClip*     ges_clip_split                                (GESClip *clip,
+                                                            guint64 position);
+GES_API
+GESClip*     ges_clip_split_full                           (GESClip *clip,
+                                                            guint64 position,
+                                                            GError ** error);
+
+GES_API
+GstClockTime ges_clip_get_internal_time_from_timeline_time (GESClip * clip,
+                                                            GESTrackElement * child,
+                                                            GstClockTime timeline_time,
+                                                            GError ** error);
+GES_API
+GstClockTime ges_clip_get_timeline_time_from_internal_time (GESClip * clip,
+                                                            GESTrackElement * child,
+                                                            GstClockTime internal_time,
+                                                            GError ** error);
+GES_API
+GstClockTime ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
+                                                           GESFrameNumber frame_number,
+                                                           GError ** error);
+
+GES_API
+GstClockTime ges_clip_get_duration_limit (GESClip * clip);
 
 G_END_DECLS
-#endif /* _GES_CLIP */
index afca778..823d951 100644 (file)
@@ -50,6 +50,12 @@ _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
 static gboolean
 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
     GstStructure * structure, GError ** error);
+static gboolean
+_ges_command_line_formatter_add_track (GESTimeline * timeline,
+    GstStructure * structure, GError ** error);
+static gboolean
+_ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
+    GstStructure * structure, GError ** error);
 
 typedef struct
 {
@@ -67,18 +73,52 @@ typedef struct
   const gchar *long_name;
   gchar short_name;
   ActionFromStructureFunc callback;
+  const gchar *synopsis;
   const gchar *description;
+  const gchar *examples;
   /* The first property must be the ID on the command line */
   Property properties[MAX_PROPERTIES];
 } GESCommandLineOption;
 
 /*  *INDENT-OFF* */
 static GESCommandLineOption options[] = {
-  {"clip", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
-    "<clip uri> - Adds a clip in the timeline.",
-    {
+  {
+    .long_name = "clip",
+    .short_name='c',
+    .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_clip,
+    .synopsis="<clip uri>",
+    .description="Adds a clip in the timeline. "
+                 "See documentation for the --track-types option to ges-launch-1.0, as it "
+                 " will affect the result of this command.",
+    .examples="    ges-launch-1.0 +clip /path/to/media\n\n"
+              "This will simply play the sample from its beginning to its end.\n\n"
+              "    ges-launch-1.0 +clip /path/to/media inpoint=4.0\n\n"
+              "Assuming 'media' is a 10 second long media sample, this will play the sample\n"
+              "from the 4th second to the 10th, resulting in a 6-seconds long playback.\n\n"
+              "    ges-launch-1.0 +clip /path/to/media inpoint=4.0 duration=2.0 start=4.0\n\n"
+              "Assuming \"media\" is an audio video sample longer than 6 seconds, this will play\n"
+              "a black frame and silence for 4 seconds, then the sample from its 4th second to\n"
+              "its sixth second, resulting in a 6-seconds long playback.\n\n"
+              "    ges-launch-1.0 --track-types=audio +clip /path/to/media\n\n"
+              "Assuming \"media\" is an audio video sample, this will only play the audio of the\n"
+              "sample in its entirety.\n\n"
+              "    ges-launch-1.0 +clip /path/to/media1 layer=1 set-alpha 0.9 +clip /path/to/media2 layer=0\n\n"
+              "Assume media1 and media2 both contain audio and video and last for 10 seconds.\n\n"
+              "This will first add media1 in a new layer of \"priority\" 1, thus implicitly\n"
+              "creating a layer of \"priority\" 0, the start of the clip will be 0 as no clip\n"
+              "had been added in that layer before.\n\n"
+              "It will then add media2 in the layer of \"priority\" 0 which was created\n"
+              "previously, the start of this new clip will also be 0 as no clip has been added\n"
+              "in this layer before.\n\n"
+              "Both clips will thus overlap on two layers for 10 seconds.\n\n"
+              "The \"alpha\" property of the second clip will finally be set to a value of 0.9.\n\n"
+              "All this will result in a 10 seconds playback, where media2 is barely visible\n"
+              "through media1, which is nearly opaque. If alpha was set to 0.5, both clips\n"
+              "would be equally visible, and if it was set to 0.0, media1 would be invisible\n"
+              "and media2 completely opaque.\n",
+    .properties={
       {
-        "uri", "n", 0, "asset-id",
+        "uri", 0, 0, "asset-id",
         "The URI of the media file."
       },
       {
@@ -86,7 +126,7 @@ static GESCommandLineOption options[] = {
         "The name of the clip, can be used as an ID later."
       },
       {
-        "start", "s",GST_TYPE_CLOCK_TIME, NULL,
+        "start", "s", GST_TYPE_CLOCK_TIME, NULL,
         "The starting position of the clip in the timeline."
       },
       {
@@ -108,10 +148,16 @@ static GESCommandLineOption options[] = {
       {NULL, 0, 0, NULL, FALSE},
     },
   },
-  {"effect", 'e', (ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
-    "<effect bin description> - Adds an effect as specified by 'bin-description',\n"
-    "similar to gst-launch-style pipeline description, without setting properties\n"
-    "(see `set-` for information about how to set properties).\n",
+  {
+    .long_name="effect",
+    .short_name='e',
+    .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_effect,
+    .synopsis="<effect bin description>",
+    .description="Adds an effect as specified by 'bin-description', similar to gst-launch-style"
+                 " pipeline description, without setting properties (see `set-<property-name>` for information"
+                 " about how to set properties).",
+    .examples="    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\"\n\n"
+              "This will apply the agingtv effect to \"media\" and play it back.",
     {
       {
         "bin-description", "d", 0, "asset-id",
@@ -122,17 +168,27 @@ static GESCommandLineOption options[] = {
         "The name of the element to apply the effect on."
       },
       {
+        "inpoint", "i", GST_TYPE_CLOCK_TIME, NULL,
+        "Implies that the effect has 'internal content'"
+        "(see [ges_track_element_set_has_internal_source](ges_track_element_set_has_internal_source))",
+      },
+      {
         "name", "n", 0, "child-name",
         "The name to be given to the effect."
       },
       {NULL, NULL, 0, NULL, FALSE},
     },
   },
-  {"test-clip", 0, (ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
-    "<test clip pattern> - Add a test clip in the timeline.",
-    {
+  {
+    .long_name="test-clip",
+    .short_name=0,
+    .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_test_clip,
+    .synopsis="<test clip pattern>",
+    .description="Add a test clip in the timeline.",
+    .examples=NULL,
+    .properties={
       {
-        "pattern", "p", 0, NULL,
+        "vpattern", "p", 0, NULL,
         "The testsource pattern name."
       },
       {
@@ -140,7 +196,7 @@ static GESCommandLineOption options[] = {
         "The name of the clip, can be used as an ID later."
       },
       {
-        "start", "s",GST_TYPE_CLOCK_TIME, NULL,
+        "start", "s", GST_TYPE_CLOCK_TIME, NULL,
         "The starting position of the clip in the timeline."
       },
       {
@@ -158,11 +214,16 @@ static GESCommandLineOption options[] = {
       {NULL, 0, 0, NULL, FALSE},
     },
   },
-  {"title", 'c', (ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
-    "<title text> - Adds a clip in the timeline.",
-    {
+  {
+    .long_name="title",
+    .short_name='c',
+    .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_title_clip,
+    .synopsis="<title text>",
+    .description="Adds a clip in the timeline.",
+    .examples=NULL,
+    .properties={
       {
-        "text", "n", 0, NULL,
+        "text", "t", 0, NULL,
         "The text to be used as title."
       },
       {
@@ -186,19 +247,71 @@ static GESCommandLineOption options[] = {
         "The type of the tracks where the clip should be used (audio or video or audio+video)."
       },
       {
-        "layer", "l", 0, NULL,
+        "layer", "l", G_TYPE_INT, NULL,
         "The priority of the layer into which the clip should be added."
       },
       {NULL, 0, 0, NULL, FALSE},
     },
   },
   {
-    "set-", 0, NULL,
-    "<property name> <value> - Set a property on the last added element.\n"
-    "Any child property that exists on the previously added element\n"
-    "can be used as <property name>",
-    {
-      {NULL, NULL, 0, NULL, FALSE},
+    .long_name="track",
+    .short_name='t',
+    .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_track,
+    .synopsis="<track type>",
+    .description="Adds a track to the timeline.",
+    .examples=NULL,
+    .properties={
+      {"track-type", 0, 0, NULL, NULL},
+      {
+        "restrictions", "r", 0, NULL,
+        "The restriction caps to set on the track."
+      },
+      {NULL, 0, 0, NULL, FALSE},
+    },
+  },
+  {
+    .long_name="keyframes",
+    .short_name='k',
+    .callback=(ActionFromStructureFunc) _ges_command_line_formatter_add_keyframes,
+    .synopsis="<property name>",
+    .description="Adds keyframes for the specified property in the form:\n\n",
+    .examples="    ges-launch-1.0 +test-clip blue d=1.0 +keyframes posx 0=0 1.0=1280 t=direct-absolute +k posy 0=0 1.0=720 t=direct-absolute\n\n"
+              "This add a testclip that will disappear in the bottom right corner",
+    .properties={
+      {"property-name", 0, 0, NULL, NULL},
+      {
+        "binding-type", "t", 0, NULL,
+        "The type of binding to use, eg. 'direct-absolute', 'direct'"
+      },
+      {
+        "interpolation-mode", "m", 0, NULL,
+        "The GstInterpolationMode to user."
+      },
+      {
+        "...", 0, 0, NULL,
+        "The list of keyframe_timestamp=value to be set."
+      },
+      {NULL, 0, 0, NULL, FALSE},
+    },
+  },
+  {
+    .long_name="set-",
+    .short_name=0,
+    .callback=NULL,
+    .synopsis="<property name> <value>",
+    .description="Set a property on the last added element."
+                 " Any child property that exists on the previously added element"
+                 " can be used as <property name>"
+                 "By default, set-<property-name> will lookup the property on the last added"
+                  "object.",
+    .examples="    ges-launch-1.0 +clip /path/to/media set-alpha 0.3\n\n"
+              "This will set the alpha property on \"media\" then play it back, assuming \"media\""
+              "contains a video stream.\n\n"
+              "    ges-launch-1.0 +clip /path/to/media +effect \"agingtv\" set-dusts false\n\n"
+              "This will set the \"dusts\" property of the agingtv to false and play the\n"
+              "timeline back.",
+    .properties={
+      {NULL, 0, 0, NULL, FALSE},
     },
   },
 };
@@ -211,6 +324,8 @@ typedef enum
   EFFECT,
   TEST_CLIP,
   TITLE,
+  TRACK,
+  KEYFRAMES,
   SET,
 } GESCommandLineOptionType;
 
@@ -220,7 +335,7 @@ _convert_to_clocktime (GstStructure * structure, const gchar * name,
 {
   gint res = 1;
   gdouble val;
-  GValue d_val = { 0 };
+  GValue d_val = G_VALUE_INIT, converted = G_VALUE_INIT;
   GstClockTime timestamp;
   const GValue *gvalue = gst_structure_get_value (structure, name);
 
@@ -232,16 +347,42 @@ _convert_to_clocktime (GstStructure * structure, const gchar * name,
     goto done;
   }
 
-  if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME)
-    return 1;
+  if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
+    const gchar *val_string = g_value_get_string (gvalue);
+    /* if starts with an 'f', interpret as a frame number, keep as
+     * a string for now */
+    if (val_string && val_string[0] == 'f')
+      return 1;
+    /* else, try convert to a GstClockTime, or a double */
+    g_value_init (&converted, GST_TYPE_CLOCK_TIME);
+    if (!gst_value_deserialize (&converted, val_string)) {
+      g_value_unset (&converted);
+      g_value_init (&converted, G_TYPE_DOUBLE);
+      if (!gst_value_deserialize (&converted, val_string)) {
+        GST_ERROR ("Could not get timestamp for %s by deserializing %s",
+            name, val_string);
+        goto error;
+      }
+    }
+  } else {
+    g_value_init (&converted, G_VALUE_TYPE (gvalue));
+    g_value_copy (gvalue, &converted);
+  }
+
+  if (G_VALUE_TYPE (&converted) == GST_TYPE_CLOCK_TIME) {
+    timestamp = g_value_get_uint64 (&converted);
+    goto done;
+  }
 
   g_value_init (&d_val, G_TYPE_DOUBLE);
-  if (!g_value_transform (gvalue, &d_val)) {
-    GST_ERROR ("Could not get timestamp for %s", name);
 
-    return 0;
+  if (!g_value_transform (&converted, &d_val)) {
+    GST_ERROR ("Could not get timestamp for %s", name);
+    goto error;
   }
+
   val = g_value_get_double ((const GValue *) &d_val);
+  g_value_unset (&d_val);
 
   if (val == -1.0)
     timestamp = GST_CLOCK_TIME_NONE;
@@ -250,8 +391,14 @@ _convert_to_clocktime (GstStructure * structure, const gchar * name,
 
 done:
   gst_structure_set (structure, name, G_TYPE_UINT64, timestamp, NULL);
+  g_value_unset (&converted);
 
   return res;
+
+error:
+  g_value_unset (&converted);
+
+  return 0;
 }
 
 static gboolean
@@ -264,13 +411,19 @@ _cleanup_fields (const Property * field_names, GstStructure * structure,
     gboolean exists = FALSE;
 
     /* Move shortly named fields to longname variante */
-    if (gst_structure_has_field (structure, field_names[i].short_name)) {
+    if (field_names[i].short_name &&
+        gst_structure_has_field (structure, field_names[i].short_name)) {
       exists = TRUE;
 
       if (gst_structure_has_field (structure, field_names[i].long_name)) {
-        *error = g_error_new (GES_ERROR, 0, "Using short and long name"
-            " at the same time for property: %s, which one should I use?!",
-            field_names[i].long_name);
+        gchar *str_info = gst_structure_serialize (structure, 0);
+
+        *error =
+            g_error_new (GES_ERROR, 0,
+            "Using short (%s) and long name (%s)"
+            " at the same time s in %s, which one should I use?!",
+            field_names[i].short_name, field_names[i].long_name, str_info);
+        g_free (str_info);
 
         return FALSE;
       } else {
@@ -312,12 +465,22 @@ static gboolean
 _ges_command_line_formatter_add_clip (GESTimeline * timeline,
     GstStructure * structure, GError ** error)
 {
+  GESProject *proj;
+  GESAsset *asset;
   if (!_cleanup_fields (options[CLIP].properties, structure, error))
     return FALSE;
 
   gst_structure_set (structure, "type", G_TYPE_STRING, "GESUriClip", NULL);
 
-  return _ges_add_clip_from_struct (timeline, structure, error);
+  if (!_ges_add_clip_from_struct (timeline, structure, error))
+    return FALSE;
+
+  proj = GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
+  asset = _ges_get_asset_from_timeline (timeline, GES_TYPE_URI_CLIP,
+      gst_structure_get_string (structure, "asset-id"), NULL);
+  ges_project_add_asset (proj, asset);
+
+  return TRUE;
 }
 
 static gboolean
@@ -328,8 +491,10 @@ _ges_command_line_formatter_add_test_clip (GESTimeline * timeline,
     return FALSE;
 
   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTestClip", NULL);
-  gst_structure_set (structure, "asset-id", G_TYPE_STRING,
-      gst_structure_get_string (structure, "pattern"), NULL);
+
+  if (!gst_structure_has_field_typed (structure, "asset-id", G_TYPE_STRING))
+    gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTestClip",
+        NULL);
 
   return _ges_add_clip_from_struct (timeline, structure, error);
 }
@@ -338,18 +503,40 @@ static gboolean
 _ges_command_line_formatter_add_title_clip (GESTimeline * timeline,
     GstStructure * structure, GError ** error)
 {
-  if (!_cleanup_fields (options[TEST_CLIP].properties, structure, error))
+  if (!_cleanup_fields (options[TITLE].properties, structure, error))
     return FALSE;
 
   gst_structure_set (structure, "type", G_TYPE_STRING, "GESTitleClip", NULL);
   gst_structure_set (structure, "asset-id", G_TYPE_STRING, "GESTitleClip",
       NULL);
-  GST_ERROR ("Structure: %" GST_PTR_FORMAT, structure);
 
   return _ges_add_clip_from_struct (timeline, structure, error);
 }
 
 static gboolean
+_ges_command_line_formatter_add_keyframes (GESTimeline * timeline,
+    GstStructure * structure, GError ** error)
+{
+  if (!_cleanup_fields (options[KEYFRAMES].properties, structure, error))
+    return FALSE;
+
+  if (!_ges_set_control_source_from_struct (timeline, structure, error))
+    return FALSE;
+
+  return _ges_add_remove_keyframe_from_struct (timeline, structure, error);
+}
+
+static gboolean
+_ges_command_line_formatter_add_track (GESTimeline * timeline,
+    GstStructure * structure, GError ** error)
+{
+  if (!_cleanup_fields (options[TRACK].properties, structure, error))
+    return FALSE;
+
+  return _ges_add_track_from_struct (timeline, structure, error);
+}
+
+static gboolean
 _ges_command_line_formatter_add_effect (GESTimeline * timeline,
     GstStructure * structure, GError ** error)
 {
@@ -385,23 +572,55 @@ ges_command_line_formatter_get_help (gint nargs, gchar ** commands)
     }
 
     if (print) {
-      g_string_append_printf (help, "%s%s %s\n",
+      gint j;
+
+      gchar *tmp = g_strdup_printf ("  `%s%s` - %s\n",
           option.properties[0].long_name ? "+" : "",
-          option.long_name, option.description);
+          option.long_name, option.synopsis);
+
+      g_string_append (help, tmp);
+      g_string_append (help, "  ");
+      g_string_append (help, "\n\n  ");
+      g_free (tmp);
+
+      for (j = 0; option.description[j] != '\0'; j++) {
+
+        if (j && (j % 80) == 0) {
+          while (option.description[j] != '\0' && option.description[j] != ' ')
+            g_string_append_c (help, option.description[j++]);
+          g_string_append (help, "\n  ");
+          continue;
+        }
+
+        g_string_append_c (help, option.description[j]);
+      }
+      g_string_append_c (help, '\n');
 
       if (option.properties[0].long_name) {
         gint j;
 
-        g_string_append (help, "  Properties:\n");
+        g_string_append (help, "\n  Properties:\n\n");
 
         for (j = 1; option.properties[j].long_name; j++) {
           Property prop = option.properties[j];
-          g_string_append_printf (help, "    * %s: %s\n", prop.long_name,
+          g_string_append_printf (help, "    * `%s`: %s\n", prop.long_name,
               prop.desc);
         }
       }
+      if (option.examples) {
+        gint j;
+        gchar **examples = g_strsplit (option.examples, "\n", -1);
 
-      g_string_append (help, "\n");
+        g_string_append (help, "\n  Examples:\n\n");
+        for (j = 0; examples[j]; j++) {
+          if (examples[j])
+            g_string_append_printf (help, "    %s", examples[j]);
+          g_string_append_c (help, '\n');
+        }
+        g_strfreev (examples);
+      }
+
+      g_string_append_c (help, '\n');
     }
   }
 
@@ -440,34 +659,86 @@ _parse_structures (const gchar * string)
   return parser;
 }
 
+/* @uri: (transfer full): */
+static gchar *
+get_timeline_desc_from_uri (GstUri * uri)
+{
+  gchar *res, *path;
+
+  if (!uri)
+    return NULL;
+
+  /* Working around parser requiring a space to begin with */
+  path = gst_uri_get_path (uri);
+  res = g_strconcat (" ", path, NULL);
+  g_free (path);
+
+  gst_uri_unref (uri);
+
+  return res;
+}
+
 static gboolean
 _can_load (GESFormatter * dummy_formatter, const gchar * string,
     GError ** error)
 {
   gboolean res = FALSE;
+  GstUri *uri;
+  const gchar *scheme;
+  gchar *timeline_desc = NULL;
   GESStructureParser *parser;
 
-  if (string == NULL)
+  if (string == NULL) {
+    GST_ERROR ("No URI!");
+    return FALSE;
+  }
+
+  uri = gst_uri_from_string (string);
+  if (!uri) {
+    GST_INFO_OBJECT (dummy_formatter, "Wrong uri: %s", string);
     return FALSE;
+  }
 
-  parser = _parse_structures (string);
+  scheme = gst_uri_get_scheme (uri);
+  if (!g_strcmp0 (scheme, "ges:")) {
+    GST_INFO_OBJECT (dummy_formatter, "Wrong scheme: %s", string);
+    gst_uri_unref (uri);
 
+    return FALSE;
+  }
+
+  timeline_desc = get_timeline_desc_from_uri (uri);
+  parser = _parse_structures (timeline_desc);
   if (parser->structures)
     res = TRUE;
 
   gst_object_unref (parser);
+  g_free (timeline_desc);
 
   return res;
 }
 
 static gboolean
+_set_project_loaded (GESFormatter * self)
+{
+  ges_project_set_loaded (self->project, self, NULL);
+  gst_object_unref (self);
+
+  return FALSE;
+}
+
+static gboolean
 _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
     GError ** error)
 {
   guint i;
   GList *tmp;
   GError *err;
-  GESStructureParser *parser = _parse_structures (string);
+  gchar *timeline_desc =
+      get_timeline_desc_from_uri (gst_uri_from_string (string));
+  GESStructureParser *parser = _parse_structures (timeline_desc);
+
+  g_free (timeline_desc);
 
   err = ges_structure_parser_get_error (parser);
 
@@ -479,11 +750,6 @@ _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
   }
 
   g_object_set (timeline, "auto-transition", TRUE, NULL);
-  if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()))))
-    goto fail;
-
-  if (!(ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()))))
-    goto fail;
 
   /* Here we've finished initializing our timeline, we're
    * ready to start using it... by solely working with the layer !*/
@@ -498,12 +764,15 @@ _load (GESFormatter * self, GESTimeline * timeline, const gchar * string,
       if (gst_structure_has_name (tmp->data, options[i].long_name)
           || (strlen (name) == 1 && *name == options[i].short_name)) {
         EXEC (((ActionFromStructureFunc) options[i].callback), tmp->data, &err);
+        break;
       }
     }
   }
 
   gst_object_unref (parser);
 
+  ges_idle_add ((GSourceFunc) _set_project_loaded, g_object_ref (self), NULL);
+
   return TRUE;
 
 fail:
@@ -540,3 +809,350 @@ ges_command_line_formatter_class_init (GESCommandLineFormatterClass * klass)
   formatter_klass->load_from_uri = _load;
   formatter_klass->rank = GST_RANK_MARGINAL;
 }
+
+/* Copy of GST_ASCII_IS_STRING */
+#define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \
+    ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \
+    ((c) == '.'))
+
+static void
+_sanitize_argument (const gchar * arg, GString * res)
+{
+  gboolean need_wrap = FALSE;
+  const gchar *tmp_string;
+
+  for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) {
+    if (!ASCII_IS_STRING (*tmp_string) || (*tmp_string == '\n')) {
+      need_wrap = TRUE;
+      break;
+    }
+  }
+
+  if (!need_wrap) {
+    g_string_append (res, arg);
+    return;
+  }
+
+  g_string_append_c (res, '"');
+  while (*arg != '\0') {
+    if (*arg == '"' || *arg == '\\') {
+      g_string_append_c (res, '\\');
+    } else if (*arg == '\n') {
+      g_string_append (res, "\\n");
+      arg++;
+      continue;
+    }
+
+    g_string_append_c (res, *(arg++));
+  }
+  g_string_append_c (res, '"');
+}
+
+static gboolean
+_serialize_control_binding (GESTrackElement * e, const gchar * prop,
+    GString * res)
+{
+  GstInterpolationMode mode;
+  GstControlSource *source = NULL;
+  GList *timed_values, *tmp;
+  gboolean absolute = FALSE;
+  GstControlBinding *binding = ges_track_element_get_control_binding (e, prop);
+
+  if (!binding)
+    return FALSE;
+
+  if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) {
+    g_warning ("Unsupported control binding type: %s",
+        G_OBJECT_TYPE_NAME (binding));
+    goto done;
+  }
+
+  g_object_get (binding, "control-source", &source,
+      "absolute", &absolute, NULL);
+
+  if (!GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
+    g_warning ("Unsupported control source type: %s",
+        G_OBJECT_TYPE_NAME (source));
+    goto done;
+  }
+
+  g_object_get (source, "mode", &mode, NULL);
+  g_string_append_printf (res, " +keyframes %s t=%s",
+      prop, absolute ? "direct-absolute" : "direct");
+
+  if (mode != GST_INTERPOLATION_MODE_LINEAR)
+    g_string_append_printf (res, " mode=%s",
+        g_enum_get_value (g_type_class_peek (GST_TYPE_INTERPOLATION_MODE),
+            mode)->value_nick);
+
+  timed_values =
+      gst_timed_value_control_source_get_all
+      (GST_TIMED_VALUE_CONTROL_SOURCE (source));
+  for (tmp = timed_values; tmp; tmp = tmp->next) {
+    gchar strbuf[G_ASCII_DTOSTR_BUF_SIZE];
+    GstTimedValue *value;
+
+    value = (GstTimedValue *) tmp->data;
+    g_string_append_printf (res, " %f=%s",
+        (gdouble) value->timestamp / (gdouble) GST_SECOND,
+        g_ascii_dtostr (strbuf, G_ASCII_DTOSTR_BUF_SIZE, value->value));
+  }
+  g_list_free (timed_values);
+
+done:
+  g_clear_object (&source);
+  return TRUE;
+}
+
+static void
+_serialize_object_properties (GObject * object, GESCommandLineOption * option,
+    gboolean children_props, GString * res)
+{
+  guint n_props, j;
+  GParamSpec *spec, **pspecs;
+  GObjectClass *class = G_OBJECT_GET_CLASS (object);
+  const gchar *ignored_props[] = {
+    "max-duration", "supported-formats", "priority", "video-direction",
+    "is-image", NULL,
+  };
+
+  if (!children_props)
+    pspecs = g_object_class_list_properties (class, &n_props);
+  else {
+    pspecs =
+        ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
+        (object), &n_props);
+    g_assert (GES_IS_TRACK_ELEMENT (object));
+  }
+
+  for (j = 0; j < n_props; j++) {
+    const gchar *name;
+    gchar *value_str = NULL;
+    GValue val = { 0 };
+    gint i;
+
+    spec = pspecs[j];
+    if (!ges_util_can_serialize_spec (spec))
+      continue;
+
+    g_value_init (&val, spec->value_type);
+    if (!children_props)
+      g_object_get_property (object, spec->name, &val);
+    else
+      ges_timeline_element_get_child_property_by_pspec (GES_TIMELINE_ELEMENT
+          (object), spec, &val);
+
+    if (gst_value_compare (g_param_spec_get_default_value (spec),
+            &val) == GST_VALUE_EQUAL) {
+      GST_INFO ("Ignoring %s as it is using the default value", spec->name);
+      goto next;
+    }
+
+    name = spec->name;
+    if (!children_props && !g_strcmp0 (name, "in-point"))
+      name = "inpoint";
+
+    for (i = 0; option->properties[i].long_name; i++) {
+      if (!g_strcmp0 (spec->name, option->properties[i].long_name)) {
+        if (children_props) {
+          name = NULL;
+        } else {
+          name = option->properties[i].short_name;
+          if (option->properties[i].type == GST_TYPE_CLOCK_TIME)
+            value_str =
+                g_strdup_printf ("%f",
+                (gdouble) (g_value_get_uint64 (&val) / GST_SECOND));
+        }
+        break;
+      } else if (!g_strcmp0 (spec->name, option->properties[0].long_name)) {
+        name = NULL;
+        break;
+      }
+    }
+
+    for (i = 0; i < G_N_ELEMENTS (ignored_props); i++) {
+      if (!g_strcmp0 (spec->name, ignored_props[i])) {
+        name = NULL;
+        break;
+      }
+    }
+
+    if (!name) {
+      g_free (value_str);
+      continue;
+    }
+
+    if (GES_IS_TRACK_ELEMENT (object) &&
+        _serialize_control_binding (GES_TRACK_ELEMENT (object), name, res)) {
+      g_free (value_str);
+      continue;
+    }
+
+    if (!value_str)
+      value_str = gst_value_serialize (&val);
+
+    g_string_append_printf (res, " %s%s%s",
+        children_props ? "set-" : "", name, children_props ? " " : "=");
+    _sanitize_argument (value_str, res);
+    g_free (value_str);
+
+  next:
+    g_value_unset (&val);
+  }
+  g_free (pspecs);
+}
+
+static void
+_serialize_clip_track_types (GESClip * clip, GESTrackType tt, GString * res)
+{
+  GValue v = G_VALUE_INIT;
+  gchar *ttype_str;
+
+  if (ges_clip_get_supported_formats (clip) == tt)
+    return;
+
+  g_value_init (&v, GES_TYPE_TRACK_TYPE);
+  g_value_set_flags (&v, ges_clip_get_supported_formats (clip));
+
+  ttype_str = gst_value_serialize (&v);
+
+  g_string_append_printf (res, " tt=%s", ttype_str);
+  g_value_reset (&v);
+  g_free (ttype_str);
+}
+
+static void
+_serialize_clip_effects (GESClip * clip, GString * res)
+{
+  GList *tmpeffect, *effects;
+
+  effects = ges_clip_get_top_effects (clip);
+  for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
+    gchar *bin_desc;
+
+    g_object_get (tmpeffect->data, "bin-description", &bin_desc, NULL);
+
+    g_string_append_printf (res, " +effect %s", bin_desc);
+    g_free (bin_desc);
+  }
+  g_list_free_full (effects, gst_object_unref);
+
+}
+
+/**
+ * ges_command_line_formatter_get_timeline_uri:
+ * @timeline: A GESTimeline to serialize
+ *
+ * Since: 1.20
+ */
+gchar *
+ges_command_line_formatter_get_timeline_uri (GESTimeline * timeline)
+{
+  gchar *tmpstr;
+  GList *tmp;
+  gint i;
+  GString *res = g_string_new ("ges:");
+  GESTrackType tt = 0;
+
+  if (!timeline)
+    goto done;
+
+  for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
+    GstCaps *caps, *default_caps;
+    GESTrack *tmptrack, *track = tmp->data;
+
+    if (GES_IS_VIDEO_TRACK (track))
+      tmptrack = GES_TRACK (ges_video_track_new ());
+    else if (GES_IS_AUDIO_TRACK (track))
+      tmptrack = GES_TRACK (ges_audio_track_new ());
+    else {
+      g_warning ("Unhandled track type: %s", G_OBJECT_TYPE_NAME (track));
+      continue;
+    }
+
+    tt |= track->type;
+
+    g_string_append_printf (res, " +track %s",
+        (track->type == GES_TRACK_TYPE_VIDEO) ? "video" : "audio");
+
+    default_caps = ges_track_get_restriction_caps (tmptrack);
+    caps = ges_track_get_restriction_caps (track);
+    if (!gst_caps_is_equal (caps, default_caps)) {
+      tmpstr = gst_caps_serialize (caps, 0);
+
+      g_string_append (res, " restrictions=");
+      _sanitize_argument (tmpstr, res);
+      g_free (tmpstr);
+    }
+    gst_caps_unref (default_caps);
+    gst_caps_unref (caps);
+    gst_object_unref (tmptrack);
+  }
+
+  for (tmp = timeline->layers, i = 0; tmp; tmp = tmp->next, i++) {
+    GList *tmpclip, *clips = ges_layer_get_clips (tmp->data);
+    GList *tmptrackelem;
+
+    for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
+      GESClip *clip = tmpclip->data;
+      GESCommandLineOption *option = NULL;
+
+      if (GES_IS_TEST_CLIP (clip)) {
+        GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
+        const gchar *id = ges_asset_get_id (asset);
+
+        g_string_append (res, " +test-clip ");
+
+        _sanitize_argument (g_enum_get_value (g_type_class_peek
+                (GES_VIDEO_TEST_PATTERN_TYPE),
+                ges_test_clip_get_vpattern (GES_TEST_CLIP (clip)))->value_nick,
+            res);
+
+        if (g_strcmp0 (id, "GESTestClip")) {
+          g_string_append (res, " asset-id=");
+          _sanitize_argument (id, res);
+        }
+
+        option = &options[TEST_CLIP];
+      } else if (GES_IS_TITLE_CLIP (clip)) {
+        g_string_append (res, " +title ");
+        _sanitize_argument (ges_title_clip_get_text (GES_TITLE_CLIP (clip)),
+            res);
+        option = &options[TITLE];
+      } else if (GES_IS_URI_CLIP (clip)) {
+        g_string_append (res, " +clip ");
+
+        _sanitize_argument (ges_uri_clip_get_uri (GES_URI_CLIP (clip)), res);
+        option = &options[CLIP];
+      } else {
+        g_warning ("Unhandled clip type: %s", G_OBJECT_TYPE_NAME (clip));
+        continue;
+      }
+
+      _serialize_clip_track_types (clip, tt, res);
+
+      if (i)
+        g_string_append_printf (res, " layer=%d", i);
+
+      _serialize_object_properties (G_OBJECT (clip), option, FALSE, res);
+      _serialize_clip_effects (clip, res);
+
+      for (tmptrackelem = GES_CONTAINER_CHILDREN (clip); tmptrackelem;
+          tmptrackelem = tmptrackelem->next)
+        _serialize_object_properties (G_OBJECT (tmptrackelem->data), option,
+            TRUE, res);
+    }
+    g_list_free_full (clips, gst_object_unref);
+  }
+
+done:
+  return g_string_free (res, FALSE);
+  {
+    GstUri *uri = gst_uri_from_string (res->str);
+    gchar *uri_str = gst_uri_to_string (uri);
+
+    g_string_free (res, TRUE);
+
+    return uri_str;
+  }
+}
index 1158fd9..eaee523 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
-#ifndef _GES_COMMAND_LINE_FORMATTER_H_
-#define _GES_COMMAND_LINE_FORMATTER_H_
+#pragma once
 
 #include <glib-object.h>
 #include "ges-formatter.h"
 
 G_BEGIN_DECLS
 
-#define GES_TYPE_COMMAND_LINE_FORMATTER             (ges_command_line_formatter_get_type ())
-#define GES_COMMAND_LINE_FORMATTER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatter))
-#define GES_COMMAND_LINE_FORMATTER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterClass))
-#define GES_IS_COMMAND_LINE_FORMATTER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_COMMAND_LINE_FORMATTER))
-#define GES_IS_COMMAND_LINE_FORMATTER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_COMMAND_LINE_FORMATTER))
-#define GES_COMMAND_LINE_FORMATTER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_COMMAND_LINE_FORMATTER, GESCommandLineFormatterClass))
-
 typedef struct _GESCommandLineFormatterClass GESCommandLineFormatterClass;
 typedef struct _GESCommandLineFormatter GESCommandLineFormatter;
-typedef struct _GESCommandLineFormatterPrivate GESCommandLineFormatterPrivate;
 
+#define GES_TYPE_COMMAND_LINE_FORMATTER             (ges_command_line_formatter_get_type ())
+GES_DECLARE_TYPE(CommandLineFormatter, command_line_formatter, COMMAND_LINE_FORMATTER);
 
 struct _GESCommandLineFormatterClass
 {
@@ -51,11 +44,9 @@ struct _GESCommandLineFormatter
 };
 
 GES_API
-GType ges_command_line_formatter_get_type (void);
-GES_API
 gchar * ges_command_line_formatter_get_help (gint nargs, gchar ** commands);
 
-G_END_DECLS
-
-#endif /* _GES_COMMAND_LINE_FORMATTER_H_ */
+GES_API
+gchar * ges_command_line_formatter_get_timeline_uri (GESTimeline *timeline);
 
+G_END_DECLS
index 9a30a85..71c3f38 100644 (file)
 /**
  * SECTION:gescontainer
  * @title: GESContainer
- * @short_description: Base Class for objects responsible for controlling other
- * GESTimelineElement-s
+ * @short_description: Base Class for elements responsible for controlling
+ * other #GESTimelineElement-s
+ *
+ * A #GESContainer is a timeline element that controls other
+ * #GESTimelineElement-s, which are its children. In particular, it is
+ * responsible for maintaining the relative #GESTimelineElement:start and
+ * #GESTimelineElement:duration times of its children. Therefore, if a
+ * container is temporally adjusted or moved to a new layer, it may
+ * accordingly adjust and move its children. Similarly, a change in one of
+ * its children may prompt the parent to correspondingly change its
+ * siblings.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -49,12 +58,11 @@ typedef struct
 
   GstClockTime start_offset;
   GstClockTime duration_offset;
-  GstClockTime inpoint_offset;
-  gint32 priority_offset;
 
-  guint start_notifyid;
-  guint duration_notifyid;
-  guint inpoint_notifyid;
+  gulong start_notifyid;
+  gulong duration_notifyid;
+  gulong child_property_added_notifyid;
+  gulong child_property_removed_notifyid;
 } ChildMapping;
 
 enum
@@ -109,10 +117,16 @@ _free_mapping (ChildMapping * mapping)
     g_signal_handler_disconnect (child, mapping->start_notifyid);
   if (mapping->duration_notifyid)
     g_signal_handler_disconnect (child, mapping->duration_notifyid);
-  if (mapping->inpoint_notifyid)
-    g_signal_handler_disconnect (child, mapping->inpoint_notifyid);
+  if (mapping->child_property_added_notifyid)
+    g_signal_handler_disconnect (child, mapping->child_property_added_notifyid);
+  if (mapping->child_property_removed_notifyid)
+    g_signal_handler_disconnect (child,
+        mapping->child_property_removed_notifyid);
+  if (child) {
+    ges_timeline_element_set_parent (child, NULL);
+    gst_object_unref (child);
+  }
 
-  ges_timeline_element_set_parent (child, NULL);
   g_slice_free (ChildMapping, mapping);
 }
 
@@ -137,10 +151,11 @@ compare_grouping_prio (GType * a, GType * b)
 }
 
 static void
-_resync_start_offsets (GESTimelineElement * child,
+_resync_position_offsets (GESTimelineElement * child,
     ChildMapping * map, GESContainer * container)
 {
   map->start_offset = _START (container) - _START (child);
+  map->duration_offset = _DURATION (container) - _DURATION (child);
 }
 
 /*****************************************************
@@ -171,22 +186,6 @@ _set_start (GESTimelineElement * element, GstClockTime start)
 }
 
 static gboolean
-_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
-{
-  GList *tmp;
-  GESContainer *container = GES_CONTAINER (element);
-
-  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
-    ChildMapping *map = g_hash_table_lookup (container->priv->mappings, child);
-
-    map->inpoint_offset = inpoint - _INPOINT (child);
-  }
-
-  return TRUE;
-}
-
-static gboolean
 _set_duration (GESTimelineElement * element, GstClockTime duration)
 {
   GList *tmp;
@@ -204,78 +203,108 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
 }
 
 static void
+_add_childs_child_property (GESTimelineElement * container_child,
+    GObject * prop_child, GParamSpec * property, GESContainer * container)
+{
+  /* the container_child is kept as the owner of this child property when
+   * we register it on ourselves, but we use the same GObject child
+   * instance who the property comes from */
+  gboolean res =
+      ges_timeline_element_add_child_property_full (GES_TIMELINE_ELEMENT
+      (container), container_child, property, prop_child);
+  if (!res)
+    GST_INFO_OBJECT (container, "Could not register the child property '%s' "
+        "of our child %" GES_FORMAT " for the object %" GST_PTR_FORMAT,
+        property->name, GES_ARGS (container_child), prop_child);
+}
+
+static void
 _ges_container_add_child_properties (GESContainer * container,
     GESTimelineElement * child)
 {
   guint n_props, i;
 
+  /* use get_children_properties, rather than list_children_properties
+   * to ensure we are getting all the properties, without any interference
+   * from the ->list_children_properties vmethods */
   GParamSpec **child_props =
-      ges_timeline_element_list_children_properties (child,
+      ges_timeline_element_get_children_properties (child,
       &n_props);
 
   for (i = 0; i < n_props; i++) {
-    GObject *prop_child;
-    gchar *prop_name = g_strdup_printf ("%s::%s",
-        g_type_name (child_props[i]->owner_type),
-        child_props[i]->name);
-
-    if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
-      ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container),
-          child_props[i], prop_child);
-      gst_object_unref (prop_child);
-
-    }
-    g_free (prop_name);
-    g_param_spec_unref (child_props[i]);
+    GParamSpec *property = child_props[i];
+    GObject *prop_child =
+        ges_timeline_element_get_child_from_child_property (child, property);
+    if (prop_child)
+      _add_childs_child_property (child, prop_child, property, container);
+    g_param_spec_unref (property);
   }
 
   g_free (child_props);
 }
 
 static void
+_remove_childs_child_property (GESTimelineElement * container_child,
+    GObject * prop_child, GParamSpec * property, GESContainer * container)
+{
+  /* NOTE: some children may share the same GParamSpec. Currently, only
+   * the first such child added will have its children properties
+   * successfully registered for the container (even though the GObject
+   * child who the properties belong to will be a different instance). As
+   * such, we only want to remove the child property if it corresponds to
+   * the same instance that the parent container has.
+   * E.g. if we add child1 and child2, that have the same (or some
+   * overlapping) children properties. And child1 is added before child2,
+   * then child2's overlapping children properties would not be registered.
+   * If we remove child2, we do *not* want to removed the child properties
+   * for child1 because they belong to a GObject instance that we still
+   * have in our control.
+   * If we remove child1, we *do* want to remove the child properties for
+   * child1, even though child2 may overlap with some of them, because we
+   * are loosing the specific GObject instance that it belongs to!
+   * We could try and register the ones that match for the other children.
+   * However, it is probably simpler to change
+   * ges_timeline_element_add_child_property_full to accept the same
+   * GParamSpec, for different instances.
+   */
+  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
+  GObject *our_prop_child =
+      ges_timeline_element_get_child_from_child_property (element, property);
+  if (our_prop_child == prop_child)
+    ges_timeline_element_remove_child_property (element, property);
+  else
+    GST_INFO_OBJECT (container, "Not removing child property '%s' for child"
+        " %" GES_FORMAT " because it derives from the object %" GST_PTR_FORMAT
+        "(%p) rather than the object %" GST_PTR_FORMAT "(%p)", property->name,
+        GES_ARGS (container_child), prop_child, prop_child, our_prop_child,
+        our_prop_child);
+}
+
+static void
 _ges_container_remove_child_properties (GESContainer * container,
     GESTimelineElement * child)
 {
   guint n_props, i;
 
+  /* use get_children_properties, rather than list_children_properties
+   * to ensure we are getting all the properties, without any interference
+   * from the ->list_children_properties vmethods */
   GParamSpec **child_props =
-      ges_timeline_element_list_children_properties (child,
+      ges_timeline_element_get_children_properties (child,
       &n_props);
 
   for (i = 0; i < n_props; i++) {
-    GObject *prop_child;
-    gchar *prop_name = g_strdup_printf ("%s::%s",
-        g_type_name (child_props[i]->owner_type),
-        child_props[i]->name);
-
-    if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
-      ges_timeline_element_remove_child_property (GES_TIMELINE_ELEMENT
-          (container), child_props[i]);
-      gst_object_unref (prop_child);
-
-    }
-
-    g_free (prop_name);
-    g_param_spec_unref (child_props[i]);
+    GParamSpec *property = child_props[i];
+    GObject *prop_child =
+        ges_timeline_element_get_child_from_child_property (child, property);
+    if (prop_child)
+      _remove_childs_child_property (child, prop_child, property, container);
+    g_param_spec_unref (property);
   }
 
   g_free (child_props);
 }
 
-static GParamSpec **
-_list_children_properties (GESTimelineElement * self, guint * n_properties)
-{
-  GList *tmp;
-
-  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next)
-    _ges_container_add_child_properties (GES_CONTAINER (self), tmp->data);
-
-  return
-      GES_TIMELINE_ELEMENT_CLASS
-      (ges_container_parent_class)->list_children_properties (self,
-      n_properties);
-}
-
 static gboolean
 _lookup_child (GESTimelineElement * self, const gchar * prop_name,
     GObject ** child, GParamSpec ** pspec)
@@ -316,15 +345,11 @@ _deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
   GESContainer *self = GES_CONTAINER (element), *ccopy = GES_CONTAINER (copy);
 
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
-    ChildMapping *map;
-
-    map =
-        g_slice_dup (ChildMapping, g_hash_table_lookup (self->priv->mappings,
-            tmp->data));
+    ChildMapping *map, *orig_map;
+    orig_map = g_hash_table_lookup (self->priv->mappings, tmp->data);
+    map = g_slice_new0 (ChildMapping);
     map->child = ges_timeline_element_copy (tmp->data, TRUE);
-    map->start_notifyid = 0;
-    map->inpoint_notifyid = 0;
-    map->duration_notifyid = 0;
+    map->start_offset = orig_map->start_offset;
 
     ccopy->priv->copied_children = g_list_prepend (ccopy->priv->copied_children,
         map);
@@ -357,9 +382,10 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
       return NULL;
     }
 
-    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (ncontainer),
-        GES_TIMELINE_ELEMENT_TIMELINE (ref));
-    ges_container_add (ncontainer, nchild);
+    /* for GESGroups, this may register the group on the timeline */
+    if (!ges_container_add (ncontainer, nchild))
+      GST_ERROR ("%" GES_FORMAT " could not add child %p while"
+          " copying, this should never happen", GES_ARGS (ncontainer), nchild);
   }
 
   return GES_TIMELINE_ELEMENT (ncontainer);
@@ -446,7 +472,10 @@ ges_container_class_init (GESContainerClass * klass)
   /**
    * GESContainer:height:
    *
-   * The span of priorities which this container occupies.
+   * The span of the container's children's #GESTimelineElement:priority
+   * values, which is the number of integers that lie between (inclusive)
+   * the minimum and maximum priorities found amongst the container's
+   * children (maximum - minimum + 1).
    */
   properties[PROP_HEIGHT] = g_param_spec_uint ("height", "Height",
       "The span of priorities this container occupies", 0, G_MAXUINT, 1,
@@ -456,38 +485,33 @@ ges_container_class_init (GESContainerClass * klass)
 
   /**
    * GESContainer::child-added:
-   * @container: the #GESContainer
-   * @element: the #GESTimelineElement that was added.
+   * @container: The #GESContainer
+   * @element: The child that was added
    *
-   * Will be emitted after a child was added to @container.
-   * Usually you should connect with #g_signal_connect_after
-   * as in the first emission stage, the signal emission might
-   * get stopped internally.
+   * Will be emitted after a child is added to the container. Usually,
+   * you should connect with g_signal_connect_after() since the signal
+   * may be stopped internally.
    */
   ges_container_signals[CHILD_ADDED_SIGNAL] =
       g_signal_new ("child-added", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESContainerClass, child_added),
-      NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT);
 
   /**
    * GESContainer::child-removed:
-   * @container: the #GESContainer
-   * @element: the #GESTimelineElement that was removed.
+   * @container: The #GESContainer
+   * @element: The child that was removed
    *
-   * Will be emitted after a child was removed from @container.
+   * Will be emitted after a child is removed from the container.
    */
   ges_container_signals[CHILD_REMOVED_SIGNAL] =
       g_signal_new ("child-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESContainerClass, child_removed),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
-      GES_TYPE_TIMELINE_ELEMENT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT);
 
 
   element_class->set_start = _set_start;
   element_class->set_duration = _set_duration;
-  element_class->set_inpoint = _set_inpoint;
-  element_class->list_children_properties = _list_children_properties;
   element_class->lookup_child = _lookup_child;
   element_class->get_track_types = _get_track_types;
   element_class->paste = _paste;
@@ -523,86 +547,91 @@ ges_container_init (GESContainer * self)
  *    Property notifications from Children    *
  *                                            *
  **********************************************/
+
 static void
-_child_start_changed_cb (GESTimelineElement * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
+_update_start_duration (GESContainer * container, GESTimelineElement * child)
 {
-  ChildMapping *map;
-  GstClockTime start;
-
-  GESContainerPrivate *priv = container->priv;
-  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
-  GESChildrenControlMode pmode = container->children_control_mode;
-
-  map = g_hash_table_lookup (priv->mappings, child);
-  g_assert (map);
+  GList *tmp;
+  GstClockTime duration, end = 0, start = G_MAXUINT64;
+  gboolean was_being_edited = GES_TIMELINE_ELEMENT_BEING_EDITED (container);
+
+  if (!container->children) {
+    /* If we are now empty, keep the same duration and start. This works
+     * well for a clip. For a group, the duration should probably be set
+     * to 0, but it gets automatically removed from the timeline when it
+     * is emptied */
+    return;
+  }
 
-  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
-    container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES;
+  GES_TIMELINE_ELEMENT_SET_BEING_EDITED (container);
 
-  switch (container->children_control_mode) {
-    case GES_CHILDREN_IGNORE_NOTIFIES:
-      return;
-    case GES_CHILDREN_UPDATE_ALL_VALUES:
-      _ges_container_sort_children (container);
-      start = container->children ?
-          _START (container->children->data) : _START (container);
+  for (tmp = container->children; tmp; tmp = tmp->next) {
+    start = MIN (start, _START (tmp->data));
+    end = MAX (end, _END (tmp->data));
+  }
 
-      if (start != _START (container)) {
-        _DURATION (container) = _END (container) - start;
-        _START (container) = start;
+  if (end < start)
+    duration = 0;
+  else
+    duration = end - start;
 
-        GST_DEBUG_OBJECT (container, "Child move made us move %" GES_FORMAT,
-            GES_ARGS (container));
+  if (start != _START (container) || duration != _DURATION (container)) {
+    GstClockTime prev_dur = _DURATION (container);
+    GstClockTime prev_start = _START (container);
 
-        g_object_notify (G_OBJECT (container), "start");
-      }
+    _DURATION (container) = duration;
+    _START (container) = start;
 
-      /* Falltrough! */
-    case GES_CHILDREN_UPDATE_OFFSETS:
-      map->start_offset = _START (container) - _START (child);
-      break;
+    GST_INFO ("%" GES_FORMAT " child %" GES_FORMAT " move made us move",
+        GES_ARGS (container), GES_ARGS (child));
 
-    case GES_CHILDREN_UPDATE:
-      /* We update all the children calling our set_start method */
-      container->initiated_move = child;
-      _set_start0 (element, _START (child) + map->start_offset);
-      container->initiated_move = NULL;
-      break;
-    default:
-      break;
+    if (prev_start != start)
+      g_object_notify (G_OBJECT (container), "start");
+    if (prev_dur != duration)
+      g_object_notify (G_OBJECT (container), "duration");
   }
+  if (!was_being_edited)
+    GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (container);
 
-  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
-    container->children_control_mode = pmode;
+  g_hash_table_foreach (container->priv->mappings,
+      (GHFunc) _resync_position_offsets, container);
 }
 
 static void
-_child_inpoint_changed_cb (GESTimelineElement * child,
+_child_start_changed_cb (GESTimelineElement * child,
     GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
 {
   ChildMapping *map;
 
   GESContainerPrivate *priv = container->priv;
   GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
+  GESChildrenControlMode mode = container->children_control_mode;
 
-  if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
+  if (mode == GES_CHILDREN_IGNORE_NOTIFIES)
     return;
 
+  if (GES_TIMELINE_ELEMENT_BEING_EDITED (child))
+    mode = GES_CHILDREN_UPDATE_ALL_VALUES;
+
   map = g_hash_table_lookup (priv->mappings, child);
   g_assert (map);
 
-  if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS
-      || ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-    map->inpoint_offset = _START (container) - _START (child);
-
-    return;
+  switch (mode) {
+    case GES_CHILDREN_UPDATE_ALL_VALUES:
+      _update_start_duration (container, child);
+      break;
+    case GES_CHILDREN_UPDATE_OFFSETS:
+      map->start_offset = _START (container) - _START (child);
+      break;
+    case GES_CHILDREN_UPDATE:
+      /* We update all the children calling our set_start method */
+      container->initiated_move = child;
+      _set_start0 (element, _START (child) + map->start_offset);
+      container->initiated_move = NULL;
+      break;
+    default:
+      break;
   }
-
-  /* We update all the children calling our set_inpoint method */
-  container->initiated_move = child;
-  _set_inpoint0 (element, _INPOINT (child) + map->inpoint_offset);
-  container->initiated_move = NULL;
 }
 
 static void
@@ -611,50 +640,38 @@ _child_duration_changed_cb (GESTimelineElement * child,
 {
   ChildMapping *map;
 
-  GList *tmp;
-  GstClockTime end = 0;
   GESContainerPrivate *priv = container->priv;
   GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
-  GESChildrenControlMode pmode = container->children_control_mode;
+  GESChildrenControlMode mode = container->children_control_mode;
 
-  if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
+  if (mode == GES_CHILDREN_IGNORE_NOTIFIES)
     return;
 
-  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
-    container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES;
+  if (GES_TIMELINE_ELEMENT_BEING_EDITED (child))
+    mode = GES_CHILDREN_UPDATE_ALL_VALUES;
 
   map = g_hash_table_lookup (priv->mappings, child);
   g_assert (map);
 
-  switch (container->children_control_mode) {
-    case GES_CHILDREN_IGNORE_NOTIFIES:
-      break;
+  switch (mode) {
     case GES_CHILDREN_UPDATE_ALL_VALUES:
-      _ges_container_sort_children_by_end (container);
-
-      for (tmp = container->children; tmp; tmp = tmp->next)
-        end = MAX (end, _END (tmp->data));
-
-      if (end != _END (container)) {
-        _DURATION (container) = end - _START (container);
-        g_object_notify (G_OBJECT (container), "duration");
-      }
-      /* Falltrough */
+      _update_start_duration (container, child);
+      break;
     case GES_CHILDREN_UPDATE_OFFSETS:
-      map->inpoint_offset = _START (container) - _START (child);
+      map->duration_offset = _DURATION (container) - _DURATION (child);
       break;
     case GES_CHILDREN_UPDATE:
       /* We update all the children calling our set_duration method */
       container->initiated_move = child;
+      /* FIXME: this is *not* the correct duration for a group!
+       * the ->set_duration method for GESGroup tries to hack around
+       * this by calling set_duration on itself to the actual value */
       _set_duration0 (element, _DURATION (child) + map->duration_offset);
       container->initiated_move = NULL;
       break;
     default:
       break;
   }
-
-  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
-    container->children_control_mode = pmode;
 }
 
 /****************************************************
@@ -671,13 +688,6 @@ _ges_container_sort_children (GESContainer * container)
 }
 
 void
-_ges_container_sort_children_by_end (GESContainer * container)
-{
-  container->children = g_list_sort (container->children,
-      (GCompareFunc) element_end_compare);
-}
-
-void
 _ges_container_set_height (GESContainer * container, guint32 height)
 {
   if (container->height != height) {
@@ -687,28 +697,6 @@ _ges_container_set_height (GESContainer * container, guint32 height)
   }
 }
 
-gint
-_ges_container_get_priority_offset (GESContainer * container,
-    GESTimelineElement * elem)
-{
-  ChildMapping *map = g_hash_table_lookup (container->priv->mappings, elem);
-
-  g_return_val_if_fail (map, 0);
-
-  return map->priority_offset;
-}
-
-void
-_ges_container_set_priority_offset (GESContainer * container,
-    GESTimelineElement * elem, gint32 priority_offset)
-{
-  ChildMapping *map = g_hash_table_lookup (container->priv->mappings, elem);
-
-  g_return_if_fail (map);
-
-  map->priority_offset = priority_offset;
-}
-
 /**********************************************
  *                                            *
  *            API implementation              *
@@ -717,19 +705,29 @@ _ges_container_set_priority_offset (GESContainer * container,
 
 /**
  * ges_container_add:
- * @container: a #GESContainer
- * @child: the #GESTimelineElement
+ * @container: A #GESContainer
+ * @child: The element to add as a child
  *
- * Add the #GESTimelineElement to the container.
+ * Adds a timeline element to the container. The element will now be a
+ * child of the container (and the container will be the
+ * #GESTimelineElement:parent of the added element), which means that it
+ * is now controlled by the container. This may change the properties of
+ * the child or the container, depending on the subclass.
  *
- * Returns: %TRUE on success, %FALSE on failure.
+ * Additionally, the children properties of the newly added element will
+ * be shared with the container, meaning they can also be read and set
+ * using ges_timeline_element_get_child_property() and
+ * ges_timeline_element_set_child_property() on the container.
+ *
+ * Returns: %TRUE if @child was successfully added to @container.
  */
 gboolean
 ges_container_add (GESContainer * container, GESTimelineElement * child)
 {
   ChildMapping *mapping;
-  gboolean notify_start = FALSE;
+  gboolean ret = FALSE;
   GESContainerClass *class;
+  GList *current_children, *tmp;
   GESContainerPrivate *priv;
 
   g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
@@ -742,36 +740,29 @@ ges_container_add (GESContainer * container, GESTimelineElement * child)
   GST_DEBUG_OBJECT (container, "adding timeline element %" GST_PTR_FORMAT,
       child);
 
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+  /* freeze all notifies */
+  g_object_freeze_notify (G_OBJECT (container));
+  /* copy to use at end, since container->children may have child
+   * added to it */
+  current_children = g_list_copy_deep (container->children,
+      (GCopyFunc) gst_object_ref, NULL);
+  for (tmp = current_children; tmp; tmp = tmp->next)
+    g_object_freeze_notify (G_OBJECT (tmp->data));
+  g_object_freeze_notify (G_OBJECT (child));
+  gst_object_ref_sink (child);
+
   if (class->add_child) {
     if (class->add_child (container, child) == FALSE) {
-      container->children_control_mode = GES_CHILDREN_UPDATE;
-      GST_WARNING_OBJECT (container, "Erreur adding child %p", child);
-      return FALSE;
+      GST_WARNING_OBJECT (container, "Error adding child %p", child);
+      goto done;
     }
   }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
-
-  if (_START (container) > _START (child)) {
-    _START (container) = _START (child);
-
-    g_hash_table_foreach (priv->mappings, (GHFunc) _resync_start_offsets,
-        container);
-    notify_start = TRUE;
-  }
 
   mapping = g_slice_new0 (ChildMapping);
   mapping->child = gst_object_ref (child);
-  mapping->start_offset = _START (container) - _START (child);
-  mapping->duration_offset = _DURATION (container) - _DURATION (child);
-  mapping->inpoint_offset = _INPOINT (container) - _INPOINT (child);
-
   g_hash_table_insert (priv->mappings, child, mapping);
-
   container->children = g_list_prepend (container->children, child);
 
-  _ges_container_sort_children (container);
-
   /* Listen to all property changes */
   mapping->start_notifyid =
       g_signal_connect (G_OBJECT (child), "notify::start",
@@ -779,9 +770,6 @@ ges_container_add (GESContainer * container, GESTimelineElement * child)
   mapping->duration_notifyid =
       g_signal_connect (G_OBJECT (child), "notify::duration",
       G_CALLBACK (_child_duration_changed_cb), container);
-  mapping->inpoint_notifyid =
-      g_signal_connect (G_OBJECT (child), "notify::in-point",
-      G_CALLBACK (_child_inpoint_changed_cb), container);
 
   if (ges_timeline_element_set_parent (child, GES_TIMELINE_ELEMENT (container))
       == FALSE) {
@@ -790,38 +778,60 @@ ges_container_add (GESContainer * container, GESTimelineElement * child)
 
     g_hash_table_remove (priv->mappings, child);
     container->children = g_list_remove (container->children, child);
-    _ges_container_sort_children (container);
 
-    return FALSE;
+    goto done;
   }
 
+  _update_start_duration (container, child);
+  _ges_container_sort_children (container);
+
   _ges_container_add_child_properties (container, child);
+  mapping->child_property_added_notifyid =
+      g_signal_connect (G_OBJECT (child), "child-property-added",
+      G_CALLBACK (_add_childs_child_property), container);
+  mapping->child_property_removed_notifyid =
+      g_signal_connect (G_OBJECT (child), "child-property-removed",
+      G_CALLBACK (_remove_childs_child_property), container);
 
   priv->adding_children = g_list_prepend (priv->adding_children, child);
   g_signal_emit (container, ges_container_signals[CHILD_ADDED_SIGNAL], 0,
       child);
   priv->adding_children = g_list_remove (priv->adding_children, child);
 
-  if (notify_start)
-    g_object_notify (G_OBJECT (container), "start");
+  ret = TRUE;
 
-  return TRUE;
+done:
+  /* thaw all notifies */
+  /* Ignore notifies for the start and duration since the child should
+   * already be correctly set up */
+  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+  g_object_thaw_notify (G_OBJECT (container));
+  for (tmp = current_children; tmp; tmp = tmp->next)
+    g_object_thaw_notify (G_OBJECT (tmp->data));
+  g_object_thaw_notify (G_OBJECT (child));
+  g_list_free_full (current_children, gst_object_unref);
+  gst_object_unref (child);
+  container->children_control_mode = GES_CHILDREN_UPDATE;
+  return ret;
 }
 
 /**
  * ges_container_remove:
- * @container: a #GESContainer
- * @child: the #GESTimelineElement to release
+ * @container: A #GESContainer
+ * @child: The child to remove
  *
- * Release the @child from the control of @container.
+ * Removes a timeline element from the container. The element will no
+ * longer be controlled by the container.
  *
- * Returns: %TRUE if the @child was properly released, else %FALSE.
+ * Returns: %TRUE if @child was successfully removed from @container.
  */
 gboolean
 ges_container_remove (GESContainer * container, GESTimelineElement * child)
 {
   GESContainerClass *klass;
   GESContainerPrivate *priv;
+  GList *current_children, *tmp;
+  gboolean ret = FALSE;
 
   g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE);
@@ -837,13 +847,25 @@ ges_container_remove (GESContainer * container, GESTimelineElement * child)
     return FALSE;
   }
 
+  /* ref the container since it might be destroyed when the child is
+   * removed! (see GESGroup ->child_removed) */
+  gst_object_ref (container);
+  /* freeze all notifies */
+  g_object_freeze_notify (G_OBJECT (container));
+  /* copy to use at end, since container->children may have child
+   * removed from it */
+  current_children = g_list_copy_deep (container->children,
+      (GCopyFunc) gst_object_ref, NULL);
+  for (tmp = current_children; tmp; tmp = tmp->next)
+    g_object_freeze_notify (G_OBJECT (tmp->data));
+
+
   if (klass->remove_child) {
     if (klass->remove_child (container, child) == FALSE)
-      return FALSE;
+      goto done;
   }
 
   container->children = g_list_remove (container->children, child);
-  /* Let it live removing from our mappings */
   g_hash_table_remove (priv->mappings, child);
 
   _ges_container_remove_child_properties (container, child);
@@ -852,12 +874,30 @@ ges_container_remove (GESContainer * container, GESTimelineElement * child)
     g_signal_emit (container, ges_container_signals[CHILD_REMOVED_SIGNAL], 0,
         child);
   } else {
-    GST_INFO_OBJECT (container, "Not emitting 'child-removed' signal as child"
+    GESContainerClass *klass = GES_CONTAINER_GET_CLASS (container);
+
+    if (klass->child_removed)
+      klass->child_removed (container, child);
+
+    GST_INFO_OBJECT (container,
+        "Not emitting 'child-removed' signal as child"
         " removal happend during 'child-added' signal emission");
   }
-  gst_object_unref (child);
 
-  return TRUE;
+  _update_start_duration (container, child);
+
+  ret = TRUE;
+
+done:
+  /* thaw all notifies */
+  g_object_thaw_notify (G_OBJECT (container));
+  for (tmp = current_children; tmp; tmp = tmp->next)
+    g_object_thaw_notify (G_OBJECT (tmp->data));
+  g_list_free_full (current_children, gst_object_unref);
+
+  gst_object_unref (container);
+
+  return ret;
 }
 
 static void
@@ -879,15 +919,16 @@ _get_children_recursively (GESContainer * container, GList ** children)
 
 /**
  * ges_container_get_children:
- * @container: a #GESContainer
+ * @container: A #GESContainer
  * @recursive:  Whether to recursively get children in @container
  *
- * Get the list of #GESTimelineElement contained in @container
- * The user is responsible for unreffing the contained objects
- * and freeing the list.
+ * Get the list of timeline elements contained in the container. If
+ * @recursive is %TRUE, and the container contains other containers as
+ * children, then their children will be added to the list, in addition to
+ * themselves, and so on.
  *
  * Returns: (transfer full) (element-type GESTimelineElement): The list of
- * timeline element contained in @container.
+ * #GESTimelineElement-s contained in @container.
  */
 GList *
 ges_container_get_children (GESContainer * container, gboolean recursive)
@@ -906,17 +947,23 @@ ges_container_get_children (GESContainer * container, gboolean recursive)
 
 /**
  * ges_container_ungroup:
- * @container: (transfer full): The #GESContainer to ungroup
- * @recursive: Wether to recursively ungroup @container
+ * @container: (transfer full): The container to ungroup
+ * @recursive: Whether to recursively ungroup @container
+ *
+ * Ungroups the container by splitting it into several containers
+ * containing various children of the original. The rules for how the
+ * container splits depends on the subclass. A #GESGroup will simply split
+ * into its children. A #GESClip will split into one #GESClip per
+ * #GESTrackType it overlaps with (so an audio-video clip will split into
+ * an audio clip and a video clip), where each clip contains all the
+ * #GESTrackElement-s from the original clip with a matching
+ * #GESTrackElement:track-type.
  *
- * Ungroups the #GESTimelineElement contained in this GESContainer,
- * creating new #GESContainer containing those #GESTimelineElement
- * apropriately.
+ * If @recursive is %TRUE, and the container contains other containers as
+ * children, then they will also be ungrouped, and so on.
  *
  * Returns: (transfer full) (element-type GESContainer): The list of
- * #GESContainer resulting from the ungrouping operation
- * The user is responsible for unreffing the contained objects
- * and freeing the list.
+ * new #GESContainer-s created from the splitting of @container.
  */
 GList *
 ges_container_ungroup (GESContainer * container, gboolean recursive)
@@ -939,18 +986,23 @@ ges_container_ungroup (GESContainer * container, gboolean recursive)
 
 /**
  * ges_container_group:
- * @containers: (transfer none)(element-type GESContainer) (allow-none): The
- * #GESContainer to group, they must all be in a same #GESTimeline
+ * @containers: (transfer none)(element-type GESContainer) (allow-none):
+ * The #GESContainer-s to group
  *
- * Groups the #GESContainer-s provided in @containers. It creates a subclass
- * of #GESContainer, depending on the containers provided in @containers.
- * Basically, if all the containers in @containers should be contained in a same
- * clip (all the #GESTrackElement they contain have the exact same
- * start/inpoint/duration and are in the same layer), it will create a #GESClip
- * otherwise a #GESGroup will be created
+ * Groups the containers into a single container by merging them. The
+ * containers must all belong to the same #GESTimelineElement:timeline.
  *
- * Returns: (transfer none): The #GESContainer (subclass) resulting of the
- * grouping
+ * If the elements are all #GESClip-s then this method will attempt to
+ * combine them all into a single #GESClip. This should succeed if they:
+ * share the same #GESTimelineElement:start, #GESTimelineElement:duration
+ * and #GESTimelineElement:in-point; exist in the same layer; and all of
+ * the sources share the same #GESAsset. If this fails, or one of the
+ * elements is not a #GESClip, this method will try to create a #GESGroup
+ * instead.
+ *
+ * Returns: (transfer floating): The container created by merging
+ * @containers, or %NULL if they could not be merged into a single
+ * container.
  */
 GESContainer *
 ges_container_group (GList * containers)
@@ -971,8 +1023,14 @@ ges_container_group (GList * containers)
     g_return_val_if_fail (timeline, NULL);
   }
 
-  if (g_list_length (containers) == 1)
+  if (g_list_length (containers) == 1) {
+    /* FIXME: Should return a floating **copy**. API specifies that the
+     * returned element is created. So users might expect to be able to
+     * freely dispose of the list, without the risk of the returned
+     * element being freed as well.
+     * TODO 2.0: (transfer full) would have been better */
     return containers->data;
+  }
 
   for (tmp = containers; tmp; tmp = tmp->next) {
     g_return_val_if_fail (GES_IS_CONTAINER (tmp->data), NULL);
@@ -982,12 +1040,15 @@ ges_container_group (GList * containers)
         NULL);
   }
 
+  /* FIXME: how can user sub-classes interact with this if
+   * ->grouping_priority is private? */
   children_types = g_type_children (GES_TYPE_CONTAINER, &n_children);
   g_qsort_with_data (children_types, n_children, sizeof (GType),
       (GCompareDataFunc) compare_grouping_prio, NULL);
 
   for (i = 0; i < n_children; i++) {
     clip_class = g_type_class_peek (children_types[i]);
+    /* FIXME: handle NULL ->group */
     ret = GES_CONTAINER_CLASS (clip_class)->group (containers);
 
     if (ret)
@@ -1000,22 +1061,22 @@ ges_container_group (GList * containers)
 
 /**
  * ges_container_edit:
- * @container: the #GESClip to edit
- * @layers: (element-type GESLayer): The layers you want the edit to
- *  happen in, %NULL means that the edition is done in all the
- *  #GESLayers contained in the current timeline.
- * @new_layer_priority: The priority of the layer @container should land in.
- *  If the layer you're trying to move the container to doesn't exist, it will
- *  be created automatically. -1 means no move.
- * @mode: The #GESEditMode in which the editition will happen.
- * @edge: The #GESEdge the edit should happen on.
- * @position: The position at which to edit @container (in nanosecond)
+ * @container: The #GESContainer to edit
+ * @layers: (element-type GESLayer) (nullable): A whitelist of layers
+ * where the edit can be performed, %NULL allows all layers in the
+ * timeline
+ * @new_layer_priority: The priority/index of the layer @container should
+ * be moved to. -1 means no move
+ * @mode: The edit mode
+ * @edge: The edge of @container where the edit should occur
+ * @position: The edit position: a new location for the edge of @container
+ * (in nanoseconds)
  *
- * Edit @container in the different exisiting #GESEditMode modes. In the case of
- * slide, and roll, you need to specify a #GESEdge
+ * Edits the container within its timeline.
  *
- * Returns: %TRUE if the container as been edited properly, %FALSE if an error
- * occured
+ * Returns: %TRUE if the edit of @container completed, %FALSE on failure.
+ *
+ * Deprecated: 1.18: use #ges_timeline_element_edit instead.
  */
 gboolean
 ges_container_edit (GESContainer * container, GList * layers,
@@ -1023,11 +1084,6 @@ ges_container_edit (GESContainer * container, GList * layers,
 {
   g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
 
-  if (G_UNLIKELY (GES_CONTAINER_GET_CLASS (container)->edit == NULL)) {
-    GST_WARNING_OBJECT (container, "No edit vmethod implementation");
-    return FALSE;
-  }
-
-  return GES_CONTAINER_GET_CLASS (container)->edit (container, layers,
-      new_layer_priority, mode, edge, position);
+  return ges_timeline_element_edit (GES_TIMELINE_ELEMENT (container),
+      layers, new_layer_priority, mode, edge, position);
 }
index 8b03366..2769ccc 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_CONTAINER
-#define _GES_CONTAINER
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_CONTAINER             ges_container_get_type()
-#define GES_CONTAINER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_CONTAINER, GESContainer))
-#define GES_CONTAINER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_CONTAINER, GESContainerClass))
-#define GES_IS_CONTAINER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_CONTAINER))
-#define GES_IS_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_CONTAINER))
-#define GES_CONTAINER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_CONTAINER, GESContainerClass))
+GES_DECLARE_TYPE(Container, container, CONTAINER);
 
-typedef struct _GESContainerPrivate GESContainerPrivate;
-
-/* To be used by sublcasses only */
+/**
+ * GESChildrenControlMode:
+ *
+ * To be used by subclasses only. This indicate how to handle a change in
+ * a child.
+ */
 typedef enum
 {
   GES_CHILDREN_UPDATE,
@@ -52,7 +50,7 @@ typedef enum
  * GES_CONTAINER_HEIGHT:
  * @obj: a #GESContainer
  *
- * The span of priorities this object occupies.
+ * The #GESContainer:height of @obj.
  */
 #define GES_CONTAINER_HEIGHT(obj) (((GESContainer*)obj)->height)
 
@@ -60,17 +58,17 @@ typedef enum
  * GES_CONTAINER_CHILDREN:
  * @obj: a #GESContainer
  *
- * A #GList containing the children of @object
+ * The #GList containing the children of @obj.
  */
 #define GES_CONTAINER_CHILDREN(obj) (((GESContainer*)obj)->children)
 
 /**
  * GESContainer:
- * @children: (element-type GES.TimelineElement): A list of TimelineElement
- * controlled by this Container. NOTE: Do not modify.
- * @height: The span of priorities this container occupies
+ * @children: (element-type GES.TimelineElement): The list of
+ * #GESTimelineElement-s controlled by this Container
+ * @height: The #GESContainer:height of @obj
  *
- * The #GESContainer base class.
+ * Note, you may read, but should not modify these properties.
  */
 struct _GESContainer
 {
@@ -102,9 +100,11 @@ struct _GESContainer
  * @child_removed: Virtual method that is called right after a #GESTimelineElement is removed
  * @remove_child: Virtual method to remove a child
  * @add_child: Virtual method to add a child
- * @ungroup: Ungroups the #GESTimelineElement contained in this #GESContainer, creating new
- * @group: Groups the #GESContainers together
- * #GESContainer containing those #GESTimelineElement apropriately.
+ * @ungroup: Virtual method to ungroup a container into a list of
+ * containers
+ * @group: Virtual method to group a list of containers together under a
+ * single container
+ * @edit: Deprecated
  */
 struct _GESContainerClass
 {
@@ -119,6 +119,8 @@ struct _GESContainerClass
   gboolean (*remove_child)        (GESContainer *container, GESTimelineElement *element);
   GList* (*ungroup)               (GESContainer *container, gboolean recursive);
   GESContainer * (*group)         (GList *containers);
+
+  /* Deprecated and not used anymore */
   gboolean (*edit)                (GESContainer * container,
                                    GList * layers, gint new_layer_priority,
                                    GESEditMode mode,
@@ -134,9 +136,6 @@ struct _GESContainerClass
   gpointer _ges_reserved[GES_PADDING_LARGE];
 };
 
-GES_API
-GType ges_container_get_type (void);
-
 /* Children handling */
 GES_API
 GList* ges_container_get_children (GESContainer *container, gboolean recursive);
@@ -149,7 +148,7 @@ GList * ges_container_ungroup     (GESContainer * container, gboolean recursive)
 GES_API
 GESContainer *ges_container_group (GList *containers);
 
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_element_edit)
 gboolean ges_container_edit       (GESContainer * container,
                                    GList * layers, gint new_layer_priority,
                                    GESEditMode mode,
@@ -157,4 +156,3 @@ gboolean ges_container_edit       (GESContainer * container,
                                    guint64 position);
 
 G_END_DECLS
-#endif /* _GES_CONTAINER */
index 120dbf4..fa90a66 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
-/* SECTION: geseffectasset
+/**
+ * SECTION: geseffectasset
+ * @title: GESEffectAsset
  * @short_description: A GESAsset subclass specialized in GESEffect extraction
  *
- * This is internal, and implementation details, so we are not showing it in the
- * documentation
+ * This asset has a GStreamer bin-description as ID and is able to determine
+ * to what track type the effect should be used in.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -47,7 +49,7 @@ _fill_track_type (GESAsset * asset)
   gchar *bin_desc;
   const gchar *id = ges_asset_get_id (asset);
 
-  bin_desc = ges_effect_assect_id_get_type_and_bindesc (id, &ttype, NULL);
+  bin_desc = ges_effect_asset_id_get_type_and_bindesc (id, &ttype, NULL);
 
   if (bin_desc) {
     ges_track_element_asset_set_track_type (GES_TRACK_ELEMENT_ASSET (asset),
@@ -108,35 +110,282 @@ ges_effect_asset_class_init (GESEffectAssetClass * klass)
   asset_class->extract = _extract;
 }
 
+static gboolean
+find_compatible_pads (GstElement * bin, const gchar * bin_desc,
+    GstElement * child, GstCaps * valid_caps, GstPad ** srcpad,
+    GList ** sinkpads, GList ** elems_with_reqsink,
+    GList ** elems_with_reqsrc, GError ** error)
+{
+  GList *tmp, *tmptemplate;
+
+  for (tmp = child->pads; tmp; tmp = tmp->next) {
+    GstCaps *caps;
+    GstPad *pad = tmp->data;
+
+    if (GST_PAD_PEER (pad))
+      continue;
+
+    if (GST_PAD_IS_SRC (pad) && *srcpad) {
+      g_set_error (error, GES_ERROR, GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION,
+          "More than 1 source pad in effect '%s', that is not handled",
+          bin_desc);
+      return FALSE;
+    }
+
+    caps = gst_pad_query_caps (pad, NULL);
+    if (gst_caps_can_intersect (caps, valid_caps)) {
+      if (GST_PAD_IS_SINK (pad))
+        *sinkpads = g_list_append (*sinkpads, gst_object_ref (pad));
+      else
+        *srcpad = gst_object_ref (pad);
+    } else {
+      GST_LOG_OBJECT (pad, "Can't link pad %" GST_PTR_FORMAT, caps);
+    }
+
+    gst_caps_unref (caps);
+  }
+
+  tmptemplate =
+      gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS (child));
+  for (; tmptemplate; tmptemplate = tmptemplate->next) {
+    GstPadTemplate *template = tmptemplate->data;
+
+    if (template->direction == GST_PAD_SINK) {
+      if (template->presence == GST_PAD_REQUEST)
+        *elems_with_reqsink = g_list_append (*elems_with_reqsink, child);
+    }
+  }
+
+  return TRUE;
+}
+
+static GstPad *
+request_pad (GstElement * element, GstPadDirection direction)
+{
+  GstPad *pad = NULL;
+  GList *templates;
+
+  templates = gst_element_class_get_pad_template_list
+      (GST_ELEMENT_GET_CLASS (element));
+
+  for (; templates; templates = templates->next) {
+    GstPadTemplate *templ = (GstPadTemplate *) templates->data;
+
+    GST_LOG_OBJECT (element, "Trying template %s",
+        GST_PAD_TEMPLATE_NAME_TEMPLATE (templ));
+
+    if ((GST_PAD_TEMPLATE_DIRECTION (templ) == direction) &&
+        (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST)) {
+      pad =
+          gst_element_request_pad_simple (element,
+          GST_PAD_TEMPLATE_NAME_TEMPLATE (templ));
+      if (pad)
+        break;
+    }
+  }
+
+  return pad;
+}
+
+static GstPad *
+get_pad_from_elements_with_request_pad (GstElement * effect,
+    const gchar * bin_desc, GList * requestable, GstPadDirection direction,
+    GError ** error)
+{
+  GstElement *request_element = NULL;
+
+  if (!requestable) {
+    g_set_error (error, GES_ERROR, GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION,
+        "No %spads available for effect: %s",
+        (direction == GST_PAD_SRC) ? "src" : "sink", bin_desc);
+
+    return NULL;
+  }
+
+  request_element = requestable->data;
+  if (requestable->next) {
+    GstIterator *it = gst_bin_iterate_sorted (GST_BIN (effect));
+    GValue v;
+
+    while (gst_iterator_next (it, &v) != GST_ITERATOR_DONE) {
+      GstElement *tmpe = g_value_get_object (&v);
+
+      if (g_list_find (requestable, tmpe)) {
+        request_element = tmpe;
+        if (direction == GST_PAD_SRC) {
+          break;
+        }
+      }
+      g_value_reset (&v);
+    }
+    gst_iterator_free (it);
+  }
+
+  return request_pad (request_element, direction);
+}
+
+static gboolean
+ghost_pad (GstElement * effect, const gchar * bin_desc, GstPad * pad,
+    gint n_pad, const gchar * converter_str, GError ** error)
+{
+  gchar *name;
+  GstPad *peer, *ghosted;
+  GstPadLinkReturn lret;
+  GstElement *converter;
+
+  if (!converter_str) {
+    ghosted = pad;
+    goto ghost;
+  }
+
+  converter = gst_parse_bin_from_description_full (converter_str, TRUE, NULL,
+      GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_PLACE_IN_BIN,
+      error);
+
+  if (!converter) {
+    GST_ERROR_OBJECT (effect, "Could not create converter '%s'", converter_str);
+    return FALSE;
+  }
+
+  peer =
+      GST_PAD_IS_SINK (pad) ? converter->srcpads->data : converter->sinkpads->
+      data;
+
+  gst_bin_add (GST_BIN (effect), converter);
+  lret =
+      gst_pad_link (GST_PAD_IS_SINK (pad) ? peer : pad,
+      GST_PAD_IS_SINK (pad) ? pad : peer);
+
+  if (lret != GST_PAD_LINK_OK) {
+    gst_object_unref (converter);
+    g_set_error (error, GES_ERROR, GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION,
+        "Effect %s can not link converter %s with %s", bin_desc, converter_str,
+        gst_pad_link_get_name (lret));
+    return FALSE;
+  }
+
+  ghosted =
+      GST_PAD_IS_SRC (pad) ? converter->srcpads->data : converter->sinkpads->
+      data;
+
+ghost:
+
+  if (GST_PAD_IS_SINK (pad))
+    name = g_strdup_printf ("sink_%d", n_pad);
+  else
+    name = g_strdup_printf ("src");
+
+  gst_element_add_pad (effect, gst_ghost_pad_new (name, ghosted));
+  g_free (name);
+
+  return TRUE;
+}
+
+GstElement *
+ges_effect_from_description (const gchar * bin_desc, GESTrackType type,
+    GError ** error)
+{
+
+  gint n_sink = 0;
+  GstPad *srcpad = NULL;
+  GstCaps *valid_caps = NULL;
+  const gchar *converter_str = NULL;
+  GList *tmp, *sinkpads = NULL, *elems_with_reqsink = NULL,
+      *elems_with_reqsrc = NULL;
+  GstElement *effect =
+      gst_parse_bin_from_description_full (bin_desc, FALSE, NULL,
+      GST_PARSE_FLAG_PLACE_IN_BIN | GST_PARSE_FLAG_FATAL_ERRORS, error);
+
+  if (!effect) {
+    GST_ERROR ("An error occurred while creating: %s",
+        (error && *error) ? (*error)->message : "Unknown error");
+    goto err;
+  }
+
+  if (type == GES_TRACK_TYPE_VIDEO) {
+    valid_caps = gst_caps_from_string ("video/x-raw(ANY)");
+    converter_str = "videoconvert";
+  } else if (type == GES_TRACK_TYPE_AUDIO) {
+    valid_caps = gst_caps_from_string ("audio/x-raw(ANY)");
+    converter_str = "audioconvert ! audioresample ! audioconvert";
+  } else {
+    valid_caps = gst_caps_new_any ();
+  }
+
+  for (tmp = GST_BIN_CHILDREN (effect); tmp; tmp = tmp->next) {
+    if (!find_compatible_pads (effect, bin_desc, tmp->data, valid_caps, &srcpad,
+            &sinkpads, &elems_with_reqsink, &elems_with_reqsrc, error))
+      goto err;
+  }
+
+  if (!sinkpads) {
+    GstPad *sinkpad = get_pad_from_elements_with_request_pad (effect, bin_desc,
+        elems_with_reqsink, GST_PAD_SINK, error);
+    if (!sinkpad)
+      goto err;
+    sinkpads = g_list_append (sinkpads, sinkpad);
+  }
+
+  if (!srcpad) {
+    srcpad = get_pad_from_elements_with_request_pad (effect, bin_desc,
+        elems_with_reqsrc, GST_PAD_SRC, error);
+    if (!srcpad)
+      goto err;
+  }
+
+  for (tmp = sinkpads; tmp; tmp = tmp->next) {
+    if (!ghost_pad (effect, bin_desc, tmp->data, n_sink, converter_str, error))
+      goto err;
+    n_sink++;
+  }
+
+  if (!ghost_pad (effect, bin_desc, srcpad, 0, converter_str, error))
+    goto err;
+
+done:
+  g_list_free (elems_with_reqsink);
+  g_list_free (elems_with_reqsrc);
+  g_list_free_full (sinkpads, gst_object_unref);
+  gst_clear_caps (&valid_caps);
+  gst_clear_object (&srcpad);
+
+  return effect;
+
+err:
+  gst_clear_object (&effect);
+  goto done;
+}
+
 gchar *
-ges_effect_assect_id_get_type_and_bindesc (const char *id,
+ges_effect_asset_id_get_type_and_bindesc (const char *id,
     GESTrackType * track_type, GError ** error)
 {
   GList *tmp;
   GstElement *effect;
   gchar **typebin_desc = NULL;
+  const gchar *user_bindesc;
   gchar *bindesc = NULL;
 
   *track_type = GES_TRACK_TYPE_UNKNOWN;
   typebin_desc = g_strsplit (id, " ", 2);
   if (!g_strcmp0 (typebin_desc[0], "audio")) {
     *track_type = GES_TRACK_TYPE_AUDIO;
-    bindesc = g_strdup (typebin_desc[1]);
+    user_bindesc = typebin_desc[1];
   } else if (!g_strcmp0 (typebin_desc[0], "video")) {
     *track_type = GES_TRACK_TYPE_VIDEO;
-    bindesc = g_strdup (typebin_desc[1]);
+    user_bindesc = typebin_desc[1];
   } else {
-    bindesc = g_strdup (id);
+    *track_type = GES_TRACK_TYPE_UNKNOWN;
+    user_bindesc = id;
   }
 
+  bindesc = g_strdup (user_bindesc);
   g_strfreev (typebin_desc);
 
   effect = gst_parse_bin_from_description (bindesc, TRUE, error);
   if (effect == NULL) {
+    GST_ERROR ("Could not create element from: %s", bindesc);
     g_free (bindesc);
-
-    GST_ERROR ("Could not create element from: %s", id);
-
     return NULL;
   }
 
@@ -169,7 +418,15 @@ ges_effect_assect_id_get_type_and_bindesc (const char *id,
     *track_type = GES_TRACK_TYPE_VIDEO;
     GST_ERROR ("Could not determine track type for %s, defaulting to video",
         id);
+
   }
 
+  if (!(effect = ges_effect_from_description (bindesc, *track_type, error))) {
+    g_free (bindesc);
+
+    return NULL;
+  }
+  gst_object_unref (effect);
+
   return bindesc;
 }
index 17f4655..0c624fe 100644 (file)
@@ -18,8 +18,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
  */
 
-#ifndef _GES_EFFECT_ASSET_H_
-#define _GES_EFFECT_ASSET_H_
+#pragma once
 
 #include <glib-object.h>
 #include "ges-track-element-asset.h"
 G_BEGIN_DECLS
 
 #define GES_TYPE_EFFECT_ASSET             (ges_effect_asset_get_type ())
-#define GES_EFFECT_ASSET(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_EFFECT_ASSET, GESEffectAsset))
-#define GES_EFFECT_ASSET_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_EFFECT_ASSET, GESEffectAssetClass))
-#define GES_IS_EFFECT_ASSET(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_EFFECT_ASSET))
-#define GES_IS_EFFECT_ASSET_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_EFFECT_ASSET))
-#define GES_EFFECT_ASSET_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_EFFECT_ASSET, GESEffectAssetClass))
-
-typedef struct _GESEffectAssetClass GESEffectAssetClass;
-typedef struct _GESEffectAsset GESEffectAsset;
-typedef struct _GESEffectAssetPrivate GESEffectAssetPrivate;
-
+GES_DECLARE_TYPE(EffectAsset, effect_asset, EFFECT_ASSET);
 
 struct _GESEffectAssetClass
 {
@@ -54,10 +44,4 @@ struct _GESEffectAsset
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_effect_asset_get_type (void) G_GNUC_CONST;
-
 G_END_DECLS
-
-#endif /* _GES_EFFECT_ASSET_H_ */
-
index f43f2f4..4b89054 100644 (file)
  *
  * The effect will be applied on the sources that have lower priorities
  * (higher number) between the inpoint and the end of it.
+ *
+ * The asset ID of an effect clip is in the form:
+ *
+ * ```
+ *   "audio ! bin ! description || video ! bin ! description"
+ * ```
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -40,8 +46,11 @@ struct _GESEffectClipPrivate
   gchar *audio_bin_description;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GESEffectClip, ges_effect_clip,
-    GES_TYPE_BASE_EFFECT_CLIP);
+static void ges_extractable_interface_init (GESExtractableInterface * iface);
+G_DEFINE_TYPE_WITH_CODE (GESEffectClip, ges_effect_clip,
+    GES_TYPE_BASE_EFFECT_CLIP, G_ADD_PRIVATE (GESEffectClip)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
+        ges_extractable_interface_init));
 
 enum
 {
@@ -54,6 +63,79 @@ static void ges_effect_clip_finalize (GObject * object);
 static GESTrackElement *_create_track_element (GESClip * self,
     GESTrackType type);
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
+static GParameter *
+extractable_get_parameters_from_id (const gchar * id, guint * n_params)
+{
+  gchar *bin_desc;
+  GESTrackType ttype;
+  GParameter *params = g_new0 (GParameter, 2);
+  gchar **effects_desc = g_strsplit (id, "||", -1);
+  gint i;
+
+  *n_params = 0;
+
+  if (g_strv_length (effects_desc) > 2)
+    GST_ERROR ("EffectClip id %s contains too many effect descriptions", id);
+
+  for (i = 0; effects_desc[i] && i < 2; i++) {
+    bin_desc =
+        ges_effect_asset_id_get_type_and_bindesc (effects_desc[i], &ttype,
+        NULL);
+
+    if (ttype == GES_TRACK_TYPE_AUDIO) {
+      *n_params = *n_params + 1;
+      params[*n_params - 1].name = "audio-bin-description";
+    } else if (ttype == GES_TRACK_TYPE_VIDEO) {
+      *n_params = *n_params + 1;
+      params[i].name = "video-bin-description";
+    } else {
+      g_free (bin_desc);
+      GST_ERROR ("Could not find effect type for %s", effects_desc[i]);
+      continue;
+    }
+
+    g_value_init (&params[*n_params - 1].value, G_TYPE_STRING);
+    g_value_set_string (&params[*n_params - 1].value, bin_desc);
+    g_free (bin_desc);
+  }
+
+  g_strfreev (effects_desc);
+  return params;
+}
+
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
+
+static gchar *
+extractable_check_id (GType type, const gchar * id, GError ** error)
+{
+  return g_strdup (id);
+}
+
+static gchar *
+extractable_get_id (GESExtractable * self)
+{
+  GString *id = g_string_new (NULL);
+  GESEffectClipPrivate *priv = GES_EFFECT_CLIP (self)->priv;
+
+  if (priv->audio_bin_description)
+    g_string_append_printf (id, "audio %s ||", priv->audio_bin_description);
+  if (priv->video_bin_description)
+    g_string_append_printf (id, "video %s", priv->video_bin_description);
+
+  return g_string_free (id, FALSE);
+}
+
+static void
+ges_extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->asset_type = GES_TYPE_ASSET;
+  iface->check_id = extractable_check_id;
+  iface->get_parameters_from_id = extractable_get_parameters_from_id;
+  iface->get_id = extractable_get_id;
+}
+
+
 static void
 ges_effect_clip_finalize (GObject * object)
 {
@@ -160,11 +242,8 @@ _create_track_element (GESClip * self, GESTrackType type)
     bin_description = effect->priv->audio_bin_description;
   }
 
-  if (bin_description) {
-    /* FIXME Work with a GESAsset here! */
-    return g_object_new (GES_TYPE_EFFECT, "bin-description",
-        bin_description, "track-type", type, NULL);
-  }
+  if (bin_description)
+    return GES_TRACK_ELEMENT (ges_effect_new (bin_description));
 
   GST_WARNING ("Effect doesn't handle this track type");
   return NULL;
@@ -184,8 +263,19 @@ GESEffectClip *
 ges_effect_clip_new (const gchar * video_bin_description,
     const gchar * audio_bin_description)
 {
-  /*  FIXME Handle GESAsset! */
-  return g_object_new (GES_TYPE_EFFECT_CLIP,
-      "video-bin-description", video_bin_description,
-      "audio-bin-description", audio_bin_description, NULL);
+  GESAsset *asset;
+  GESEffectClip *res;
+  GString *id = g_string_new (NULL);
+
+  if (audio_bin_description)
+    g_string_append_printf (id, "audio %s ||", audio_bin_description);
+  if (video_bin_description)
+    g_string_append_printf (id, "video %s", video_bin_description);
+
+  asset = ges_asset_request (GES_TYPE_EFFECT_CLIP, id->str, NULL);
+  res = GES_EFFECT_CLIP (ges_asset_extract (asset, NULL));
+  g_string_free (id, TRUE);
+  gst_object_unref (asset);
+
+  return res;
 }
index 183a96b..1a2dc70 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_EFFECT_CLIP
-#define _GES_EFFECT_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_EFFECT_CLIP ges_effect_clip_get_type()
-
-#define GES_EFFECT_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_EFFECT_CLIP, GESEffectClip))
-
-#define GES_EFFECT_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_EFFECT_CLIP, GESEffectClipClass))
-
-#define GES_IS_EFFECT_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_EFFECT_CLIP))
-
-#define GES_IS_EFFECT_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_EFFECT_CLIP))
-
-#define GES_EFFECT_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_EFFECT_CLIP, GESEffectClipClass))
-
-typedef struct _GESEffectClipPrivate GESEffectClipPrivate;
+GES_DECLARE_TYPE(EffectClip, effect_clip, EFFECT_CLIP);
 
 /**
  * GESEffectClip:
@@ -70,12 +53,8 @@ struct _GESEffectClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_effect_clip_get_type (void);
-
 GES_API GESEffectClip *
 ges_effect_clip_new (const gchar * video_bin_description,
                                      const gchar * audio_bin_description);
 
-G_END_DECLS
-#endif /* _GES_EFFECT_CLIP */
+G_END_DECLS
\ No newline at end of file
index cc900b0..fb6bf78 100644 (file)
 /**
  * SECTION:geseffect
  * @title: GESEffect
- * @short_description: adds an effect build from a parse-launch style
- * bin description to a stream in a GESSourceClip or a GESLayer
+ * @short_description: adds an effect build from a parse-launch style bin
+ * description to a stream in a GESSourceClip or a GESLayer
+ *
+ * Currently we only support effects with N sinkpads and one single srcpad.
+ * Apart from `gesaudiomixer` and `gescompositor` which can be used as effects
+ * and where sinkpads will be requested as needed based on the timeline topology
+ * GES will always request at most one sinkpad per effect (when required).
+ *
+ * > Note: GES always adds converters (`audioconvert ! audioresample !
+ * > audioconvert` for audio effects and `videoconvert` for video effects) to
+ * > make it simpler for end users.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -63,7 +72,7 @@ extractable_check_id (GType type, const gchar * id, GError ** error)
   gchar *bin_desc, *real_id;
   GESTrackType ttype;
 
-  bin_desc = ges_effect_assect_id_get_type_and_bindesc (id, &ttype, error);
+  bin_desc = ges_effect_asset_id_get_type_and_bindesc (id, &ttype, error);
 
   if (bin_desc == NULL)
     return NULL;
@@ -80,6 +89,7 @@ extractable_check_id (GType type, const gchar * id, GError ** error)
   return real_id;
 }
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
 static GParameter *
 extractable_get_parameters_from_id (const gchar * id, guint * n_params)
 {
@@ -87,7 +97,7 @@ extractable_get_parameters_from_id (const gchar * id, guint * n_params)
   gchar *bin_desc;
   GESTrackType ttype;
 
-  bin_desc = ges_effect_assect_id_get_type_and_bindesc (id, &ttype, NULL);
+  bin_desc = ges_effect_asset_id_get_type_and_bindesc (id, &ttype, NULL);
 
   params[0].name = "bin-description";
   g_value_init (&params[0].value, G_TYPE_STRING);
@@ -103,6 +113,8 @@ extractable_get_parameters_from_id (const gchar * id, guint * n_params)
   return params;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
+
 static gchar *
 extractable_get_id (GESExtractable * self)
 {
@@ -125,33 +137,6 @@ property_name_compare (gconstpointer s1, gconstpointer s2)
 }
 
 static void
-_set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child,
-    GParamSpec * pspec, GValue * value)
-{
-  GESEffectClass *klass = GES_EFFECT_GET_CLASS (self);
-  gchar *full_property_name;
-
-  GES_TIMELINE_ELEMENT_CLASS
-      (ges_effect_parent_class)->set_child_property (self, child, pspec, value);
-
-  full_property_name = g_strdup_printf ("%s::%s", G_OBJECT_TYPE_NAME (child),
-      pspec->name);
-
-  if (g_list_find_custom (klass->rate_properties, full_property_name,
-          property_name_compare)) {
-    GstElement *nleobject =
-        ges_track_element_get_nleobject (GES_TRACK_ELEMENT (self));
-    gdouble media_duration_factor =
-        ges_timeline_element_get_media_duration_factor (self);
-
-    g_object_set (nleobject, "media-duration-factor", media_duration_factor,
-        NULL);
-  }
-
-  g_free (full_property_name);
-}
-
-static void
 ges_effect_get_property (GObject * object,
     guint property_id, GValue * value, GParamSpec * pspec)
 {
@@ -186,11 +171,9 @@ ges_effect_class_init (GESEffectClass * klass)
 {
   GObjectClass *object_class;
   GESTrackElementClass *obj_bg_class;
-  GESTimelineElementClass *element_class;
 
   object_class = G_OBJECT_CLASS (klass);
   obj_bg_class = GES_TRACK_ELEMENT_CLASS (klass);
-  element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
 
   object_class->get_property = ges_effect_get_property;
   object_class->set_property = ges_effect_set_property;
@@ -198,7 +181,6 @@ ges_effect_class_init (GESEffectClass * klass)
   object_class->finalize = ges_effect_finalize;
 
   obj_bg_class->create_element = ges_effect_create_element;
-  element_class->set_child_property = _set_child_property;
 
   klass->rate_properties = NULL;
   ges_effect_class_register_rate_property (klass, "scaletempo", "rate");
@@ -244,45 +226,80 @@ ges_effect_finalize (GObject * object)
   G_OBJECT_CLASS (ges_effect_parent_class)->finalize (object);
 }
 
-static void
-ghost_compatible_pads (GstElement * bin, GstElement * child,
-    GstCaps * valid_caps, gint * n_src, gint * n_sink)
+static gdouble
+_get_rate_factor (GESBaseEffect * effect, GHashTable * rate_values)
 {
-  GList *tmp;
-
-  for (tmp = child->pads; tmp; tmp = tmp->next) {
-    GstCaps *caps;
-    GstPad *pad = tmp->data;
-
-    if (GST_PAD_PEER (pad))
-      continue;
-
-    caps = gst_pad_query_caps (pad, NULL);
+  GHashTableIter iter;
+  gpointer key, val;
+  gdouble factor = 1.0;
+
+  g_hash_table_iter_init (&iter, rate_values);
+  while (g_hash_table_iter_next (&iter, &key, &val)) {
+    GValue *value = val;
+    gchar *prop_name = key;
+    gdouble rate = 1.0;
+
+    switch (G_VALUE_TYPE (value)) {
+      case G_TYPE_DOUBLE:
+        rate = g_value_get_double (value);
+        break;
+      case G_TYPE_FLOAT:
+        rate = g_value_get_float (value);
+        break;
+      default:
+        GST_ERROR_OBJECT (effect, "Rate property %s has neither a gdouble "
+            "nor gfloat value", prop_name);
+        break;
+    }
+    factor *= rate;
+  }
 
-    if (gst_caps_can_intersect (caps, valid_caps)) {
-      gchar *name =
-          g_strdup_printf ("%s_%d", GST_PAD_IS_SINK (pad) ? "sink" : "src",
-          GST_PAD_IS_SINK (pad) ? *n_sink++ : *n_src++);
+  return factor;
+}
 
-      GST_DEBUG_OBJECT (bin, "Ghosting pad: %" GST_PTR_FORMAT, pad);
-      gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new (name, pad));
-      g_free (name);
-    } else {
-      GST_DEBUG_OBJECT (pad, "Can't ghost pad %" GST_PTR_FORMAT, caps);
-    }
+static GstClockTime
+_rate_source_to_sink (GESBaseEffect * effect, GstClockTime time,
+    GHashTable * rate_values, gpointer user_data)
+{
+  /* multiply by rate factor
+   * E.g. rate=2.0, then the time 30 at the source would become
+   * 60 at the sink because we are using up twice as much data in a given
+   * time */
+  gdouble rate_factor = _get_rate_factor (effect, rate_values);
+
+  if (time == 0)
+    return 0;
+  if (rate_factor == 0.0) {
+    GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0");
+    return 0;
+  }
+  return (GstClockTime) (time * rate_factor);
+}
 
-    gst_caps_unref (caps);
+static GstClockTime
+_rate_sink_to_source (GESBaseEffect * effect, GstClockTime time,
+    GHashTable * rate_values, gpointer user_data)
+{
+  /* divide by rate factor */
+  gdouble rate_factor = _get_rate_factor (effect, rate_values);
+
+  if (time == 0)
+    return 0;
+  if (rate_factor == 0.0) {
+    GST_ERROR_OBJECT (effect, "The rate effect has a rate of 0");
+    return GST_CLOCK_TIME_NONE;
   }
+  return (GstClockTime) (time / rate_factor);
 }
 
 static GstElement *
 ges_effect_create_element (GESTrackElement * object)
 {
   GList *tmp;
+  GESEffectClass *class;
   GstElement *effect;
-  gchar *bin_desc;
-  GstCaps *valid_caps;
-  gint n_src = 0, n_sink = 0;
+  gboolean is_rate_effect = FALSE;
+  GESBaseEffect *base_effect = GES_BASE_EFFECT (object);
 
   GError *error = NULL;
   GESEffect *self = GES_EFFECT (object);
@@ -291,44 +308,39 @@ ges_effect_create_element (GESTrackElement * object)
 
   GESTrackType type = ges_track_element_get_track_type (object);
 
-  if (type == GES_TRACK_TYPE_VIDEO) {
-    bin_desc = g_strconcat ("videoconvert name=pre_video_convert ! ",
-        self->priv->bin_description, " ! videoconvert name=post_video_convert",
-        NULL);
-    valid_caps = gst_caps_from_string ("video/x-raw(ANY)");
-  } else if (type == GES_TRACK_TYPE_AUDIO) {
-    bin_desc =
-        g_strconcat ("audioconvert ! audioresample !",
-        self->priv->bin_description, NULL);
-    valid_caps = gst_caps_from_string ("audio/x-raw(ANY)");
-  } else {
-    g_assert_not_reached ();
-  }
+  if (!g_strcmp0 (self->priv->bin_description, "gesaudiomixer") ||
+      !g_strcmp0 (self->priv->bin_description, "gescompositor"))
+    return gst_element_factory_make (self->priv->bin_description, NULL);
 
-  effect = gst_parse_bin_from_description (bin_desc, FALSE, &error);
-  g_free (bin_desc);
+  effect =
+      ges_effect_from_description (self->priv->bin_description, type, &error);
   if (error != NULL) {
-    GST_ERROR ("An error occured while creating the GstElement: %s",
+    GST_ERROR ("An error occurred while creating the GstElement: %s",
         error->message);
     g_error_free (error);
     goto fail;
   }
 
-  for (tmp = GST_BIN_CHILDREN (effect); tmp; tmp = tmp->next) {
-    ghost_compatible_pads (effect, tmp->data, valid_caps, &n_src, &n_sink);
+  ges_track_element_add_children_props (object, effect, NULL,
+      blacklisted_factories, NULL);
 
-    if (n_src > 1) {
-      GST_ERROR ("More than 1 source pad in the effect, that is not possible");
+  class = GES_EFFECT_CLASS (g_type_class_peek (GES_TYPE_EFFECT));
 
-      goto fail;
+  for (tmp = class->rate_properties; tmp; tmp = tmp->next) {
+    gchar *prop = tmp->data;
+    if (ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (object), prop,
+            NULL, NULL)) {
+      if (!ges_base_effect_register_time_property (base_effect, prop))
+        GST_ERROR_OBJECT (object, "Failed to register rate property %s", prop);
+      is_rate_effect = TRUE;
     }
   }
-
-  ges_track_element_add_children_props (object, effect, NULL,
-      blacklisted_factories, NULL);
+  if (is_rate_effect
+      && !ges_base_effect_set_time_translation_funcs (base_effect,
+          _rate_source_to_sink, _rate_sink_to_source, NULL, NULL))
+    GST_ERROR_OBJECT (object, "Failed to set rate translation functions");
 
 done:
-  gst_clear_caps (&valid_caps);
 
   return effect;
 
@@ -371,22 +383,38 @@ ges_effect_new (const gchar * bin_description)
 /**
  * ges_effect_class_register_rate_property:
  * @klass: Instance of the GESEffectClass
- * @element_name: Name of the GstElement that changes the rate
- * @property_name: Name of the property that changes the rate
+ * @element_name: The #GstElementFactory name of the element that changes
+ * the rate
+ * @property_name: The name of the property that changes the rate
+ *
+ * Register an element that can change the rate at which media is playing.
+ * The property type must be float or double, and must be a factor of the
+ * rate, i.e. a value of 2.0 must mean that the media plays twice as fast.
+ * Several properties may be registered for a single element type,
+ * provided they all contribute to the rate as independent factors. For
+ * example, this is true for the "GstPitch::rate" and "GstPitch::tempo"
+ * properties. These are already registered by default in GES, along with
+ * #videorate:rate for #videorate and #scaletempo:rate for #scaletempo.
+ *
+ * If such a rate property becomes a child property of a #GESEffect upon
+ * its creation (the element is part of its #GESEffect:bin-description),
+ * it will be automatically registered as a time property (see
+ * ges_base_effect_register_time_property()) and will have its time
+ * translation functions set (see
+ * ges_base_effect_set_time_translation_funcs()) to use the overall rate
+ * of the rate properties. Note that if an effect contains a rate
+ * property as well as a non-rate time property, you should ensure to set
+ * the time translation functions to some other methods using
+ * ges_base_effect_set_time_translation_funcs().
  *
- * Register an element that can change the rate at which media is playing. The
- * property type must be float or double, and must be a factor of the rate,
- * i.e. a value of 2.0 must mean that the media plays twice as fast. For
- * example, this is true for the properties 'rate' and 'tempo' of the element
- * 'pitch', which is already registered by default. By registering the element,
- * timeline duration can be correctly converted into media duration, allowing
- * the right segment seeks to be sent to the sources.
+ * Note, you can obtain a reference to the GESEffectClass using
  *
- * A reference to the GESEffectClass can be obtained as follows:
+ * ```
  *   GES_EFFECT_CLASS (g_type_class_ref (GES_TYPE_EFFECT));
+ * ```
  *
- * Returns: whether the rate property was succesfully registered. When this
- * method returns false, a warning is emitted with more information.
+ * Returns: %TRUE if the rate property was successfully registered. When
+ * this method returns %FALSE, a warning is emitted with more information.
  */
 gboolean
 ges_effect_class_register_rate_property (GESEffectClass * klass,
index 9453c24..e8f476a 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_EFFECT
-#define _GES_EFFECT
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 
 G_BEGIN_DECLS
 #define GES_TYPE_EFFECT ges_effect_get_type()
-
-#define GES_EFFECT(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_EFFECT, GESEffect))
-
-#define GES_EFFECT_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_EFFECT, GESEffectClass))
-
-#define GES_IS_EFFECT(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_EFFECT))
-
-#define GES_IS_EFFECT_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_EFFECT))
-
-#define GES_EFFECT_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_EFFECT, GESEffectClass))
-
-
-typedef struct _GESEffectPrivate   GESEffectPrivate;
+GES_DECLARE_TYPE(Effect, effect, EFFECT);
 
 /**
  * GESEffect:
@@ -76,14 +58,10 @@ struct _GESEffectClass
 
 };
 
-GES_API
-GType ges_effect_get_type (void);
-
 GES_API GESEffect*
 ges_effect_new (const gchar * bin_description);
 
 GES_API gboolean
 ges_effect_class_register_rate_property (GESEffectClass *klass, const gchar *element_name, const gchar *property_name);
 
-G_END_DECLS
-#endif /* _GES_EFFECT */
+G_END_DECLS
\ No newline at end of file
index 299e2b5..83b5cd7 100644 (file)
@@ -111,24 +111,58 @@ register_ges_edit_mode (GType * id)
     {C_ENUM (GES_EDIT_MODE_NORMAL), "GES_EDIT_MODE_NORMAL",
         "edit_normal"},
 
+    {C_ENUM (GES_EDIT_MODE_NORMAL), "GES_EDIT_MODE_NORMAL",
+        "normal"},
+
     {C_ENUM (GES_EDIT_MODE_RIPPLE), "GES_EDIT_MODE_RIPPLE",
         "edit_ripple"},
 
+    {C_ENUM (GES_EDIT_MODE_RIPPLE), "GES_EDIT_MODE_RIPPLE",
+        "ripple"},
+
     {C_ENUM (GES_EDIT_MODE_ROLL), "GES_EDIT_MODE_ROLL",
         "edit_roll"},
 
+    {C_ENUM (GES_EDIT_MODE_ROLL), "GES_EDIT_MODE_ROLL",
+        "roll"},
+
     {C_ENUM (GES_EDIT_MODE_TRIM), "GES_EDIT_MODE_TRIM",
         "edit_trim"},
 
+    {C_ENUM (GES_EDIT_MODE_TRIM), "GES_EDIT_MODE_TRIM",
+        "trim"},
+
     {C_ENUM (GES_EDIT_MODE_SLIDE), "GES_EDIT_MODE_SLIDE",
         "edit_slide"},
 
+    {C_ENUM (GES_EDIT_MODE_SLIDE), "GES_EDIT_MODE_SLIDE",
+        "slide"},
+
     {0, NULL, NULL}
   };
 
   *id = g_enum_register_static ("GESEditMode", edit_mode);
 }
 
+const gchar *
+ges_edit_mode_name (GESEditMode mode)
+{
+  switch (mode) {
+    case GES_EDIT_MODE_NORMAL:
+      return "normal";
+    case GES_EDIT_MODE_RIPPLE:
+      return "ripple";
+    case GES_EDIT_MODE_ROLL:
+      return "roll";
+    case GES_EDIT_MODE_TRIM:
+      return "trim";
+    case GES_EDIT_MODE_SLIDE:
+      return "slide";
+    default:
+      return "unknown";
+  }
+}
+
 GType
 ges_edit_mode_get_type (void)
 {
@@ -144,14 +178,25 @@ register_ges_edge (GType * id)
 {
   static const GEnumValue edges[] = {
     {C_ENUM (GES_EDGE_START), "GES_EDGE_START", "edge_start"},
+    {C_ENUM (GES_EDGE_START), "GES_EDGE_START", "start"},
     {C_ENUM (GES_EDGE_END), "GES_EDGE_END", "edge_end"},
+    {C_ENUM (GES_EDGE_END), "GES_EDGE_END", "end"},
     {C_ENUM (GES_EDGE_NONE), "GES_EDGE_NONE", "edge_none"},
+    {C_ENUM (GES_EDGE_NONE), "GES_EDGE_NONE", "none"},
     {0, NULL, NULL}
   };
 
   *id = g_enum_register_static ("GESEdge", edges);
 }
 
+/**
+ * ges_edge_name:
+ * @edge: The #GESEdge to get the name of
+ *
+ * Returns: A human friendly name for @edge
+ *
+ * Since: 1.16
+ */
 const gchar *
 ges_edge_name (GESEdge edge)
 {
@@ -560,3 +605,26 @@ ges_meta_flag_get_type (void)
   g_once (&once, (GThreadFunc) register_ges_meta_flag, &id);
   return id;
 }
+
+static void
+register_ges_marker_flags (GType * id)
+{
+  static const GFlagsValue values[] = {
+    {C_ENUM (GES_MARKER_FLAG_NONE), "GES_MARKER_FLAG_NONE", "none"},
+    {C_ENUM (GES_MARKER_FLAG_SNAPPABLE), "GES_MARKER_FLAG_SNAPPABLE",
+        "snappable"},
+    {0, NULL, NULL}
+  };
+
+  *id = g_flags_register_static ("GESMarkerFlags", values);
+}
+
+GType
+ges_marker_flags_get_type (void)
+{
+  static GType id;
+  static GOnce once = G_ONCE_INIT;
+
+  g_once (&once, (GThreadFunc) register_ges_marker_flags, &id);
+  return id;
+}
index 34d2aa9..ff7a013 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GES_ENUMS_H__
-#define __GES_ENUMS_H__
+#pragma once
 
 #include <gst/gst.h>
 #include <ges/ges-prelude.h>
@@ -334,13 +333,22 @@ GType ges_video_test_pattern_get_type (void);
 
 /**
  * GESPipelineFlags:
- * @GES_PIPELINE_MODE_PREVIEW_AUDIO: output audio to the soundcard
- * @GES_PIPELINE_MODE_PREVIEW_VIDEO: output video to the screen
- * @GES_PIPELINE_MODE_PREVIEW: output audio/video to soundcard/screen (default)
- * @GES_PIPELINE_MODE_RENDER: render timeline (forces decoding)
- * @GES_PIPELINE_MODE_SMART_RENDER: render timeline (tries to avoid decoding/reencoding)
+ * @GES_PIPELINE_MODE_PREVIEW_AUDIO: Output the #GESPipeline:timeline's
+ * audio to the soundcard
+ * @GES_PIPELINE_MODE_PREVIEW_VIDEO: Output the #GESPipeline:timeline's
+ * video to the screen
+ * @GES_PIPELINE_MODE_PREVIEW: Output both the #GESPipeline:timeline's
+ * audio and video to the soundcard and screen (default)
+ * @GES_PIPELINE_MODE_RENDER: Render the #GESPipeline:timeline with
+ * forced decoding (the underlying #encodebin has its
+ * #encodebin:avoid-reencoding property set to %FALSE)
+ * @GES_PIPELINE_MODE_SMART_RENDER: Render the #GESPipeline:timeline,
+ * avoiding decoding/reencoding (the underlying #encodebin has its
+ * #encodebin:avoid-reencoding property set to %TRUE).
+ * > NOTE: Smart rendering can not work in tracks where #GESTrack:mixing
+ * > is enabled.
  *
- * The various modes the #GESPipeline can be configured to.
+ * The various modes a #GESPipeline can be configured to.
  */
 typedef enum {
   GES_PIPELINE_MODE_PREVIEW_AUDIO      = 1 << 0,
@@ -358,31 +366,155 @@ GType ges_pipeline_flags_get_type (void);
 
 /**
  * GESEditMode:
- * @GES_EDIT_MODE_NORMAL: The object is edited the normal way (default).
- * @GES_EDIT_MODE_RIPPLE: The objects are edited in ripple mode.
- *  The Ripple mode allows you to modify the beginning/end of a clip
- *  and move the neighbours accordingly. This will change the overall
- *  timeline duration. In the case of ripple end, the duration of the
- *  clip being rippled can't be superior to its max_duration - inpoint
- *  otherwise the action won't be executed.
- * @GES_EDIT_MODE_ROLL: The object is edited in roll mode.
- *  The Roll mode allows you to modify the position of an editing point
- *  between two clips without modifying the inpoint of the first clip
- *  nor the out-point of the second clip. This will not change the
- *  overall timeline duration.
- * @GES_EDIT_MODE_TRIM: The object is edited in trim mode.
- *  The Trim mode allows you to modify the in-point/duration of a clip
- *  without modifying its position in the timeline.
- * @GES_EDIT_MODE_SLIDE: The object is edited in slide mode.
- *  The Slide mode allows you to modify the position of a clip in a
- *  timeline without modifying its duration or its in-point, but will
- *  modify the duration of the previous clip and in-point of the
- *  following clip so does not modify the overall timeline duration.
- *  (not implemented yet)
+ * @GES_EDIT_MODE_NORMAL: The element is edited the normal way (default).
+ *  If acting on the element as a whole (#GES_EDGE_NONE), this will MOVE
+ *  the element by MOVING its toplevel. When acting on the start of the
+ *  element (#GES_EDGE_START), this will only MOVE the element, but not
+ *  its toplevel parent. This can allow you to move a #GESClip or
+ *  #GESGroup to a new start time or layer within its container group,
+ *  without effecting other members of the group. When acting on the end
+ *  of the element (#GES_EDGE_END), this will END-TRIM the element,
+ *  leaving its toplevel unchanged.
+ * @GES_EDIT_MODE_RIPPLE: The element is edited in ripple mode: moving
+ *  itself as well as later elements, keeping their relative times. This
+ *  edits the element the same as #GES_EDIT_MODE_NORMAL. In addition, if
+ *  acting on the element as a whole, or the start of the element, any
+ *  toplevel element in the same timeline (including different layers)
+ *  whose start time is later than the *current* start time of the MOVED
+ *  element will also be MOVED by the same shift as the edited element.
+ *  If acting on the end of the element, any toplevel element whose start
+ *  time is later than the *current* end time of the edited element will
+ *  also be MOVED by the same shift as the change in the end of the
+ *  edited element. These additional elements will also be shifted by
+ *  the same shift in layers as the edited element.
+ * @GES_EDIT_MODE_ROLL: The element is edited in roll mode: swapping its
+ *  content for its neighbour's, or vis versa, in the timeline output.
+ *  This edits the element the same as #GES_EDIT_MODE_TRIM. In addition,
+ *  any neighbours are also TRIMMED at their opposite edge to the same
+ *  timeline position. When acting on the start of the element, a
+ *  neighbour is any earlier element in the timeline whose end time
+ *  matches the *current* start time of the edited element. When acting on
+ *  the end of the element, a neighbour is any later element in the
+ *  timeline whose start time matches the *current* start time of the
+ *  edited element. In addition, a neighbour have a #GESSource at its
+ *  end/start edge that shares a track with a #GESSource at the start/end
+ *  edge of the edited element. Basically, a neighbour is an element that
+ *  can be extended, or cut, to have its content replace, or be replaced
+ *  by, the content of the edited element. Acting on the element as a
+ *  whole (#GES_EDGE_NONE) is not defined. The element can not shift
+ *  layers under this mode.
+ * @GES_EDIT_MODE_TRIM: The element is edited in trim mode. When acting
+ *  on the start of the element, this will START-TRIM it. When acting on
+ *  the end of the element, this will END-TRIM it. Acting on the element
+ *  as a whole (#GES_EDGE_NONE) is not defined.
+ * @GES_EDIT_MODE_SLIDE: The element is edited in slide mode (not yet
+ *  implemented): moving the element replacing or consuming content on
+ *  each end. When acting on the element as a whole, this will MOVE the
+ *  element, and TRIM any neighbours on either side. A neighbour is
+ *  defined in the same way as in #GES_EDIT_MODE_ROLL, but they may be on
+ *  either side of the edited elements. Elements at the end with be
+ *  START-TRIMMED to the new end position of the edited element. Elements
+ *  at the start will be END-TRIMMED to the new start position of the
+ *  edited element. Acting on the start or end of the element
+ *  (#GES_EDGE_START and #GES_EDGE_END) is not defined. The element can
+ *  not shift layers under this mode.
+ *
+ * When a single timeline element is edited within its timeline at some
+ * position, using ges_timeline_element_edit(), depending on the edit
+ * mode, its #GESTimelineElement:start, #GESTimelineElement:duration or
+ * #GESTimelineElement:in-point will be adjusted accordingly. In addition,
+ * any clips may change #GESClip:layer.
+ *
+ * Each edit can be broken down into a combination of three basic edits:
+ *
+ * + MOVE: This moves the start of the element to the edit position.
+ * + START-TRIM: This cuts or grows the start of the element, whilst
+ *   maintaining the time at which its internal content appears in the
+ *   timeline data output. If the element is made shorter, the data that
+ *   appeared at the edit position will still appear in the timeline at
+ *   the same time. If the element is made longer, the data that appeared
+ *   at the previous start of the element will still appear in the
+ *   timeline at the same time.
+ * + END-TRIM: Similar to START-TRIM, but the end of the element is cut or
+ *   grown.
+ *
+ * In particular, when editing a #GESClip:
+ *
+ * + MOVE: This will set the #GESTimelineElement:start of the clip to the
+ *   edit position.
+ * + START-TRIM: This will set the #GESTimelineElement:start of the clip
+ *   to the edit position. To keep the end time the same, the
+ *   #GESTimelineElement:duration of the clip will be adjusted in the
+ *   opposite direction. In addition, the #GESTimelineElement:in-point of
+ *   the clip will be shifted such that the content that appeared at the
+ *   new or previous start time, whichever is latest, still appears at the
+ *   same timeline time. For example, if a frame appeared at the start of
+ *   the clip, and the start of the clip is reduced, the in-point of the
+ *   clip will also reduce such that the frame will appear later within
+ *   the clip, but at the same timeline position.
+ * + END-TRIM: This will set the #GESTimelineElement:duration of the clip
+ *   such that its end time will match the edit position.
+ *
+ * When editing a #GESGroup:
+ *
+ * + MOVE: This will set the #GESGroup:start of the clip to the edit
+ *   position by shifting all of its children by the same amount. So each
+ *   child will maintain their relative positions.
+ * + START-TRIM: If the group is made shorter, this will START-TRIM any
+ *   clips under the group that start after the edit position to the same
+ *   edit position. If the group is made longer, this will START-TRIM any
+ *   clip under the group whose start matches the start of the group to
+ *   the same edit position.
+ * + END-TRIM: If the group is made shorter, this will END-TRIM any clips
+ *   under the group that end after the edit position to the same edit
+ *   position. If the group is made longer, this will END-TRIM any clip
+ *   under the group whose end matches the end of the group to the same
+ *   edit position.
+ *
+ * When editing a #GESTrackElement, if it has a #GESClip parent, this
+ * will be edited instead. Otherwise it is edited in the same way as a
+ * #GESClip.
+ *
+ * The layer priority of a #GESGroup is the lowest layer priority of any
+ * #GESClip underneath it. When a group is edited to a new layer
+ * priority, it will shift all clips underneath it by the same amount,
+ * such that their relative layers stay the same.
+ *
+ * If the #GESTimeline has a #GESTimeline:snapping-distance, then snapping
+ * may occur for some of the edges of the **main** edited element:
+ *
+ * + MOVE: The start or end edge of *any* #GESSource under the element may
+ *   be snapped.
+ * + START-TRIM: The start edge of a #GESSource whose start edge touches
+ *   the start edge of the element may snap.
+ * + END-TRIM: The end edge of a #GESSource whose end edge touches the end
+ *   edge of the element may snap.
+ *
+ * These edges may snap with either the start or end edge of *any* other
+ * #GESSource in the timeline that is not also being moved by the element,
+ * including those in different layers, if they are within the
+ * #GESTimeline:snapping-distance. During an edit, only up to one snap can
+ * occur. This will shift the edit position such that the snapped edges
+ * will touch once the edit has completed.
+ *
+ * Note that snapping can cause an edit to fail where it would have
+ * otherwise succeeded because it may push the edit position such that the
+ * edit would result in an unsupported timeline configuration. Similarly,
+ * snapping can cause an edit to succeed where it would have otherwise
+ * failed.
+ *
+ * For example, in #GES_EDIT_MODE_RIPPLE acting on #GES_EDGE_NONE, the
+ * main element is the MOVED toplevel of the edited element. Any source
+ * under the main MOVED toplevel may have its start or end edge snapped.
+ * Note, these sources cannot snap with each other. The edit may also
+ * push other elements, but any sources under these elements cannot snap,
+ * nor can they be snapped with. If a snap does occur, the MOVE of the
+ * toplevel *and* all other elements pushed by the ripple will be shifted
+ * by the same amount such that the snapped edges will touch.
  *
  * You can also find more explanation about the behaviour of those modes at:
- * <ulink url="http://pitivi.org/manual/trimming.html"> trim, ripple and roll</ulink>
- * and <ulink url="http://pitivi.org/manual/usingclips.html">clip management</ulink>.
+ * [trim, ripple and roll](http://pitivi.org/manual/trimming.html)
+ * and [clip management](http://pitivi.org/manual/usingclips.html).
  */
 typedef enum {
     GES_EDIT_MODE_NORMAL,
@@ -392,6 +524,18 @@ typedef enum {
     GES_EDIT_MODE_SLIDE
 } GESEditMode;
 
+/**
+ * ges_edit_mode_name:
+ * @mode: a #GESEditMode
+ *
+ * Return a string representation of @mode.
+ *
+ * Returns: (transfer none): a string representation of @mode.
+ * Since: 1.18
+ */
+GES_API
+const gchar * ges_edit_mode_name (GESEditMode mode);
+
 #define GES_TYPE_EDIT_MODE ges_edit_mode_get_type()
 
 GES_API
@@ -401,7 +545,7 @@ GType ges_edit_mode_get_type (void);
  * GESEdge:
  * @GES_EDGE_START: Represents the start of an object.
  * @GES_EDGE_END: Represents the end of an object.
- * @GES_EDGE_NONE: Represent the fact we are not workin with any edge of an
+ * @GES_EDGE_NONE: Represent the fact we are not working with any edge of an
  *   object.
  *
  * The edges of an object contain in a #GESTimeline or #GESTrack
@@ -420,9 +564,24 @@ const gchar * ges_edge_name (GESEdge edge);
 GES_API
 GType ges_edge_get_type (void);
 
+#define GES_TYPE_MARKER_FLAGS (ges_marker_flags_get_type ())
+
+GES_API
+GType ges_marker_flags_get_type (void);
+
+/**
+ * GESMarkerFlags:
+ * @GES_MARKER_FLAG_NONE: Marker does not serve any special purpose.
+ * @GES_MARKER_FLAG_SNAPPABLE: Marker can be a snapping target.
+ *
+ * Since: 1.20
+ */
+typedef enum {
+  GES_MARKER_FLAG_NONE = 0,
+  GES_MARKER_FLAG_SNAPPABLE = 1 << 0,
+} GESMarkerFlags;
+
 
 GES_API
 const gchar * ges_track_type_name (GESTrackType type);
 G_END_DECLS
-
-#endif /* __GES_ENUMS_H__ */
index 9d7d3f1..6ce50dd 100644 (file)
 /**
  * SECTION: gesextractable
  * @title: GESExtractable Interface
- * @short_description: An interface for objects which can be extracted from a GESAsset
+ * @short_description: An interface for objects which can be extracted
+ * from a #GESAsset
  *
- * FIXME: Long description needed
+ * A #GObject that implements the #GESExtractable interface can be
+ * extracted from a #GESAsset using ges_asset_extract().
+ *
+ * Each extractable type will have its own way of interpreting the
+ * #GESAsset:id of an asset (or, if it is associated with a specific
+ * subclass of #GESAsset, the asset subclass may handle the
+ * interpretation of the #GESAsset:id). By default, the requested asset
+ * #GESAsset:id will be ignored by a #GESExtractable and will be set to
+ * the type name of the extractable instead. Also by default, when the
+ * requested asset is extracted, the returned object will simply be a
+ * newly created default object of that extractable type. You should check
+ * the documentation for each extractable type to see if they differ from
+ * the default.
+ *
+ * After the object is extracted, it will have a reference to the asset it
+ * came from, which you can retrieve using ges_extractable_get_asset().
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -52,6 +68,7 @@ ges_extractable_get_real_extractable_type_default (GType type, const gchar * id)
   return type;
 }
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
 static GParameter *
 extractable_get_parameters_from_id (const gchar * id, guint * n_params)
 {
@@ -60,9 +77,15 @@ extractable_get_parameters_from_id (const gchar * id, guint * n_params)
   return NULL;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
 static gchar *
 extractable_get_id (GESExtractable * self)
 {
+  GESAsset *asset;
+
+  if ((asset = ges_extractable_get_asset (self)))
+    return g_strdup (ges_asset_get_id (asset));
+
   return g_strdup (g_type_name (G_OBJECT_TYPE (self)));
 
 }
@@ -84,12 +107,12 @@ ges_extractable_default_init (GESExtractableInterface * iface)
 
 /**
  * ges_extractable_get_asset:
- * @self: The #GESExtractable from which to retrieve a #GESAsset
+ * @self: A #GESExtractable
  *
- * Method for getting an asset from a #GESExtractable
+ * Get the asset that has been set on the extractable object.
  *
- * Returns: (transfer none) (nullable): The #GESAsset or %NULL if none has
- * been set
+ * Returns: (transfer none) (nullable): The asset set on @self, or %NULL
+ * if no asset has been set.
  */
 GESAsset *
 ges_extractable_get_asset (GESExtractable * self)
@@ -101,17 +124,28 @@ ges_extractable_get_asset (GESExtractable * self)
 
 /**
  * ges_extractable_set_asset:
- * @self: Target object
- * @asset: (transfer none): The #GESAsset to set
+ * @self: A #GESExtractable
+ * @asset: (transfer none): The asset to set
+ *
+ * Sets the asset for this extractable object.
  *
- * Method to set the asset which instantiated the specified object
+ * When an object is extracted from an asset using ges_asset_extract() its
+ * asset will be automatically set. Note that many classes that implement
+ * #GESExtractable will automatically create their objects using assets
+ * when you call their @new methods. However, you can use this method to
+ * associate an object with a compatible asset if it was created by other
+ * means and does not yet have an asset. Or, for some implementations of
+ * #GESExtractable, you can use this to change the asset of the given
+ * extractable object, which will lead to a change in its state to
+ * match the new asset #GESAsset:id.
  *
- * Return: %TRUE if @asset could be set %FALSE otherwize
+ * Returns: %TRUE if @asset could be successfully set on @self.
  */
 gboolean
 ges_extractable_set_asset (GESExtractable * self, GESAsset * asset)
 {
   GESExtractableInterface *iface;
+  GType extract_type;
 
   g_return_val_if_fail (GES_IS_EXTRACTABLE (self), FALSE);
 
@@ -121,6 +155,16 @@ ges_extractable_set_asset (GESExtractable * self, GESAsset * asset)
   if (iface->can_update_asset == FALSE &&
       g_object_get_qdata (G_OBJECT (self), ges_asset_key)) {
     GST_WARNING_OBJECT (self, "Can not reset asset on object");
+    /* FIXME: do not fail if the same asset */
+
+    return FALSE;
+  }
+
+  extract_type = ges_asset_get_extractable_type (asset);
+  if (G_OBJECT_TYPE (self) != extract_type) {
+    GST_WARNING_OBJECT (self, "Can not set the asset to %" GST_PTR_FORMAT
+        " because its extractable-type is %s, rather than %s",
+        asset, g_type_name (extract_type), G_OBJECT_TYPE_NAME (self));
 
     return FALSE;
   }
@@ -130,6 +174,7 @@ ges_extractable_set_asset (GESExtractable * self, GESAsset * asset)
 
   /* Let classes that implement the interface know that a asset has been set */
   if (iface->set_asset_full)
+    /* FIXME: return to the previous asset if the setting fails */
     return iface->set_asset_full (self, asset);
 
   if (iface->set_asset)
@@ -140,9 +185,18 @@ ges_extractable_set_asset (GESExtractable * self, GESAsset * asset)
 
 /**
  * ges_extractable_get_id:
- * @self: The #GESExtractable
+ * @self: A #GESExtractable
+ *
+ * Gets the #GESAsset:id of some associated asset. It may be the case
+ * that the object has no set asset, or even that such an asset does not
+ * yet exist in the GES cache. Instead, this will return the asset
+ * #GESAsset:id that is _compatible_ with the current state of the object,
+ * as determined by the #GESExtractable implementer. If it was indeed
+ * extracted from an asset, this should return the same as its
+ * corresponding asset #GESAsset:id.
  *
- * Returns: (transfer full): The #id of the associated #GESAsset, free with #g_free
+ * Returns: (transfer full): The #GESAsset:id of some associated #GESAsset
+ * that is compatible with @self's current state.
  */
 gchar *
 ges_extractable_get_id (GESExtractable * self)
@@ -152,6 +206,7 @@ ges_extractable_get_id (GESExtractable * self)
   return GES_EXTRACTABLE_GET_INTERFACE (self)->get_id (self);
 }
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
 /**
  * ges_extractable_type_get_parameters_for_id:
  * @type: The #GType implementing #GESExtractable
@@ -183,6 +238,8 @@ ges_extractable_type_get_parameters_from_id (GType type, const gchar * id,
   return ret;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
+
 /**
  * ges_extractable_type_get_asset_type:
  * @type: The #GType implementing #GESExtractable
index 3347830..77ad495 100644 (file)
@@ -18,8 +18,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef _GES_EXTRACTABLE_
-#define _GES_EXTRACTABLE_
+#pragma once
 
 typedef struct _GESExtractable GESExtractable;
 
@@ -33,28 +32,81 @@ G_BEGIN_DECLS
 
 /* GESExtractable interface declarations */
 #define GES_TYPE_EXTRACTABLE                (ges_extractable_get_type ())
-#define GES_EXTRACTABLE(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_EXTRACTABLE, GESExtractable))
-#define GES_IS_EXTRACTABLE(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_EXTRACTABLE))
 #define GES_EXTRACTABLE_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GES_TYPE_EXTRACTABLE, GESExtractableInterface))
-
 GES_API
-GType ges_extractable_get_type (void);
+G_DECLARE_INTERFACE(GESExtractable, ges_extractable, GES, EXTRACTABLE, GInitiallyUnowned);
 
 /**
  * GESExtractableCheckId:
- * @type: The #GType to check @id for:
- * @id: The id to check
+ * @type: The #GESExtractable type to check @id for
+ * @id: The ID to check
  * @error: An error that can be set if needed
  *
- * Returns: The ID to use for the asset or %NULL if @id is not valid
+ * Method for checking that an ID is valid for the given #GESExtractable
+ * type. If the given ID is considered valid, it can be adjusted into some
+ * standard and returned to prevent the creation of separate #GESAsset-s,
+ * with different #GESAsset:id, that would otherwise act the same.
+ *
+ * Returns (transfer full) (nullable): The actual #GESAsset:id to set on
+ * any corresponding assets, based on @id, or %NULL if @id is not valid.
  */
 
 typedef gchar* (*GESExtractableCheckId) (GType type, const gchar *id,
     GError **error);
 
 /**
- * GESExtractable:
+ * GESExtractableInterface:
+ * @asset_type: The subclass type of #GESAsset that should be created when
+ * an asset with the corresponding #GESAsset:extractable-type is
+ * requested.
+ * @check_id: The method to call to check whether a given ID is valid as
+ * an asset #GESAsset:id for the given #GESAsset:extractable-type. The
+ * returned ID is the actual #GESAsset:id that is set on the asset. The
+ * default implementation will simply always return the type name of the
+ * #GESAsset:extractable-type, even if the received ID is %NULL. As such,
+ * any given ID is considered valid (or is ignored), but only one is
+ * actually ever set on an asset, which means the given
+ * #GESAsset:extractable-type can only have one associated asset.
+ * @can_update_asset: Whether an object of this class can have its
+ * #GESAsset change over its lifetime. This should be set to %TRUE if one
+ * of the object's parameters that is associated with its ID can change
+ * after construction, which would require an asset with a new ID. Note
+ * that the subclass is required to handle the requesting and setting of
+ * the new asset on the object.
+ * @set_asset: This method is called after the #GESAsset of an object is
+ * set. If your class supports the asset of an object changing, then you
+ * can use this method to change the parameters of the object to match the
+ * new asset #GESAsset:id. If setting the asset should be able to fail,
+ * you should implement @set_asset_full instead.
+ * @set_asset_full: Like @set_asset, but also allows you to return %FALSE
+ * to indicate a failure to change the object in response to a change in
+ * its asset.
+ * @get_parameters_from_id: The method to call to get the object
+ * properties corresponding to a given asset #GESAsset:id. The default
+ * implementation will simply return no parameters. The default #GESAsset
+ * will call this to set the returned properties on the extracted object,
+ * but other subclasses may ignore this method.
+ * @get_id: The method to fetch the #GESAsset:id of some associated asset.
+ * Note that it may be the case that the object does not have its asset
+ * set, or even that an asset with such an #GESAsset:id does not exist in
+ * the GES cache. Instead, this should return the #GESAsset:id that is
+ * _compatible_ with the current state of the object. The default
+ * implementation simply returns the currently set asset ID, or the type name
+ * of the object, which is what is used as the #GESAsset:id by default,
+ * if no asset is set.
+ * @get_real_extractable_type: The method to call to get the actual
+ * #GESAsset:extractable-type an asset should have set, given the
+ * requested #GESAsset:id. The default implementation simply returns the
+ * same type as given. You can overwrite this if it is more appropriate
+ * to extract the object from a subclass, depending on the requested
+ * #GESAsset:id. Note that when an asset is requested, this method will be
+ * called before the other class methods. In particular, this means that
+ * the @check_id and @get_parameters_from_id class methods of the returned
+ * type will be used (instead of our own).
+ * @register_metas: The method to set metadata on an asset. This is called
+ * on initiation of the asset, but before it begins to load its state.
  */
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
 struct _GESExtractableInterface
 {
   GTypeInterface parent;
@@ -84,6 +136,7 @@ struct _GESExtractableInterface
 
   gpointer _ges_reserved[GES_PADDING];
 };
+G_GNUC_END_IGNORE_DEPRECATIONS
 
 GES_API
 GESAsset* ges_extractable_get_asset      (GESExtractable *self);
@@ -94,5 +147,4 @@ gboolean ges_extractable_set_asset              (GESExtractable *self,
 GES_API
 gchar * ges_extractable_get_id                 (GESExtractable *self);
 
-G_END_DECLS
-#endif /* _GES_EXTRACTABLE_ */
+G_END_DECLS
\ No newline at end of file
index 238c562..c5b39eb 100644 (file)
 #include "ges-formatter.h"
 #include "ges-internal.h"
 #include "ges.h"
+#ifndef DISABLE_XPTV
+#include "ges-pitivi-formatter.h"
+#endif
+
+#ifdef HAS_PYTHON
+#include <Python.h>
+#include "ges-resources.h"
+#endif
+
+GST_DEBUG_CATEGORY_STATIC (ges_formatter_debug);
+#undef GST_CAT_DEFAULT
+#define GST_CAT_DEFAULT ges_formatter_debug
+static gboolean initialized = FALSE;
 
 /* TODO Add a GCancellable somewhere in the API */
 static void ges_extractable_interface_init (GESExtractableInterface * iface);
@@ -100,6 +113,10 @@ _register_metas (GESExtractableInterface * iface, GObjectClass * class,
   ges_meta_container_register_meta_string (container, GES_META_READ_WRITE,
       GES_META_FORMAT_VERSION, NULL);
 
+  /* We are leaking the metadata but we don't really have choice here
+   * as calling ges_init() after deinit() is allowed.
+   */
+
   return TRUE;
 }
 
@@ -124,11 +141,11 @@ ges_formatter_class_init (GESFormatterClass * klass)
   klass->save_to_uri = NULL;
 
   /* We set dummy  metas */
-  klass->name = "base-formatter";
-  klass->extension = "noextension";
-  klass->description = "Formatter base class, you should give"
-      " a name to your formatter";
-  klass->mimetype = "No mimetype";
+  klass->name = g_strdup ("base-formatter");
+  klass->extension = g_strdup ("noextension");
+  klass->description = g_strdup ("Formatter base class, you should give"
+      " a name to your formatter");
+  klass->mimetype = g_strdup ("No mimetype");
   klass->version = 0.0;
   klass->rank = GST_RANK_NONE;
 }
@@ -232,12 +249,24 @@ ges_formatter_can_load_uri (const gchar * uri, GError ** error)
   for (tmp = formatter_assets; tmp; tmp = tmp->next) {
     GESAsset *asset = GES_ASSET (tmp->data);
     GESFormatter *dummy_instance;
-
-    if (extension
-        && g_strcmp0 (extension,
-            ges_meta_container_get_string (GES_META_CONTAINER (asset),
-                GES_META_FORMATTER_EXTENSION)))
-      continue;
+    gchar **valid_exts =
+        g_strsplit (ges_meta_container_get_string (GES_META_CONTAINER (asset),
+            GES_META_FORMATTER_EXTENSION), ",", -1);
+    gint i;
+
+    if (extension) {
+      gboolean found = FALSE;
+
+      for (i = 0; valid_exts[i]; i++) {
+        if (!g_strcmp0 (extension, valid_exts[i])) {
+          found = TRUE;
+          break;
+        }
+      }
+
+      if (!found)
+        goto next;
+    }
 
     class = g_type_class_ref (ges_asset_get_extractable_type (asset));
     dummy_instance =
@@ -251,7 +280,10 @@ ges_formatter_can_load_uri (const gchar * uri, GError ** error)
     }
     g_type_class_unref (class);
     gst_object_unref (dummy_instance);
+  next:
+    g_strfreev (valid_exts);
   }
+  g_free (extension);
 
   g_list_free (formatter_assets);
   return ret;
@@ -283,7 +315,7 @@ ges_formatter_can_save_uri (const gchar * uri, GError ** error)
 
   if (!(gst_uri_has_protocol (uri, "file"))) {
     gchar *proto = gst_uri_get_protocol (uri);
-    GST_ERROR ("Unspported protocol '%s'", proto);
+    GST_ERROR ("Unsupported protocol '%s'", proto);
     g_free (proto);
     return FALSE;
   }
@@ -347,6 +379,8 @@ error:
  *
  * Returns: TRUE if the timeline data was successfully loaded from the URI,
  * else FALSE.
+ *
+ * Deprecated: 1.18: Use @ges_timeline_load_from_uri
  */
 
 gboolean
@@ -379,6 +413,8 @@ ges_formatter_load_from_uri (GESFormatter * formatter,
  *
  * Returns: TRUE if the timeline data was successfully saved to the URI
  * else FALSE.
+ *
+ * Deprecated: 1.18: Use @ges_timeline_save_to_uri
  */
 
 gboolean
@@ -438,20 +474,36 @@ ges_formatter_get_default (void)
   return ret;
 }
 
+/**
+ * ges_formatter_class_register_metas:
+ * @klass: The class to register metas on
+ * @name: The name of the formatter
+ * @description: The formatter description
+ * @extensions: A list of coma separated file extensions handled
+ * by the formatter. The order of the extensions should match the
+ * list of the structures inside @caps
+ * @caps: The caps the formatter handled, they should match what
+ * gstreamer typefind mechanism will report for the files the formatter
+ * handles.
+ * @version: The version of the formatter
+ * @rank: The rank of the formatter
+ */
 void
-ges_formatter_class_register_metas (GESFormatterClass * class,
-    const gchar * name, const gchar * description, const gchar * extension,
-    const gchar * mimetype, gdouble version, GstRank rank)
+ges_formatter_class_register_metas (GESFormatterClass * klass,
+    const gchar * name, const gchar * description, const gchar * extensions,
+    const gchar * caps, gdouble version, GstRank rank)
 {
-  class->name = name;
-  class->description = description;
-  class->extension = extension;
-  class->mimetype = mimetype;
-  class->version = version;
-  class->rank = rank;
-
-  if (ges_is_initialized () && g_type_class_peek (G_OBJECT_CLASS_TYPE (class)))
-    gst_object_unref (ges_asset_request (G_OBJECT_CLASS_TYPE (class), NULL,
+  g_return_if_fail (klass->name);
+  klass->name = g_strdup (name);
+  klass->description = g_strdup (description);
+  klass->extension = g_strdup (extensions);
+  klass->mimetype = g_strdup (caps);
+  klass->version = version;
+  klass->rank = rank;
+
+  if (g_atomic_int_get (&initialized)
+      && g_type_class_peek (G_OBJECT_CLASS_TYPE (klass)))
+    gst_object_unref (ges_asset_request (G_OBJECT_CLASS_TYPE (klass), NULL,
             NULL));
 }
 
@@ -493,15 +545,155 @@ _list_formatters (GType * formatters, guint n_formatters)
   }
 }
 
+static void
+load_python_formatters (void)
+{
+#ifdef HAS_PYTHON
+  PyGILState_STATE state = 0;
+  PyObject *main_module, *main_locals;
+  GError *err = NULL;
+  GResource *resource = ges_get_resource ();
+  GBytes *bytes =
+      g_resource_lookup_data (resource, "/ges/python/gesotioformatter.py",
+      G_RESOURCE_LOOKUP_FLAGS_NONE, &err);
+  PyObject *code = NULL, *res = NULL;
+  gboolean we_initialized = FALSE;
+  GModule *libpython;
+  gpointer has_python = NULL;
+
+  GST_LOG ("Checking to see if libpython is already loaded");
+  if (g_module_symbol (g_module_open (NULL, G_MODULE_BIND_LOCAL),
+          "_Py_NoneStruct", &has_python) && has_python) {
+    GST_LOG ("libpython is already loaded");
+  } else {
+    const gchar *libpython_path =
+        PY_LIB_LOC "/libpython" PYTHON_VERSION PY_ABI_FLAGS "." PY_LIB_SUFFIX;
+    GST_LOG ("loading libpython from '%s'", libpython_path);
+    libpython = g_module_open (libpython_path, 0);
+    if (!libpython) {
+      GST_ERROR ("Couldn't g_module_open libpython. Reason: %s",
+          g_module_error ());
+      return;
+    }
+  }
+
+  if (!Py_IsInitialized ()) {
+    GST_LOG ("python wasn't initialized");
+    /* set the correct plugin for registering stuff */
+    Py_Initialize ();
+    we_initialized = TRUE;
+  } else {
+    GST_LOG ("python was already initialized");
+    state = PyGILState_Ensure ();
+  }
+
+  if (!bytes) {
+    GST_DEBUG ("Could not load gesotioformatter: %s\n", err->message);
+
+    g_clear_error (&err);
+
+    goto done;
+  }
+
+  main_module = PyImport_AddModule ("__main__");
+  if (main_module == NULL) {
+    GST_WARNING ("Could not add main module");
+    PyErr_Print ();
+    PyErr_Clear ();
+    goto done;
+  }
+
+  main_locals = PyModule_GetDict (main_module);
+  /* Compiling the code ourself so it has a proper filename */
+  code =
+      Py_CompileString (g_bytes_get_data (bytes, NULL), "gesotioformatter.py",
+      Py_file_input);
+  if (PyErr_Occurred ()) {
+    PyErr_Print ();
+    PyErr_Clear ();
+    goto done;
+  }
+  res = PyEval_EvalCode ((gpointer) code, main_locals, main_locals);
+  Py_XDECREF (code);
+  Py_XDECREF (res);
+  if (PyErr_Occurred ()) {
+    PyObject *exception_backtrace;
+    PyObject *exception_type;
+    PyObject *exception_value, *exception_value_repr, *exception_value_str;
+
+    PyErr_Fetch (&exception_type, &exception_value, &exception_backtrace);
+    PyErr_NormalizeException (&exception_type, &exception_value,
+        &exception_backtrace);
+
+    exception_value_repr = PyObject_Repr (exception_value);
+    exception_value_str =
+        PyUnicode_AsEncodedString (exception_value_repr, "utf-8", "Error ~");
+    GST_INFO ("Could not load OpenTimelineIO formatter: %s",
+        PyBytes_AS_STRING (exception_value_str));
+
+    Py_XDECREF (exception_type);
+    Py_XDECREF (exception_value);
+    Py_XDECREF (exception_backtrace);
+
+    Py_XDECREF (exception_value_repr);
+    Py_XDECREF (exception_value_str);
+    PyErr_Clear ();
+  }
+
+done:
+  if (bytes)
+    g_bytes_unref (bytes);
+
+  if (we_initialized) {
+    PyEval_SaveThread ();
+  } else {
+    PyGILState_Release (state);
+  }
+#endif /* HAS_PYTHON */
+}
+
 void
 _init_formatter_assets (void)
 {
   GType *formatters;
   guint n_formatters;
+  static gsize init_debug = 0;
+
+  if (g_once_init_enter (&init_debug)) {
+
+    GST_DEBUG_CATEGORY_INIT (ges_formatter_debug, "gesformatter",
+        GST_DEBUG_FG_YELLOW, "ges formatter");
+    g_once_init_leave (&init_debug, TRUE);
+  }
 
-  formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
-  _list_formatters (formatters, n_formatters);
-  g_free (formatters);
+  if (g_atomic_int_compare_and_exchange (&initialized, FALSE, TRUE)) {
+    /* register formatter types with the system */
+#ifndef DISABLE_XPTV
+    g_type_class_ref (GES_TYPE_PITIVI_FORMATTER);
+#endif
+    g_type_class_ref (GES_TYPE_COMMAND_LINE_FORMATTER);
+    g_type_class_ref (GES_TYPE_XML_FORMATTER);
+
+    load_python_formatters ();
+
+    formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
+    _list_formatters (formatters, n_formatters);
+    g_free (formatters);
+  }
+}
+
+void
+_deinit_formatter_assets (void)
+{
+  if (g_atomic_int_compare_and_exchange (&initialized, TRUE, FALSE)) {
+
+#ifndef DISABLE_XPTV
+    g_type_class_unref (g_type_class_peek (GES_TYPE_PITIVI_FORMATTER));
+#endif
+
+    g_type_class_unref (g_type_class_peek (GES_TYPE_COMMAND_LINE_FORMATTER));
+    g_type_class_unref (g_type_class_peek (GES_TYPE_XML_FORMATTER));
+  }
 }
 
 static gint
@@ -554,3 +746,56 @@ _find_formatter_asset_for_id (const gchar * id)
 
   return asset;
 }
+
+/**
+ * ges_find_formatter_for_uri:
+ *
+ * Get the best formatter for @uri. It tries to find a formatter
+ * compatible with @uri extension, if none is found, it returns the default
+ * formatter asset.
+ *
+ * Returns: (transfer none): The #GESAsset for the best formatter to save to @uri
+ *
+ * Since: 1.18
+ */
+GESAsset *
+ges_find_formatter_for_uri (const gchar * uri)
+{
+  GList *formatter_assets, *tmp;
+  GESAsset *asset = NULL;
+
+  gchar *extension = _get_extension (uri);
+  if (!extension)
+    return ges_formatter_get_default ();
+
+  formatter_assets = g_list_sort (ges_list_assets (GES_TYPE_FORMATTER),
+      (GCompareFunc) _sort_formatters);
+
+  for (tmp = formatter_assets; tmp; tmp = tmp->next) {
+    gint i;
+    gchar **valid_exts =
+        g_strsplit (ges_meta_container_get_string (GES_META_CONTAINER
+            (tmp->data),
+            GES_META_FORMATTER_EXTENSION), ",", -1);
+
+    for (i = 0; valid_exts[i]; i++) {
+      if (!g_strcmp0 (extension, valid_exts[i])) {
+        asset = GES_ASSET (tmp->data);
+        break;
+      }
+    }
+
+    g_strfreev (valid_exts);
+    if (asset)
+      break;
+  }
+  g_free (extension);
+  g_list_free (formatter_assets);
+
+  if (asset) {
+    GST_INFO_OBJECT (asset, "Using for URI %s", uri);
+    return asset;
+  }
+
+  return ges_formatter_get_default ();
+}
index 3cf4d7a..98e57ab 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_FORMATTER
-#define _GES_FORMATTER
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-timeline.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_FORMATTER ges_formatter_get_type()
-
-#define GES_FORMATTER(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_FORMATTER, GESFormatter))
-
-#define GES_FORMATTER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_FORMATTER, GESFormatterClass))
-
-#define GES_IS_FORMATTER(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_FORMATTER))
-
-#define GES_IS_FORMATTER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_FORMATTER))
-
-#define GES_FORMATTER_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_FORMATTER, GESFormatterClass))
-
-typedef struct _GESFormatterPrivate GESFormatterPrivate;
+GES_DECLARE_TYPE(Formatter, formatter, FORMATTER);
 
 /**
  * GESFormatter:
@@ -51,7 +34,8 @@ typedef struct _GESFormatterPrivate GESFormatterPrivate;
  * Base class for timeline data serialization and deserialization.
  */
 
-struct _GESFormatter {
+struct _GESFormatter
+{
   GInitiallyUnowned parent;
 
   /*< private >*/
@@ -80,8 +64,6 @@ typedef gboolean (*GESFormatterCanLoadURIMethod) (GESFormatter *dummy_instance,
  *
  * Returns: TRUE if the @timeline was properly loaded from the given @uri,
  * else FALSE.
- *
- * Deprecated: 1.16: Use @ges_timeline_load_from_uri
  **/
 typedef gboolean (*GESFormatterLoadFromURIMethod) (GESFormatter *formatter,
                   GESTimeline *timeline,
@@ -102,8 +84,6 @@ typedef gboolean (*GESFormatterLoadFromURIMethod) (GESFormatter *formatter,
  *
  * Returns: TRUE if the @timeline was properly stored to the given @uri,
  * else FALSE.
- *
- * Deprecated: 1.16: Use @ges_timeline_save_to_uri
  */
 typedef gboolean (*GESFormatterSaveToURIMethod) (GESFormatter *formatter,
                GESTimeline *timeline, const gchar * uri, gboolean overwrite,
@@ -130,10 +110,10 @@ struct _GESFormatterClass {
   GESFormatterSaveToURIMethod save_to_uri;
 
   /* < private > */
-  const gchar *name;
-  const gchar *description;
-  const gchar *extension;
-  const gchar *mimetype;
+  gchar *name;
+  gchar *description;
+  gchar *extension;
+  gchar *mimetype;
   gdouble version;
   GstRank rank;
 
@@ -143,14 +123,11 @@ struct _GESFormatterClass {
 };
 
 GES_API
-GType ges_formatter_get_type (void);
-
-GES_API
 void ges_formatter_class_register_metas (GESFormatterClass * klass,
                                          const gchar *name,
                                          const gchar *description,
-                                         const gchar *extension,
-                                         const gchar *mimetype,
+                                         const gchar *extensions,
+                                         const gchar *caps,
                                          gdouble version,
                                          GstRank rank);
 
@@ -159,13 +136,13 @@ gboolean ges_formatter_can_load_uri     (const gchar * uri, GError **error);
 GES_API
 gboolean ges_formatter_can_save_uri     (const gchar * uri, GError **error);
 
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_load_from_uri)
 gboolean ges_formatter_load_from_uri    (GESFormatter * formatter,
                                          GESTimeline  *timeline,
                                          const gchar *uri,
                                          GError **error);
 
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_save_to_uri)
 gboolean ges_formatter_save_to_uri      (GESFormatter * formatter,
                                          GESTimeline *timeline,
                                          const gchar *uri,
@@ -175,6 +152,7 @@ gboolean ges_formatter_save_to_uri      (GESFormatter * formatter,
 GES_API
 GESAsset *ges_formatter_get_default    (void);
 
-G_END_DECLS
+GES_API
+GESAsset *ges_find_formatter_for_uri   (const gchar *uri);
 
-#endif /* _GES_FORMATTER */
+G_END_DECLS
index eaa60f9..ad2782a 100644 (file)
@@ -16,9 +16,7 @@
  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  */
-
-#ifndef __GES_ERROR_H__
-#define __GES_ERROR_H__
+#pragma once
 
 /**
  * SECTION: ges-gerror
@@ -40,13 +38,29 @@ G_BEGIN_DECLS
  * @GES_ERROR_ASSET_WRONG_ID: The ID passed is malformed
  * @GES_ERROR_ASSET_LOADING: An error happened while loading the asset
  * @GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE: The formatted files was malformed
+ * @GES_ERROR_INVALID_FRAME_NUMBER: The frame number is invalid
+ * @GES_ERROR_NEGATIVE_LAYER: The operation would lead to a negative
+ * #GES_TIMELINE_ELEMENT_LAYER_PRIORITY. (Since: 1.18)
+ * @GES_ERROR_NEGATIVE_TIME: The operation would lead to a negative time.
+ * E.g. for the #GESTimelineElement:start #GESTimelineElement:duration or
+ * #GESTimelineElement:in-point. (Since: 1.18)
+ * @GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT: Some #GESTimelineElement does
+ * not have a large enough #GESTimelineElement:max-duration to cover the
+ * desired operation. (Since: 1.18)
+ * @GES_ERROR_INVALID_OVERLAP_IN_TRACK: The operation would break one of
+ * the overlap conditions for the #GESTimeline. (Since: 1.18)
  */
 typedef enum
 {
   GES_ERROR_ASSET_WRONG_ID,
   GES_ERROR_ASSET_LOADING,
   GES_ERROR_FORMATTER_MALFORMED_INPUT_FILE,
+  GES_ERROR_INVALID_FRAME_NUMBER,
+  GES_ERROR_NEGATIVE_LAYER,
+  GES_ERROR_NEGATIVE_TIME,
+  GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
+  GES_ERROR_INVALID_OVERLAP_IN_TRACK,
+  GES_ERROR_INVALID_EFFECT_BIN_DESCRIPTION,
 } GESError;
 
 G_END_DECLS
-#endif /* __GES_ERROR_H__ */
index 44c29bd..0685170 100644 (file)
 /**
  * SECTION:gesgroup
  * @title: GESGroup
- * @short_description: Class that permits to group GESClip-s in a timeline,
- * letting the user manage it a single GESTimelineElement
+ * @short_description: Class for a collection of #GESContainer-s within
+ * a single timeline.
  *
- * A #GESGroup is an object which controls one or more
- * #GESClips in one or more #GESLayer(s).
+ * A #GESGroup controls one or more #GESContainer-s (usually #GESClip-s,
+ * but it can also control other #GESGroup-s). Its children must share
+ * the same #GESTimeline, but can otherwise lie in separate #GESLayer-s
+ * and have different timings.
  *
- * To instanciate a group, you should use the ges_container_group method,
- * this will be responsible for deciding what subclass of #GESContainer
- * should be instaciated to group the various #GESTimelineElement passed
- * in parametter.
+ * To initialise a group, you may want to use ges_container_group(),
+ * and similarly use ges_container_ungroup() to dispose of it.
+ *
+ * A group will maintain the relative #GESTimelineElement:start times of
+ * its children, as well as their relative layer #GESLayer:priority.
+ * Therefore, if one of its children has its #GESTimelineElement:start
+ * set, all other children will have their #GESTimelineElement:start
+ * shifted by the same amount. Similarly, if one of its children moves to
+ * a new layer, the other children will also change layers to maintain the
+ * difference in their layer priorities. For example, if a child moves
+ * from a layer with #GESLayer:priority 1 to a layer with priority 3, then
+ * another child that was in a layer with priority 0 will move to the
+ * layer with priority 2.
+ *
+ * The #GESGroup:start of a group refers to the earliest start
+ * time of its children. If the group's #GESGroup:start is set, all the
+ * children will be shifted equally such that the earliest start time
+ * will match the set value. The #GESGroup:duration of a group is the
+ * difference between the earliest start time and latest end time of its
+ * children. If the group's #GESGroup:duration is increased, the children
+ * whose end time matches the end of the group will be extended
+ * accordingly. If it is decreased, then any child whose end time exceeds
+ * the new end time will also have their duration decreased accordingly.
+ *
+ * A group may span several layers, but for methods such as
+ * ges_timeline_element_get_layer_priority() and
+ * ges_timeline_element_edit() a group is considered to have a layer
+ * priority that is the highest #GESLayer:priority (numerically, the
+ * smallest) of all the layers it spans.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -42,8 +69,6 @@
 #include <string.h>
 
 #define parent_class ges_group_parent_class
-#define GES_CHILDREN_INIBIT_SIGNAL_EMISSION (GES_CHILDREN_LAST + 1)
-#define GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT "ges-group-signals-ids-%p"
 
 struct _GESGroupPrivate
 {
@@ -51,9 +76,11 @@ struct _GESGroupPrivate
 
   guint32 max_layer_prio;
 
+  gboolean updating_priority;
   /* This is used while were are setting ourselve a proper timing value,
    * in this case the value should always be kept */
   gboolean setting_value;
+  GHashTable *child_sigids;
 };
 
 typedef struct
@@ -89,12 +116,12 @@ _update_our_values (GESGroup * group)
   GESContainer *container = GES_CONTAINER (group);
   guint32 min_layer_prio = G_MAXINT32, max_layer_prio = 0;
 
-  for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
+  for (tmp = container->children; tmp; tmp = tmp->next) {
     GESContainer *child = tmp->data;
 
     if (GES_IS_CLIP (child)) {
       GESLayer *layer = ges_clip_get_layer (GES_CLIP (child));
-      gint32 prio;
+      guint32 prio;
 
       if (!layer)
         continue;
@@ -103,257 +130,120 @@ _update_our_values (GESGroup * group)
 
       min_layer_prio = MIN (prio, min_layer_prio);
       max_layer_prio = MAX (prio, max_layer_prio);
+      gst_object_unref (layer);
     } else if (GES_IS_GROUP (child)) {
-      gint32 prio = _PRIORITY (child), height = GES_CONTAINER_HEIGHT (child);
+      guint32 prio = _PRIORITY (child), height = GES_CONTAINER_HEIGHT (child);
 
       min_layer_prio = MIN (prio, min_layer_prio);
-      max_layer_prio = MAX ((prio + height), max_layer_prio);
+      max_layer_prio = MAX ((prio + height - 1), max_layer_prio);
     }
   }
 
   if (min_layer_prio != _PRIORITY (group)) {
-    group->priv->setting_value = TRUE;
+    group->priv->updating_priority = TRUE;
     _set_priority0 (GES_TIMELINE_ELEMENT (group), min_layer_prio);
-    group->priv->setting_value = FALSE;
-    for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
-      GESTimelineElement *child = tmp->data;
-      guint32 child_prio = GES_IS_CLIP (child) ?
-          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) : _PRIORITY (child);
-
-      _ges_container_set_priority_offset (container,
-          child, min_layer_prio - child_prio);
-    }
+    group->priv->updating_priority = FALSE;
   }
 
+  /* FIXME: max_layer_prio not used elsewhere
+   * We could use it to inform our parent group when our maximum has
+   * changed (which we don't currently do, to allow it to change its
+   * height) */
   group->priv->max_layer_prio = max_layer_prio;
-  _ges_container_set_height (GES_CONTAINER (group),
-      max_layer_prio - min_layer_prio + 1);
+  _ges_container_set_height (container, max_layer_prio - min_layer_prio + 1);
 }
 
+/* layer changed its priority in the timeline */
 static void
 _child_priority_changed_cb (GESLayer * layer,
     GParamSpec * arg G_GNUC_UNUSED, GESTimelineElement * clip)
 {
-  GESContainer *container = GES_CONTAINER (GES_TIMELINE_ELEMENT_PARENT (clip));
-
-  gint layer_prio = ges_layer_get_priority (layer);
-  gint offset = _ges_container_get_priority_offset (container, clip);
-
-  if (container->children_control_mode != GES_CHILDREN_UPDATE) {
-    GST_DEBUG_OBJECT (container, "Ignoring updated");
-    return;
-  }
-
-  if (layer_prio + offset == _PRIORITY (container))
-    return;
-
-  container->initiated_move = clip;
-  _set_priority0 (GES_TIMELINE_ELEMENT (container), layer_prio + offset);
-  container->initiated_move = NULL;
+  _update_our_values (GES_GROUP (clip->parent));
 }
 
 static void
-_child_clip_changed_layer_cb (GESTimelineElement * clip,
-    GParamSpec * arg G_GNUC_UNUSED, GESGroup * group)
+_child_clip_changed_layer_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
+    GESGroup * group)
 {
   ChildSignalIds *sigids;
-  gchar *signals_ids_key;
-  GESLayer *old_layer, *new_layer;
-  gint offset, layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip);
-  GESContainer *container = GES_CONTAINER (group);
 
-  offset = _ges_container_get_priority_offset (container, clip);
-  signals_ids_key =
-      g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, clip);
-  sigids = g_object_get_data (G_OBJECT (group), signals_ids_key);
-  g_free (signals_ids_key);
-  old_layer = sigids->layer;
+  sigids = g_hash_table_lookup (group->priv->child_sigids, clip);
 
-  new_layer = ges_clip_get_layer (GES_CLIP (clip));
+  g_assert (sigids);
 
-  if (sigids->child_priority_changed_sid) {
-    g_signal_handler_disconnect (old_layer, sigids->child_priority_changed_sid);
+  if (sigids->layer) {
+    g_signal_handler_disconnect (sigids->layer,
+        sigids->child_priority_changed_sid);
     sigids->child_priority_changed_sid = 0;
+    gst_object_unref (sigids->layer);
   }
 
-  if (new_layer) {
+  sigids->layer = ges_clip_get_layer (GES_CLIP (clip));
+
+  if (sigids->layer) {
     sigids->child_priority_changed_sid =
-        g_signal_connect (new_layer, "notify::priority",
+        g_signal_connect (sigids->layer, "notify::priority",
         (GCallback) _child_priority_changed_cb, clip);
   }
-  sigids->layer = new_layer;
-
-  if (container->children_control_mode != GES_CHILDREN_UPDATE) {
-    if (container->children_control_mode == GES_CHILDREN_INIBIT_SIGNAL_EMISSION) {
-      container->children_control_mode = GES_CHILDREN_UPDATE;
-      g_signal_stop_emission_by_name (clip, "notify::layer");
-    }
-    return;
-  }
-
-  if (new_layer && old_layer && (layer_prio + offset < 0 ||
-          (GES_TIMELINE_ELEMENT_TIMELINE (group) &&
-              layer_prio + offset + GES_CONTAINER_HEIGHT (group) - 1 >
-              g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers)))) {
-
-    GST_INFO_OBJECT (container,
-        "Trying to move to a layer %" GST_PTR_FORMAT " outside of"
-        "the timeline layers, moving back to old layer (prio %i)", new_layer,
-        _PRIORITY (group) - offset);
-
-    container->children_control_mode = GES_CHILDREN_INIBIT_SIGNAL_EMISSION;
-    ges_clip_move_to_layer (GES_CLIP (clip), old_layer);
-    g_signal_stop_emission_by_name (clip, "notify::layer");
 
-    return;
-  }
-
-  if (!new_layer || !old_layer) {
-    _update_our_values (group);
-
-    return;
-  }
-
-  container->initiated_move = clip;
-  _set_priority0 (GES_TIMELINE_ELEMENT (group), layer_prio + offset);
-  container->initiated_move = NULL;
+  _update_our_values (group);
 }
 
 static void
 _child_group_priority_changed (GESTimelineElement * child,
     GParamSpec * arg G_GNUC_UNUSED, GESGroup * group)
 {
-  gint offset;
-  GESContainer *container = GES_CONTAINER (group);
-
-  if (container->children_control_mode != GES_CHILDREN_UPDATE) {
-    GST_DEBUG_OBJECT (group, "Ignoring updated");
-    return;
-  }
-
-  offset = _ges_container_get_priority_offset (container, child);
-
-  if (_PRIORITY (group) < offset ||
-      (GES_TIMELINE_ELEMENT_TIMELINE (group) &&
-          _PRIORITY (group) + offset + GES_CONTAINER_HEIGHT (group) >
-          g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers))) {
-
-    GST_WARNING_OBJECT (container, "Trying to move to a layer outside of"
-        "the timeline layers");
-
-    return;
-  }
-
-  container->initiated_move = child;
-  _set_priority0 (GES_TIMELINE_ELEMENT (group), _PRIORITY (child) + offset);
-  container->initiated_move = NULL;
+  _update_our_values (group);
 }
 
 /****************************************************
  *              GESTimelineElement vmethods         *
  ****************************************************/
-static gboolean
-_trim (GESTimelineElement * group, GstClockTime start)
-{
-  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
-
-  if (timeline == NULL) {
-    GST_DEBUG ("Not in a timeline yet");
-
-    return FALSE;
-  }
-
-  return timeline_tree_trim (timeline_get_tree (timeline), group,
-      0, GST_CLOCK_DIFF (start, _START (group)), GES_EDGE_START,
-      ges_timeline_get_snapping_distance (timeline));
-}
 
 static gboolean
 _set_priority (GESTimelineElement * element, guint32 priority)
 {
-  GList *tmp, *layers;
-  gint diff = priority - _PRIORITY (element);
-  GESContainer *container = GES_CONTAINER (element);
-  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
+  GList *layers;
+  GESTimeline *timeline = element->timeline;
 
-  if (GES_GROUP (element)->priv->setting_value == TRUE)
+  if (GES_GROUP (element)->priv->updating_priority == TRUE
+      || GES_TIMELINE_ELEMENT_BEING_EDITED (element))
     return TRUE;
 
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-
-  layers = GES_TIMELINE_ELEMENT_TIMELINE (element) ?
-      GES_TIMELINE_ELEMENT_TIMELINE (element)->layers : NULL;
+  layers = timeline ? timeline->layers : NULL;
   if (layers == NULL) {
     GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing"
-        "anything, timeline: %" GST_PTR_FORMAT,
-        GES_TIMELINE_ELEMENT_TIMELINE (element));
+        "anything, timeline: %" GST_PTR_FORMAT, timeline);
 
     return FALSE;
   }
 
-  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
-    GESTimelineElement *child = tmp->data;
+  /* FIXME: why are we not shifting ->max_layer_prio? */
 
-    if (child != container->initiated_move
-        || ELEMENT_FLAG_IS_SET (container, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-      if (GES_IS_CLIP (child)) {
-        guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) + diff;
-        GESLayer *layer =
-            g_list_nth_data (GES_TIMELINE_GET_LAYERS (timeline), layer_prio);
-
-        if (layer == NULL) {
-          do {
-            layer = ges_timeline_append_layer (timeline);
-          } while (ges_layer_get_priority (layer) < layer_prio);
-        }
-
-        GST_DEBUG ("%" GES_FORMAT "moving from layer: %i to %i",
-            GES_ARGS (child), GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child),
-            layer_prio);
-        ges_clip_move_to_layer (GES_CLIP (child), g_list_nth_data (layers,
-                layer_prio));
-      } else if (GES_IS_GROUP (child)) {
-        GST_DEBUG_OBJECT (child, "moving from %i to %i",
-            _PRIORITY (child), diff + _PRIORITY (child));
-        ges_timeline_element_set_priority (child, diff + _PRIORITY (child));
-      }
-    }
-  }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
-
-  return TRUE;
+  return timeline_tree_move (timeline_get_tree (timeline),
+      element, (gint64) (element->priority) - (gint64) priority, 0,
+      GES_EDGE_NONE, 0, NULL);
 }
 
 static gboolean
 _set_start (GESTimelineElement * element, GstClockTime start)
 {
-  GList *tmp;
+  GList *tmp, *children;
   gint64 diff = start - _START (element);
-  GESTimeline *timeline;
   GESContainer *container = GES_CONTAINER (element);
-  GESTimelineElement *toplevel =
-      ges_timeline_element_get_toplevel_parent (element);;
 
-  gst_object_unref (toplevel);
   if (GES_GROUP (element)->priv->setting_value == TRUE)
     /* Let GESContainer update itself */
     return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element,
         start);
 
-  if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
-      ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-    container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-    for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next)
-      _set_start0 (tmp->data, _START (tmp->data) + diff);
-    container->children_control_mode = GES_CHILDREN_UPDATE;
-
-    return TRUE;
-  }
-
-  timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
-  if (timeline)
-    return ges_timeline_move_object_simple (timeline, element, NULL,
-        GES_EDGE_NONE, start);
+  /* get copy of children, since GESContainer may resort the group */
+  children = ges_container_get_children (container, FALSE);
+  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+  for (tmp = children; tmp; tmp = tmp->next)
+    _set_start0 (tmp->data, _START (tmp->data) + diff);
+  container->children_control_mode = GES_CHILDREN_UPDATE;
+  g_list_free_full (children, gst_object_unref);
 
   return TRUE;
 }
@@ -361,13 +251,29 @@ _set_start (GESTimelineElement * element, GstClockTime start)
 static gboolean
 _set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
 {
-  return FALSE;
+  if (inpoint != 0) {
+    GST_WARNING_OBJECT (element, "The in-point of a group has no meaning,"
+        " it can not be set to a non-zero value");
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static gboolean
+_set_max_duration (GESTimelineElement * element, GstClockTime max_duration)
+{
+  if (GST_CLOCK_TIME_IS_VALID (max_duration)) {
+    GST_WARNING_OBJECT (element, "The max-duration of a group has no "
+        "meaning, it can not be set to a valid GstClockTime value");
+    return FALSE;
+  }
+  return TRUE;
 }
 
 static gboolean
 _set_duration (GESTimelineElement * element, GstClockTime duration)
 {
-  GList *tmp;
+  GList *tmp, *children;
   GstClockTime last_child_end = 0, new_end;
   GESContainer *container = GES_CONTAINER (element);
   GESGroupPrivate *priv = GES_GROUP (element)->priv;
@@ -377,18 +283,14 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
     return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element,
         duration);
 
-  if (element->timeline
-      && !timeline_tree_can_move_element (timeline_get_tree (element->timeline),
-          element, _PRIORITY (element), element->start, duration, NULL)) {
-    return FALSE;
-  }
-
   if (container->initiated_move == NULL) {
     gboolean expending = (_DURATION (element) < duration);
 
     new_end = _START (element) + duration;
+    /* get copy of children, since GESContainer may resort the group */
+    children = ges_container_get_children (container, FALSE);
     container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-    for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+    for (tmp = children; tmp; tmp = tmp->next) {
       GESTimelineElement *child = tmp->data;
       GstClockTime n_dur;
 
@@ -399,6 +301,7 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
       }
     }
     container->children_control_mode = GES_CHILDREN_UPDATE;
+    g_list_free_full (children, gst_object_unref);
   }
 
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
@@ -411,7 +314,7 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
   _set_duration0 (element, last_child_end - _START (element));
   priv->setting_value = FALSE;
 
-  return FALSE;
+  return -1;
 }
 
 /****************************************************
@@ -423,77 +326,51 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
 static gboolean
 _add_child (GESContainer * group, GESTimelineElement * child)
 {
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
   g_return_val_if_fail (GES_IS_CONTAINER (child), FALSE);
 
+  if (timeline && child->timeline != timeline) {
+    GST_WARNING_OBJECT (group, "Cannot add child %" GES_FORMAT
+        " because it belongs to timeline %" GST_PTR_FORMAT
+        " rather than the group's timeline %" GST_PTR_FORMAT,
+        GES_ARGS (child), child->timeline, timeline);
+    return FALSE;
+  }
+
   return TRUE;
 }
 
 static void
 _child_added (GESContainer * group, GESTimelineElement * child)
 {
-  GList *children, *tmp;
-  gchar *signals_ids_key;
-  ChildSignalIds *signals_ids;
-
-  GESGroupPrivate *priv = GES_GROUP (group)->priv;
-  GstClockTime last_child_end = 0, first_child_start = G_MAXUINT64;
-
-  if (!GES_TIMELINE_ELEMENT_TIMELINE (group)
-      && GES_TIMELINE_ELEMENT_TIMELINE (child)) {
-    timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (child),
-        GES_GROUP (group));
-    timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE (child),
-        GES_GROUP (group));
-  }
-
-  children = GES_CONTAINER_CHILDREN (group);
-
-  for (tmp = children; tmp; tmp = tmp->next) {
-    last_child_end = MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end);
-    first_child_start =
-        MIN (GES_TIMELINE_ELEMENT_START (tmp->data), first_child_start);
-  }
+  GESGroup *self = GES_GROUP (group);
+  ChildSignalIds *sigids;
 
-  priv->setting_value = TRUE;
-  ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  if (first_child_start != GES_TIMELINE_ELEMENT_START (group)) {
-    group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-    _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
+  /* NOTE: notifies are currently frozen by ges_container_add */
+  if (!GES_TIMELINE_ELEMENT_TIMELINE (group) && child->timeline) {
+    timeline_add_group (child->timeline, self);
+    timeline_emit_group_added (child->timeline, self);
   }
 
-  if (last_child_end != GES_TIMELINE_ELEMENT_END (group)) {
-    _set_duration0 (GES_TIMELINE_ELEMENT (group),
-        last_child_end - first_child_start);
-  }
-  ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  priv->setting_value = FALSE;
+  _update_our_values (self);
 
-  group->children_control_mode = GES_CHILDREN_UPDATE;
-  _update_our_values (GES_GROUP (group));
+  sigids = g_new0 (ChildSignalIds, 1);
+  /* doesn't take a ref to child since no data */
+  g_hash_table_insert (self->priv->child_sigids, child, sigids);
 
-  signals_ids_key =
-      g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, child);
-  signals_ids = g_malloc0 (sizeof (ChildSignalIds));
-  g_object_set_data_full (G_OBJECT (group), signals_ids_key,
-      signals_ids, g_free);
-  g_free (signals_ids_key);
   if (GES_IS_CLIP (child)) {
-    GESLayer *layer = ges_clip_get_layer (GES_CLIP (child));
+    sigids->layer = ges_clip_get_layer (GES_CLIP (child));
 
-    signals_ids->child_clip_changed_layer_sid =
-        g_signal_connect (child, "notify::layer",
-        (GCallback) _child_clip_changed_layer_cb, group);
+    sigids->child_clip_changed_layer_sid = g_signal_connect (child,
+        "notify::layer", (GCallback) _child_clip_changed_layer_cb, group);
 
-    if (layer) {
-      signals_ids->child_priority_changed_sid = g_signal_connect (layer,
+    if (sigids->layer) {
+      sigids->child_priority_changed_sid = g_signal_connect (sigids->layer,
           "notify::priority", (GCallback) _child_priority_changed_cb, child);
     }
-    signals_ids->layer = layer;
-
-  } else if (GES_IS_GROUP (child), group) {
-    signals_ids->child_group_priority_changed_sid =
-        g_signal_connect (child, "notify::priority",
-        (GCallback) _child_group_priority_changed, group);
+  } else if (GES_IS_GROUP (child)) {
+    sigids->child_group_priority_changed_sid = g_signal_connect (child,
+        "notify::priority", (GCallback) _child_group_priority_changed, group);
   }
 }
 
@@ -519,43 +396,29 @@ _disconnect_signals (GESGroup * group, GESTimelineElement * child,
   }
 }
 
-
 static void
 _child_removed (GESContainer * group, GESTimelineElement * child)
 {
-  GList *children;
-  GstClockTime first_child_start;
-  gchar *signals_ids_key;
+  GESGroup *self = GES_GROUP (group);
   ChildSignalIds *sigids;
-  GESGroupPrivate *priv = GES_GROUP (group)->priv;
 
+  /* NOTE: notifies are currently frozen by ges_container_add */
   _ges_container_sort_children (group);
 
-  children = GES_CONTAINER_CHILDREN (group);
+  sigids = g_hash_table_lookup (self->priv->child_sigids, child);
 
-  signals_ids_key =
-      g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, child);
-  sigids = g_object_get_data (G_OBJECT (group), signals_ids_key);
-  _disconnect_signals (GES_GROUP (group), child, sigids);
-  g_free (signals_ids_key);
-  if (children == NULL) {
+  g_assert (sigids);
+  _disconnect_signals (self, child, sigids);
+  g_hash_table_remove (self->priv->child_sigids, child);
+
+  if (group->children == NULL) {
     GST_FIXME_OBJECT (group, "Auto destroy myself?");
     if (GES_TIMELINE_ELEMENT_TIMELINE (group))
-      timeline_remove_group (GES_TIMELINE_ELEMENT_TIMELINE (group),
-          GES_GROUP (group));
+      timeline_remove_group (GES_TIMELINE_ELEMENT_TIMELINE (group), self);
     return;
   }
 
-  priv->setting_value = TRUE;
-  ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  first_child_start = GES_TIMELINE_ELEMENT_START (children->data);
-  if (first_child_start > GES_TIMELINE_ELEMENT_START (group)) {
-    group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-    _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
-    group->children_control_mode = GES_CHILDREN_UPDATE;
-  }
-  ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  priv->setting_value = FALSE;
+  _update_our_values (GES_GROUP (group));
 }
 
 static GList *
@@ -594,7 +457,7 @@ _group (GList * containers)
 {
   GList *tmp;
   GESTimeline *timeline = NULL;
-  GESContainer *ret = g_object_new (GES_TYPE_GROUP, NULL);
+  GESContainer *ret = GES_CONTAINER (ges_group_new ());
 
   if (!containers)
     return ret;
@@ -608,7 +471,13 @@ _group (GList * containers)
       return NULL;
     }
 
-    ges_container_add (ret, tmp->data);
+    if (!ges_container_add (ret, tmp->data)) {
+      GST_INFO ("%" GES_FORMAT " could not add child %p while"
+          " grouping", GES_ARGS (ret), tmp->data);
+      g_object_unref (ret);
+
+      return NULL;
+    }
   }
 
   /* No need to add to the timeline here, this will be done in _child_added */
@@ -616,27 +485,6 @@ _group (GList * containers)
   return ret;
 }
 
-static GESTimelineElement *
-_paste (GESTimelineElement * element, GESTimelineElement * ref,
-    GstClockTime paste_position)
-{
-  GESTimelineElement *ngroup =
-      GES_TIMELINE_ELEMENT_CLASS (parent_class)->paste (element, ref,
-      paste_position);
-
-  if (ngroup) {
-    if (GES_CONTAINER_CHILDREN (ngroup)) {
-      timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN
-              (ngroup)->data), GES_GROUP (element));
-      timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE
-          (GES_CONTAINER_CHILDREN (ngroup)->data), GES_GROUP (element));
-    }
-  }
-
-  return ngroup;
-}
-
-
 /****************************************************
  *                                                  *
  *    GObject virtual methods implementation        *
@@ -697,6 +545,16 @@ ges_group_set_property (GObject * object, guint property_id,
 }
 
 static void
+ges_group_dispose (GObject * object)
+{
+  GESGroup *self = GES_GROUP (object);
+
+  g_clear_pointer (&self->priv->child_sigids, g_hash_table_unref);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
 ges_group_class_init (GESGroupClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -705,13 +563,13 @@ ges_group_class_init (GESGroupClass * klass)
 
   object_class->get_property = ges_group_get_property;
   object_class->set_property = ges_group_set_property;
+  object_class->dispose = ges_group_dispose;
 
-  element_class->trim = _trim;
   element_class->set_duration = _set_duration;
   element_class->set_inpoint = _set_inpoint;
+  element_class->set_max_duration = _set_max_duration;
   element_class->set_start = _set_start;
   element_class->set_priority = _set_priority;
-  element_class->paste = _paste;
 
   /* We override start, inpoint, duration and max-duration from GESTimelineElement
    * in order to makes sure those fields are not serialized.
@@ -719,7 +577,9 @@ ges_group_class_init (GESGroupClass * klass)
   /**
    * GESGroup:start:
    *
-   * The position of the object in its container (in nanoseconds).
+   * An overwrite of the #GESTimelineElement:start property. For a
+   * #GESGroup, this is the earliest #GESTimelineElement:start time
+   * amongst its children.
    */
   properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
       "The position in the container", 0, G_MAXUINT64, 0,
@@ -728,11 +588,8 @@ ges_group_class_init (GESGroupClass * klass)
   /**
    * GESGroup:in-point:
    *
-   * The in-point at which this #GESGroup will start outputting data
-   * from its contents (in nanoseconds).
-   *
-   * Ex : an in-point of 5 seconds means that the first outputted buffer will
-   * be the one located 5 seconds in the controlled resource.
+   * An overwrite of the #GESTimelineElement:in-point property. This has
+   * no meaning for a group and should not be set.
    */
   properties[PROP_INPOINT] =
       g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0,
@@ -741,7 +598,11 @@ ges_group_class_init (GESGroupClass * klass)
   /**
    * GESGroup:duration:
    *
-   * The duration (in nanoseconds) which will be used in the container
+   * An overwrite of the #GESTimelineElement:duration property. For a
+   * #GESGroup, this is the difference between the earliest
+   * #GESTimelineElement:start time and the latest end time (given by
+   * #GESTimelineElement:start + #GESTimelineElement:duration) amongst
+   * its children.
    */
   properties[PROP_DURATION] =
       g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
@@ -751,7 +612,8 @@ ges_group_class_init (GESGroupClass * klass)
   /**
    * GESGroup:max-duration:
    *
-   * The maximum duration (in nanoseconds) of the #GESGroup.
+   * An overwrite of the #GESTimelineElement:max-duration property. This
+   * has no meaning for a group and should not be set.
    */
   properties[PROP_MAX_DURATION] =
       g_param_spec_uint64 ("max-duration", "Maximum duration",
@@ -759,9 +621,11 @@ ges_group_class_init (GESGroupClass * klass)
       G_PARAM_READWRITE | G_PARAM_CONSTRUCT | GES_PARAM_NO_SERIALIZATION);
 
   /**
-   * GESTGroup:priority:
+   * GESGroup:priority:
    *
-   * The priority of the object.
+   * An overwrite of the #GESTimelineElement:priority property.
+   * Setting #GESTimelineElement priorities is deprecated as all priority
+   * management is now done by GES itself.
    */
   properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
       "The priority of the object", 0, G_MAXUINT, 0,
@@ -778,11 +642,20 @@ ges_group_class_init (GESGroupClass * klass)
 }
 
 static void
+_free_sigid (gpointer mem)
+{
+  ChildSignalIds *sigids = mem;
+  gst_clear_object (&sigids->layer);
+  g_free (mem);
+}
+
+static void
 ges_group_init (GESGroup * self)
 {
   self->priv = ges_group_get_instance_private (self);
 
-  self->priv->setting_value = FALSE;
+  self->priv->child_sigids =
+      g_hash_table_new_full (NULL, NULL, NULL, _free_sigid);
 }
 
 /****************************************************
@@ -794,14 +667,20 @@ ges_group_init (GESGroup * self)
 /**
  * ges_group_new:
  *
- * Created a new empty #GESGroup, if you want to group several container
- * together, it is recommanded to use the #ges_container_group method so the
- * proper subclass is selected.
+ * Created a new empty group. You may wish to use
+ * ges_container_group() instead, which can return a different
+ * #GESContainer subclass if possible.
  *
  * Returns: (transfer floating): The new empty group.
  */
 GESGroup *
 ges_group_new (void)
 {
-  return g_object_new (GES_TYPE_GROUP, NULL);
+  GESGroup *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_GROUP, NULL, NULL);
+
+  res = GES_GROUP (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index 5fef392..7268780 100644 (file)
@@ -17,8 +17,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef GES_GROUP_H
-#define GES_GROUP_H
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_GROUP (ges_group_get_type ())
-#define GES_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_GROUP, GESGroup))
-#define GES_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_GROUP, GESGroupClass))
-#define GES_IS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_GROUP))
-#define GES_IS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_GROUP))
-#define GES_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_GROUP, GESGroupClass))
-
-typedef struct _GESGroupPrivate GESGroupPrivate;
+GES_DECLARE_TYPE(Group, group, GROUP);
 
 struct _GESGroup {
   GESContainer parent;
@@ -52,9 +45,6 @@ struct _GESGroupClass {
 };
 
 GES_API
-GType ges_group_get_type          (void);
-GES_API
 GESGroup *ges_group_new           (void);
 
 G_END_DECLS
-#endif /* _GES_GROUP_H */
index b4c3eb4..04f003c 100644 (file)
  * @short_description: outputs the video stream from a media file as a still
  * image.
  *
- * Outputs the video stream from a given file as a still frame. The frame
- * chosen will be determined by the in-point property on the track element. For
- * image files, do not set the in-point property.
+ * Outputs the video stream from a given file as a still frame. The frame chosen
+ * will be determined by the in-point property on the track element. For image
+ * files, do not set the in-point property.
+ *
+ * Deprecated: 1.18: This won't be used anymore and has been replaced by
+ * #GESUriSource instead which now plugs an `imagefreeze` element when
+ * #ges_uri_source_asset_is_image returns %TRUE.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -93,7 +97,7 @@ ges_image_source_dispose (GObject * object)
 }
 
 static void
-pad_added_cb (GstElement * timeline, GstPad * pad, GstElement * scale)
+pad_added_cb (GstElement * source, GstPad * pad, GstElement * scale)
 {
   GstPad *sinkpad;
   GstPadLinkReturn ret;
@@ -114,20 +118,20 @@ pad_added_cb (GstElement * timeline, GstPad * pad, GstElement * scale)
 }
 
 static GstElement *
-ges_image_source_create_source (GESTrackElement * track_element)
+ges_image_source_create_source (GESSource * source)
 {
-  GstElement *bin, *source, *scale, *freeze, *iconv;
-  GstPad *src, *target;
+  GstElement *bin, *src, *scale, *freeze, *iconv;
+  GstPad *srcpad, *target;
 
   bin = GST_ELEMENT (gst_bin_new ("still-image-bin"));
-  source = gst_element_factory_make ("uridecodebin", NULL);
+  src = gst_element_factory_make ("uridecodebin", NULL);
   scale = gst_element_factory_make ("videoscale", NULL);
   freeze = gst_element_factory_make ("imagefreeze", NULL);
   iconv = gst_element_factory_make ("videoconvert", NULL);
 
   g_object_set (scale, "add-borders", TRUE, NULL);
 
-  gst_bin_add_many (GST_BIN (bin), source, scale, freeze, iconv, NULL);
+  gst_bin_add_many (GST_BIN (bin), src, scale, freeze, iconv, NULL);
 
   gst_element_link_pads_full (scale, "src", iconv, "sink",
       GST_PAD_LINK_CHECK_NOTHING);
@@ -138,13 +142,13 @@ ges_image_source_create_source (GESTrackElement * track_element)
 
   target = gst_element_get_static_pad (freeze, "src");
 
-  src = gst_ghost_pad_new ("src", target);
-  gst_element_add_pad (bin, src);
+  srcpad = gst_ghost_pad_new ("src", target);
+  gst_element_add_pad (bin, srcpad);
   gst_object_unref (target);
 
-  g_object_set (source, "uri", ((GESImageSource *) track_element)->uri, NULL);
+  g_object_set (src, "uri", ((GESImageSource *) source)->uri, NULL);
 
-  g_signal_connect (G_OBJECT (source), "pad-added",
+  g_signal_connect (G_OBJECT (src), "pad-added",
       G_CALLBACK (pad_added_cb), scale);
 
   return bin;
@@ -154,7 +158,8 @@ static void
 ges_image_source_class_init (GESImageSourceClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESVideoSourceClass *source_class = GES_VIDEO_SOURCE_CLASS (klass);
+  GESSourceClass *source_class = GES_SOURCE_CLASS (klass);
+  GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_CLASS (klass);
 
   object_class->get_property = ges_image_source_get_property;
   object_class->set_property = ges_image_source_set_property;
@@ -169,8 +174,11 @@ ges_image_source_class_init (GESImageSourceClass * klass)
       g_param_spec_string ("uri", "URI", "uri of the resource",
           NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
-  GES_TIMELINE_ELEMENT_CLASS (klass)->set_inpoint = NULL;
   source_class->create_source = ges_image_source_create_source;
+  vsource_class->ABI.abi.get_natural_size =
+      ges_video_uri_source_get_natural_size;
+
+  GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE (klass) = FALSE;
 }
 
 static void
@@ -179,9 +187,7 @@ ges_image_source_init (GESImageSource * self)
   self->priv = ges_image_source_get_instance_private (self);
 }
 
-/**
- * ges_image_source_new:
- * @uri: the URI the source should control
+/* @uri: the URI the source should control
  *
  * Creates a new #GESImageSource for the provided @uri.
  *
@@ -190,6 +196,12 @@ ges_image_source_init (GESImageSource * self)
 GESImageSource *
 ges_image_source_new (gchar * uri)
 {
-  return g_object_new (GES_TYPE_IMAGE_SOURCE, "uri", uri, "track-type",
-      GES_TRACK_TYPE_VIDEO, NULL);
+  GESImageSource *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_IMAGE_SOURCE, uri, NULL);
+
+  res = GES_IMAGE_SOURCE (ges_asset_extract (asset, NULL));
+  res->uri = g_strdup (uri);
+  gst_object_unref (asset);
+
+  return res;
 }
index dc1a00e..9d05727 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_IMAGE_SOURCE
-#define _GES_IMAGE_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_IMAGE_SOURCE ges_image_source_get_type()
-
-#define GES_IMAGE_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_IMAGE_SOURCE, GESImageSource))
-
-#define GES_IMAGE_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_IMAGE_SOURCE, GESImageSourceClass))
-
-#define GES_IS_IMAGE_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_IMAGE_SOURCE))
-
-#define GES_IS_IMAGE_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_IMAGE_SOURCE))
-
-#define GES_IMAGE_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_IMAGE_SOURCE, GESImageSourceClass))
-
-typedef struct _GESImageSourcePrivate GESImageSourcePrivate;
+GES_DECLARE_TYPE(ImageSource, image_source, IMAGE_SOURCE);
 
 /**
  * GESImageSource:
@@ -68,10 +51,4 @@ struct _GESImageSourceClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_image_source_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_IMAGE_SOURCE */
-
index cc99998..66b2ba8 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GES_INTERNAL_H__
-#define __GES_INTERNAL_H__
+#pragma once
+
 #include <gst/gst.h>
 #include <gst/pbutils/encoding-profile.h>
 #include <gio/gio.h>
 
+G_BEGIN_DECLS
+
+#ifndef GST_CAT_DEFAULT
+#define GST_CAT_DEFAULT (_ges_debug ())
+#endif
+
 #include "ges-timeline.h"
 #include "ges-track-element.h"
 #include "ges-timeline-element.h"
 #include "ges-base-xml-formatter.h"
 #include "ges-timeline-tree.h"
 
-G_BEGIN_DECLS
-
-#ifndef GST_CAT_DEFAULT
-#define GST_CAT_DEFAULT (_ges_debug ())
-#endif
-
 G_GNUC_INTERNAL
 GstDebugCategory * _ges_debug (void);
 
@@ -61,6 +61,15 @@ GstDebugCategory * _ges_debug (void);
 #define _set_duration0 ges_timeline_element_set_duration
 #define _set_priority0 ges_timeline_element_set_priority
 
+#define GES_CLOCK_TIME_IS_LESS(first, second) \
+  (GST_CLOCK_TIME_IS_VALID (first) && (!GST_CLOCK_TIME_IS_VALID (second) \
+  || (first) < (second)))
+
+#define DEFAULT_FRAMERATE_N 30
+#define DEFAULT_FRAMERATE_D 1
+#define DEFAULT_WIDTH 1280
+#define DEFAULT_HEIGHT 720
+
 #define GES_TIMELINE_ELEMENT_FORMAT \
     "s<%p>" \
     " [ %" GST_TIME_FORMAT \
@@ -78,37 +87,56 @@ GstDebugCategory * _ges_debug (void);
 #define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
 #define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
 
-G_GNUC_INTERNAL gboolean
-timeline_ripple_object         (GESTimeline *timeline, GESTimelineElement *obj,
-                                gint new_layer_priority,
-                                GList * layers, GESEdge edge,
-                                guint64 position);
+#define GES_IS_TIME_EFFECT(element) \
+  (GES_IS_BASE_EFFECT (element) \
+  && ges_base_effect_is_time_effect (GES_BASE_EFFECT (element)))
 
-G_GNUC_INTERNAL gboolean
-timeline_slide_object          (GESTimeline *timeline, GESTrackElement *obj,
-                                    GList * layers, GESEdge edge, guint64 position);
+#define GES_TIMELINE_ELEMENT_SET_BEING_EDITED(element) \
+  ELEMENT_SET_FLAG ( \
+      ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \
+      GES_TIMELINE_ELEMENT_SET_SIMPLE)
 
-G_GNUC_INTERNAL gboolean
-timeline_roll_object           (GESTimeline *timeline, GESTimelineElement *obj,
-                                GList * layers, GESEdge edge, guint64 position);
+#define GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED(element) \
+  ELEMENT_UNSET_FLAG ( \
+      ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \
+      GES_TIMELINE_ELEMENT_SET_SIMPLE)
+
+#define GES_TIMELINE_ELEMENT_BEING_EDITED(element) \
+  ELEMENT_FLAG_IS_SET ( \
+      ges_timeline_element_peak_toplevel (GES_TIMELINE_ELEMENT (element)), \
+      GES_TIMELINE_ELEMENT_SET_SIMPLE)
+
+/************************
+ * Our property masks   *
+ ************************/
+#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1))
+
+#define SUPRESS_UNUSED_WARNING(a) (void)a
+
+G_GNUC_INTERNAL void
+ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze);
+
+G_GNUC_INTERNAL GESAutoTransition *
+ges_timeline_get_auto_transition_at_edge (GESTimeline * timeline, GESTrackElement * source,
+  GESEdge edge);
+
+G_GNUC_INTERNAL gboolean ges_timeline_is_disposed (GESTimeline* timeline);
 
 G_GNUC_INTERNAL gboolean
-timeline_trim_object           (GESTimeline *timeline, GESTimelineElement * object,
-                                guint32 new_layer_priority, GList * layers, GESEdge edge,
-                                guint64 position);
-G_GNUC_INTERNAL gboolean
-ges_timeline_trim_object_simple (GESTimeline * timeline, GESTimelineElement * obj,
-                                 guint32 new_layer_priority, GList * layers, GESEdge edge,
-                                 guint64 position, gboolean snapping);
+ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
+    gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
+    guint64 position, GError ** error);
 
 G_GNUC_INTERNAL gboolean
-ges_timeline_move_object_simple (GESTimeline * timeline, GESTimelineElement * object,
-                                 GList * layers, GESEdge edge, guint64 position);
+ges_timeline_layer_priority_in_gap (GESTimeline * timeline, guint layer_priority);
 
+G_GNUC_INTERNAL void
+ges_timeline_set_track_selection_error  (GESTimeline * timeline,
+                                         gboolean was_error,
+                                         GError * error);
 G_GNUC_INTERNAL gboolean
-timeline_move_object           (GESTimeline *timeline, GESTimelineElement * object,
-                                guint32 new_layer_priority, GList * layers, GESEdge edge,
-                                guint64 position);
+ges_timeline_take_track_selection_error (GESTimeline * timeline,
+                                         GError ** error);
 
 G_GNUC_INTERNAL void
 timeline_add_group             (GESTimeline *timeline,
@@ -138,15 +166,33 @@ timeline_get_tree           (GESTimeline *timeline);
 
 G_GNUC_INTERNAL
 void
-timeline_update_transition (GESTimeline *timeline);
-
-G_GNUC_INTERNAL
-void
 timeline_fill_gaps            (GESTimeline *timeline);
 
 G_GNUC_INTERNAL void
 timeline_create_transitions (GESTimeline * timeline, GESTrackElement * track_element);
 
+G_GNUC_INTERNAL void timeline_get_framerate(GESTimeline *self, gint *fps_n,
+                                            gint *fps_d);
+G_GNUC_INTERNAL void
+ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving);
+
+G_GNUC_INTERNAL gboolean
+ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error);
+
+G_GNUC_INTERNAL void
+ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip);
+
+G_GNUC_INTERNAL void
+ges_timeline_set_smart_rendering (GESTimeline * timeline, gboolean rendering_smartly);
+
+G_GNUC_INTERNAL gboolean
+ges_timeline_get_smart_rendering (GESTimeline *timeline);
+
+G_GNUC_INTERNAL void
+ges_auto_transition_set_source (GESAutoTransition * self, GESTrackElement * source, GESEdge edge);
+
+
+
 G_GNUC_INTERNAL
 void
 track_resort_and_fill_gaps    (GESTrack *track);
@@ -170,6 +216,8 @@ ges_asset_cache_put (GESAsset * asset, GTask *task);
 G_GNUC_INTERNAL gboolean
 ges_asset_cache_set_loaded(GType extractable_type, const gchar * id, GError *error);
 
+/* FIXME: marked as GES_API just so they can be used in tests! */
+
 GES_API GESAsset*
 ges_asset_cache_lookup(GType extractable_type, const gchar * id);
 
@@ -177,12 +225,15 @@ GES_API gboolean
 ges_asset_try_proxy (GESAsset *asset, const gchar *new_id);
 
 G_GNUC_INTERNAL gboolean
+ges_asset_finish_proxy (GESAsset * proxy);
+
+G_GNUC_INTERNAL gboolean
 ges_asset_request_id_update (GESAsset *asset, gchar **proposed_id,
     GError *error);
 G_GNUC_INTERNAL gchar *
-ges_effect_assect_id_get_type_and_bindesc (const char    *id,
-                                           GESTrackType  *track_type,
-                                           GError       **error);
+ges_effect_asset_id_get_type_and_bindesc (const char    *id,
+                                          GESTrackType  *track_type,
+                                          GError       **error);
 
 G_GNUC_INTERNAL void _ges_uri_asset_cleanup (void);
 
@@ -198,9 +249,12 @@ ges_extractable_type_get_asset_type              (GType type);
 G_GNUC_INTERNAL gchar *
 ges_extractable_type_check_id                    (GType type, const gchar *id, GError **error);
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
 G_GNUC_INTERNAL GParameter *
 ges_extractable_type_get_parameters_from_id      (GType type, const gchar *id,
                                                   guint *n_params);
+G_GNUC_END_IGNORE_DEPRECATIONS;
+
 G_GNUC_INTERNAL GType
 ges_extractable_get_real_extractable_type_for_id (GType type, const gchar * id);
 
@@ -231,7 +285,8 @@ _find_formatter_asset_for_id                     (const gchar *id);
 /* FIXME This should probably become public, but we need to make sure it
  * is the right API before doing so */
 G_GNUC_INTERNAL  gboolean ges_project_set_loaded                  (GESProject * project,
-                                                                   GESFormatter *formatter);
+                                                                   GESFormatter *formatter,
+                                                                   GError *error);
 G_GNUC_INTERNAL  gchar * ges_project_try_updating_id              (GESProject *self,
                                                                    GESAsset *asset,
                                                                    GError *error);
@@ -272,6 +327,7 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_layer           (GESBaseXmlForma
                                                                  guint priority,
                                                                  GstStructure *properties,
                                                                  const gchar *metadatas,
+                                                                 gchar **deactivated_tracks,
                                                                  GError **error);
 G_GNUC_INTERNAL void ges_base_xml_formatter_add_track           (GESBaseXmlFormatter *self,
                                                                  GESTrackType track_type,
@@ -309,7 +365,9 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_track_element   (GESBaseXmlForma
 
 G_GNUC_INTERNAL void ges_base_xml_formatter_add_source          (GESBaseXmlFormatter *self,
                                                                  const gchar * track_id,
-                                                                 GstStructure *children_properties);
+                                                                 GstStructure *children_properties,
+                                                                 GstStructure *properties,
+                                                                 const gchar *metadatas);
 
 G_GNUC_INTERNAL void ges_base_xml_formatter_add_group           (GESBaseXmlFormatter *self,
                                                                  const gchar *name,
@@ -328,6 +386,15 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_control_binding (GESBaseXmlForma
                                                                   const gchar *track_id,
                                                                   GSList * timed_values);
 
+G_GNUC_INTERNAL void ges_base_xml_formatter_set_timeline_properties(GESBaseXmlFormatter * self,
+                                                                    GESTimeline *timeline,
+                                                                    const gchar *properties,
+                                                                    const gchar *metadatas);
+
+G_GNUC_INTERNAL void ges_base_xml_formatter_end_current_clip       (GESBaseXmlFormatter *self);
+
+G_GNUC_INTERNAL void ges_xml_formatter_deinit                      (void);
+
 G_GNUC_INTERNAL gboolean set_property_foreach                   (GQuark field_id,
                                                                  const GValue * value,
                                                                  GObject * object);
@@ -338,6 +405,7 @@ G_GNUC_INTERNAL GstElement * get_element_for_encoding_profile   (GstEncodingProf
 /* Function to initialise GES */
 G_GNUC_INTERNAL void _init_standard_transition_assets        (void);
 G_GNUC_INTERNAL void _init_formatter_assets                  (void);
+G_GNUC_INTERNAL void _deinit_formatter_assets                (void);
 
 /* Utilities */
 G_GNUC_INTERNAL gint element_start_compare                (GESTimelineElement * a,
@@ -348,24 +416,21 @@ G_GNUC_INTERNAL GstElementFactory *
 ges_get_compositor_factory                                (void);
 
 G_GNUC_INTERNAL void
-ges_base_xml_formatter_set_timeline_properties(GESBaseXmlFormatter * self,
-                                              GESTimeline *timeline,
-                                              const gchar *properties,
-                                              const gchar *metadatas);
+ges_idle_add (GSourceFunc func, gpointer udata, GDestroyNotify notify);
+
+G_GNUC_INTERNAL gboolean
+ges_util_structure_get_clocktime (GstStructure *structure, const gchar *name,
+                                  GstClockTime *val, GESFrameNumber *frames);
+
+G_GNUC_INTERNAL gboolean /* From ges-xml-formatter.c */
+ges_util_can_serialize_spec (GParamSpec * spec);
 
 /****************************************************
  *              GESContainer                        *
  ****************************************************/
 G_GNUC_INTERNAL void _ges_container_sort_children         (GESContainer *container);
-G_GNUC_INTERNAL void _ges_container_sort_children_by_end  (GESContainer *container);
 G_GNUC_INTERNAL void _ges_container_set_height            (GESContainer * container,
                                                            guint32 height);
-G_GNUC_INTERNAL gint  _ges_container_get_priority_offset  (GESContainer * container,
-                                                           GESTimelineElement *elem);
-G_GNUC_INTERNAL void _ges_container_set_priority_offset   (GESContainer * container,
-                                                           GESTimelineElement *elem,
-                                                           gint32 priority_offset);
-
 
 /****************************************************
  *                  GESClip                         *
@@ -375,6 +440,20 @@ G_GNUC_INTERNAL gboolean          ges_clip_is_moving_from_layer   (GESClip *clip
 G_GNUC_INTERNAL void              ges_clip_set_moving_from_layer  (GESClip *clip, gboolean is_moving);
 G_GNUC_INTERNAL GESTrackElement*  ges_clip_create_track_element   (GESClip *clip, GESTrackType type);
 G_GNUC_INTERNAL GList*            ges_clip_create_track_elements  (GESClip *clip, GESTrackType type);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTrackElement * child, GstClockTime inpoint, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_max_duration_of_all_core (GESClip * clip, GstClockTime max_duration, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_max_duration_of_child (GESClip * clip, GESTrackElement * child, GstClockTime max_duration, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_active_of_child (GESClip * clip, GESTrackElement * child, gboolean active, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_priority_of_child (GESClip * clip, GESTrackElement * child, guint32 priority, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack, GError ** error);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_time_property_of_child (GESClip * clip, GESTrackElement * child, GObject * prop_object, GParamSpec * pspec, const GValue * value, GError ** error);
+G_GNUC_INTERNAL GstClockTime      ges_clip_duration_limit_with_new_children_inpoints (GESClip * clip, GHashTable * child_inpoints);
+G_GNUC_INTERNAL GstClockTime      ges_clip_get_core_internal_time_from_timeline_time (GESClip * clip, GstClockTime timeline_time, gboolean * no_core, GError ** error);
+G_GNUC_INTERNAL void              ges_clip_empty_from_track       (GESClip * clip, GESTrack * track);
+G_GNUC_INTERNAL void              ges_clip_set_add_error          (GESClip * clip, GError * error);
+G_GNUC_INTERNAL void              ges_clip_take_add_error         (GESClip * clip, GError ** error);
+G_GNUC_INTERNAL void              ges_clip_set_remove_error       (GESClip * clip, GError * error);
+G_GNUC_INTERNAL void              ges_clip_take_remove_error      (GESClip * clip, GError ** error);
 
 /****************************************************
  *              GESLayer                            *
@@ -386,17 +465,37 @@ G_GNUC_INTERNAL void layer_set_priority               (GESLayer * layer, guint p
  *              GESTrackElement                     *
  ****************************************************/
 #define         NLE_OBJECT_TRACK_ELEMENT_QUARK                  (g_quark_from_string ("nle_object_track_element_quark"))
-G_GNUC_INTERNAL gboolean  ges_track_element_set_track           (GESTrackElement * object, GESTrack * track);
+G_GNUC_INTERNAL gboolean  ges_track_element_set_track           (GESTrackElement * object, GESTrack * track, GError ** error);
 G_GNUC_INTERNAL void ges_track_element_copy_properties          (GESTimelineElement * element,
                                                                  GESTimelineElement * elementcopy);
+G_GNUC_INTERNAL void ges_track_element_set_layer_active         (GESTrackElement *element, gboolean active);
 
 G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element,
-                                                      GESTrackElement *new_element,
-                                                      guint64 position);
+                                                      GESTrackElement *new_element,
+                                                      guint64 position);
+G_GNUC_INTERNAL void ges_track_element_freeze_control_sources   (GESTrackElement * object,
+                                                                 gboolean freeze);
+G_GNUC_INTERNAL void ges_track_element_update_outpoint          (GESTrackElement * self);
 
-G_GNUC_INTERNAL GstElement *ges_source_create_topbin (const gchar * bin_name, GstElement * sub_element, ...);
-G_GNUC_INTERNAL void ges_track_set_caps                (GESTrack *track,
-                                                        const GstCaps *caps);
+G_GNUC_INTERNAL void
+ges_track_element_set_creator_asset                    (GESTrackElement * self,
+                                                       GESAsset *creator_asset);
+G_GNUC_INTERNAL GESAsset *
+ges_track_element_get_creator_asset                    (GESTrackElement * self);
+
+G_GNUC_INTERNAL void
+ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement * element);
+
+G_GNUC_INTERNAL GstElement* ges_source_create_topbin  (GESSource *source,
+                                                       const gchar* bin_name,
+                                                       GstElement* sub_element,
+                                                       GPtrArray* elements);
+G_GNUC_INTERNAL void ges_source_set_rendering_smartly (GESSource *source,
+                                                       gboolean rendering_smartly);
+G_GNUC_INTERNAL gboolean
+ges_source_get_rendering_smartly                      (GESSource *source);
+
+G_GNUC_INTERNAL void ges_track_set_smart_rendering     (GESTrack* track, gboolean rendering_smartly);
 G_GNUC_INTERNAL GstElement * ges_track_get_composition (GESTrack *track);
 
 
@@ -411,6 +510,28 @@ G_GNUC_INTERNAL GESTitleSource     * ges_title_source_new      (void);
 G_GNUC_INTERNAL GESVideoTestSource * ges_video_test_source_new (void);
 
 /****************************************************
+ *                GES*Effect                     *
+ ****************************************************/
+G_GNUC_INTERNAL gchar *
+ges_base_effect_get_time_property_name        (GESBaseEffect * effect,
+                                               GObject * child,
+                                               GParamSpec * pspec);
+G_GNUC_INTERNAL GHashTable *
+ges_base_effect_get_time_property_values      (GESBaseEffect * effect);
+G_GNUC_INTERNAL GstClockTime
+ges_base_effect_translate_source_to_sink_time (GESBaseEffect * effect,
+                                               GstClockTime time,
+                                               GHashTable * time_property_values);
+G_GNUC_INTERNAL GstClockTime
+ges_base_effect_translate_sink_to_source_time (GESBaseEffect * effect,
+                                               GstClockTime time,
+                                               GHashTable * time_property_values);
+G_GNUC_INTERNAL GstElement *
+ges_effect_from_description                   (const gchar *bin_desc,
+                                               GESTrackType type,
+                                               GError **error);
+
+/****************************************************
  *              GESTimelineElement                  *
  ****************************************************/
 typedef enum
@@ -419,10 +540,19 @@ typedef enum
   GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 1),
 } GESTimelineElementFlags;
 
-G_GNUC_INTERNAL gdouble ges_timeline_element_get_media_duration_factor(GESTimelineElement *self);
+G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_peak_toplevel (GESTimelineElement * self);
 G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_get_copied_from (GESTimelineElement *self);
 G_GNUC_INTERNAL GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement *self);
 G_GNUC_INTERNAL void                ges_timeline_element_set_flags (GESTimelineElement *self, GESTimelineElementFlags flags);
+G_GNUC_INTERNAL gboolean            ges_timeline_element_add_child_property_full (GESTimelineElement *self,
+                                                                                  GESTimelineElement *owner,
+                                                                                  GParamSpec *pspec,
+                                                                                  GObject *child);
+
+G_GNUC_INTERNAL GObject *           ges_timeline_element_get_child_from_child_property (GESTimelineElement * self,
+                                                                                        GParamSpec * pspec);
+G_GNUC_INTERNAL GParamSpec **       ges_timeline_element_get_children_properties (GESTimelineElement * self,
+                                                                                  guint * n_properties);
 
 #define ELEMENT_FLAGS(obj)             (ges_timeline_element_flags (GES_TIMELINE_ELEMENT(obj)))
 #define ELEMENT_SET_FLAG(obj,flag)     (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) | (flag))))
@@ -441,10 +571,28 @@ typedef struct GESMultiFileURI
 
 G_GNUC_INTERNAL GESMultiFileURI * ges_multi_file_uri_new (const gchar * uri);
 
-/************************
- * Our property masks   *
- ************************/
-#define GES_PARAM_NO_SERIALIZATION (1 << (G_PARAM_USER_SHIFT + 1))
+/******************************
+ *  GESUriSource internal API *
+ ******************************/
+G_GNUC_INTERNAL gboolean
+ges_video_uri_source_get_natural_size(GESVideoSource* source, gint* width, gint* height);
+
+/**********************************
+ *  GESTestClipAsset internal API *
+ **********************************/
+G_GNUC_INTERNAL gboolean ges_test_clip_asset_get_natural_size (GESAsset *self,
+                                                               gint *width,
+                                                               gint *height);
+G_GNUC_INTERNAL gchar *ges_test_source_asset_check_id         (GType type, const gchar *id,
+                                                               GError **error);
+
+/*******************************
+ *        GESMarkerList        *
+ *******************************/
+
+G_GNUC_INTERNAL GESMarker * ges_marker_list_get_closest (GESMarkerList *list, GstClockTime position);
+G_GNUC_INTERNAL gchar * ges_marker_list_serialize (const GValue * v);
+G_GNUC_INTERNAL gboolean ges_marker_list_deserialize (GValue *dest, const gchar *s);
 
 /********************
  *  Gnonlin helpers *
@@ -455,5 +603,3 @@ G_GNUC_INTERNAL gboolean ges_nle_composition_remove_object (GstElement *comp, Gs
 G_GNUC_INTERNAL gboolean ges_nle_object_commit (GstElement * nlesource, gboolean recurse);
 
 G_END_DECLS
-
-#endif /* __GES_INTERNAL_H__ */
index 248c1c0..5a0372a 100644 (file)
 /**
  * SECTION:geslayer
  * @title: GESLayer
- * @short_description: Non-overlapping sequence of GESClip
+ * @short_description: Non-overlapping sequence of #GESClip
  *
- * Responsible for the ordering of the various contained Clip(s). A
- * timeline layer has a "priority" property, which is used to manage the
- * priorities of individual Clips. Two layers should not have the
- * same priority within a given timeline.
+ * #GESLayer-s are responsible for collecting and ordering #GESClip-s.
+ *
+ * A layer within a timeline will have an associated priority,
+ * corresponding to their index within the timeline. A layer with the
+ * index/priority 0 will have the highest priority and the layer with the
+ * largest index will have the lowest priority (the order of priorities,
+ * in this sense, is the _reverse_ of the numerical ordering of the
+ * indices). ges_timeline_move_layer() should be used if you wish to
+ * change how layers are prioritised in a timeline.
+ *
+ * Layers with higher priorities will have their content priorities
+ * over content from lower priority layers, similar to how layers are
+ * used in image editing. For example, if two separate layers both
+ * display video content, then the layer with the higher priority will
+ * have its images shown first. The other layer will only have its image
+ * shown if the higher priority layer has no content at the given
+ * playtime, or is transparent in some way. Audio content in separate
+ * layers will simply play in addition.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -51,6 +65,8 @@ struct _GESLayerPrivate
   guint32 priority;             /* The priority of the layer within the
                                  * containing timeline */
   gboolean auto_transition;
+
+  GHashTable *tracks_activness;
 };
 
 typedef struct
@@ -59,6 +75,43 @@ typedef struct
   GESLayer *layer;
 } NewAssetUData;
 
+typedef struct
+{
+  GESTrack *track;
+  GESLayer *layer;
+  gboolean active;
+  gboolean track_disposed;
+} LayerActivnessData;
+
+static void
+_track_disposed_cb (LayerActivnessData * data, GObject * disposed_track)
+{
+  data->track_disposed = TRUE;
+  g_hash_table_remove (data->layer->priv->tracks_activness, data->track);
+}
+
+static void
+layer_activness_data_free (LayerActivnessData * data)
+{
+  if (!data->track_disposed)
+    g_object_weak_unref ((GObject *) data->track,
+        (GWeakNotify) _track_disposed_cb, data);
+  g_free (data);
+}
+
+static LayerActivnessData *
+layer_activness_data_new (GESTrack * track, GESLayer * layer, gboolean active)
+{
+  LayerActivnessData *data = g_new0 (LayerActivnessData, 1);
+
+  data->layer = layer;
+  data->track = track;
+  data->active = active;
+  g_object_weak_ref (G_OBJECT (track), (GWeakNotify) _track_disposed_cb, data);
+
+  return data;
+}
+
 enum
 {
   PROP_0,
@@ -71,14 +124,15 @@ enum
 {
   OBJECT_ADDED,
   OBJECT_REMOVED,
+  ACTIVE_CHANGED,
   LAST_SIGNAL
 };
 
 static guint ges_layer_signals[LAST_SIGNAL] = { 0 };
 
 G_DEFINE_TYPE_WITH_CODE (GESLayer, ges_layer,
-    G_TYPE_INITIALLY_UNOWNED, G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, NULL)
-    G_ADD_PRIVATE (GESLayer)
+    G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESLayer)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, NULL)
     G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER,
         ges_meta_container_interface_init));
 
@@ -131,6 +185,8 @@ ges_layer_dispose (GObject * object)
   while (priv->clips_start)
     ges_layer_remove_clip (layer, (GESClip *) priv->clips_start->data);
 
+  g_clear_pointer (&layer->priv->tracks_activness, g_hash_table_unref);
+
   G_OBJECT_CLASS (ges_layer_parent_class)->dispose (object);
 }
 
@@ -162,11 +218,11 @@ ges_layer_class_init (GESLayerClass * klass)
    * GESLayer:priority:
    *
    * The priority of the layer in the #GESTimeline. 0 is the highest
-   * priority. Conceptually, a #GESTimeline is a stack of GESLayers,
+   * priority. Conceptually, a timeline is a stack of layers,
    * and the priority of the layer represents its position in the stack. Two
    * layers should not have the same priority within a given GESTimeline.
    *
-   * Note that the timeline needs to be commited (with #ges_timeline_commit)
+   * Note that the timeline needs to be committed (with #ges_timeline_commit)
    * for the change to be taken into account.
    *
    * Deprecated:1.16.0: use #ges_timeline_move_layer instead. This deprecation means
@@ -180,7 +236,13 @@ ges_layer_class_init (GESLayerClass * klass)
   /**
    * GESLayer:auto-transition:
    *
-   * Sets whether transitions are added automagically when clips overlap.
+   * Whether to automatically create a #GESTransitionClip whenever two
+   * #GESSource-s that both belong to a #GESClip in the layer overlap.
+   * See #GESTimeline for what counts as an overlap.
+   *
+   * When a layer is added to a #GESTimeline, if this property is left as
+   * %FALSE, but the timeline's #GESTimeline:auto-transition is %TRUE, it
+   * will be set to %TRUE as well.
    */
   g_object_class_install_property (object_class, PROP_AUTO_TRANSITION,
       g_param_spec_boolean ("auto-transition", "Auto-Transition",
@@ -188,28 +250,44 @@ ges_layer_class_init (GESLayerClass * klass)
 
   /**
    * GESLayer::clip-added:
-   * @layer: the #GESLayer
-   * @clip: the #GESClip that was added.
+   * @layer: The #GESLayer
+   * @clip: The clip that was added
    *
-   * Will be emitted after the clip was added to the layer.
+   * Will be emitted after the clip is added to the layer.
    */
   ges_layer_signals[OBJECT_ADDED] =
       g_signal_new ("clip-added", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESLayerClass, object_added),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_CLIP);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_CLIP);
 
   /**
    * GESLayer::clip-removed:
-   * @layer: the #GESLayer
-   * @clip: the #GESClip that was removed
+   * @layer: The #GESLayer
+   * @clip: The clip that was removed
    *
-   * Will be emitted after the clip was removed from the layer.
+   * Will be emitted after the clip is removed from the layer.
    */
   ges_layer_signals[OBJECT_REMOVED] =
       g_signal_new ("clip-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESLayerClass,
-          object_removed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
-      1, GES_TYPE_CLIP);
+          object_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_CLIP);
+
+  /**
+   * GESLayer::active-changed:
+   * @layer: The #GESLayer
+   * @active: Whether @layer has been made active or de-active in the @tracks
+   * @tracks: (element-type GESTrack) (transfer none): A list of #GESTrack
+   * which have been activated or deactivated
+   *
+   * Will be emitted whenever the layer is activated or deactivated
+   * for some #GESTrack. See ges_layer_set_active_for_tracks().
+   *
+   * Since: 1.18
+   */
+  ges_layer_signals[ACTIVE_CHANGED] =
+      g_signal_new ("active-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
+      G_TYPE_BOOLEAN, G_TYPE_PTR_ARRAY);
 }
 
 static void
@@ -222,6 +300,10 @@ ges_layer_init (GESLayer * self)
   self->min_nle_priority = MIN_NLE_PRIO;
   self->max_nle_priority = LAYER_HEIGHT + MIN_NLE_PRIO;
 
+  self->priv->tracks_activness =
+      g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, (GDestroyNotify) layer_activness_data_free);
+
   _register_metas (self);
 }
 
@@ -268,7 +350,7 @@ ges_layer_resync_priorities_by_type (GESLayer * layer,
 
 /**
  * ges_layer_resync_priorities:
- * @layer: a #GESLayer
+ * @layer: The #GESLayer
  *
  * Resyncs the priorities of the clips controlled by @layer.
  */
@@ -357,12 +439,13 @@ new_asset_cb (GESAsset * source, GAsyncResult * res, NewAssetUData * udata)
 
 /**
  * ges_layer_get_duration:
- * @layer: The #GESLayer to get the duration from
+ * @layer: The layer to get the duration from
  *
- * Lets you retrieve the duration of the layer, which means
- * the end time of the last clip inside it
+ * Retrieves the duration of the layer, which is the difference
+ * between the start of the layer (always time 0) and the end (which will
+ * be the end time of the final clip).
  *
- * Returns: The duration of a layer
+ * Returns: The duration of @layer.
  */
 GstClockTime
 ges_layer_get_duration (GESLayer * layer)
@@ -384,6 +467,8 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
     gboolean emit_removed)
 {
   GESLayer *current_layer;
+  GList *tmp;
+  GESTimeline *timeline = layer->timeline;
 
   GST_DEBUG ("layer:%p, clip:%p", layer, clip);
 
@@ -409,8 +494,11 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
   /* inform the clip it's no longer in a layer */
   ges_clip_set_layer (clip, NULL);
   /* so neither in a timeline */
-  if (layer->timeline)
-    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL);
+  if (timeline)
+    ges_timeline_remove_clip (timeline, clip);
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    ges_track_element_set_layer_active (tmp->data, TRUE);
 
   /* Remove our reference to the clip */
   gst_object_unref (clip);
@@ -421,16 +509,13 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
 /* Public methods */
 /**
  * ges_layer_remove_clip:
- * @layer: a #GESLayer
- * @clip: the #GESClip to remove
+ * @layer: The #GESLayer
+ * @clip: The clip to remove
  *
- * Removes the given @clip from the @layer and unparents it.
- * Unparenting it means the reference owned by @layer on the @clip will be
- * removed. If you wish to use the @clip after this function, make sure you
- * call gst_object_ref() before removing it from the @layer.
+ * Removes the given clip from the layer.
  *
- * Returns: %TRUE if the clip could be removed, %FALSE if the layer does
- * not want to remove the clip.
+ * Returns: %TRUE if @clip was removed from @layer, or %FALSE if the
+ * operation failed.
  */
 gboolean
 ges_layer_remove_clip (GESLayer * layer, GESClip * clip)
@@ -443,11 +528,10 @@ ges_layer_remove_clip (GESLayer * layer, GESClip * clip)
 
 /**
  * ges_layer_set_priority:
- * @layer: a #GESLayer
- * @priority: the priority to set
+ * @layer: The #GESLayer
+ * @priority: The priority to set
  *
- * Sets the layer to the given @priority. See the documentation of the
- * priority property for more information.
+ * Sets the layer to the given priority. See #GESLayer:priority.
  *
  * Deprecated:1.16.0: use #ges_timeline_move_layer instead. This deprecation means
  * that you will not need to handle layer priorities at all yourself, GES
@@ -465,12 +549,11 @@ ges_layer_set_priority (GESLayer * layer, guint priority)
 
 /**
  * ges_layer_get_auto_transition:
- * @layer: a #GESLayer
+ * @layer: The #GESLayer
  *
- * Gets whether transitions are automatically added when objects
- * overlap or not.
+ * Gets the #GESLayer:auto-transition of the layer.
  *
- * Returns: %TRUE if transitions are automatically added, else %FALSE.
+ * Returns: %TRUE if transitions are automatically added to @layer.
  */
 gboolean
 ges_layer_get_auto_transition (GESLayer * layer)
@@ -482,11 +565,16 @@ ges_layer_get_auto_transition (GESLayer * layer)
 
 /**
  * ges_layer_set_auto_transition:
- * @layer: a #GESLayer
- * @auto_transition: whether the auto_transition is active
+ * @layer: The #GESLayer
+ * @auto_transition: Whether transitions should be automatically added to
+ * the layer
  *
- * Sets the layer to the given @auto_transition. See the documentation of the
- * property auto_transition for more information.
+ * Sets #GESLayer:auto-transition for the layer. Use
+ * ges_timeline_set_auto_transition() if you want all layers within a
+ * #GESTimeline to have #GESLayer:auto-transition set to %TRUE. Use this
+ * method if you want different values for different layers (and make sure
+ * to keep #GESTimeline:auto-transition as %FALSE for the corresponding
+ * timeline).
  */
 void
 ges_layer_set_auto_transition (GESLayer * layer, gboolean auto_transition)
@@ -494,17 +582,21 @@ ges_layer_set_auto_transition (GESLayer * layer, gboolean auto_transition)
 
   g_return_if_fail (GES_IS_LAYER (layer));
 
+  if (layer->priv->auto_transition == auto_transition)
+    return;
+
   layer->priv->auto_transition = auto_transition;
   g_object_notify (G_OBJECT (layer), "auto-transition");
 }
 
 /**
  * ges_layer_get_priority:
- * @layer: a #GESLayer
+ * @layer: The #GESLayer
  *
- * Get the priority of @layer within the timeline.
+ * Get the priority of the layer. When inside a timeline, this is its
+ * index in the timeline. See ges_timeline_move_layer().
  *
- * Returns: The priority of the @layer within the timeline.
+ * Returns: The priority of @layer within its timeline.
  */
 guint
 ges_layer_get_priority (GESLayer * layer)
@@ -516,13 +608,12 @@ ges_layer_get_priority (GESLayer * layer)
 
 /**
  * ges_layer_get_clips:
- * @layer: a #GESLayer
+ * @layer: The #GESLayer
  *
- * Get the clips this layer contains.
+ * Get the #GESClip-s contained in this layer.
  *
- * Returns: (transfer full) (element-type GESClip): a #GList of
- * clips. The user is responsible for
- * unreffing the contained objects and freeing the list.
+ * Returns: (transfer full) (element-type GESClip): A list of clips in
+ * @layer.
  */
 
 GList *
@@ -547,11 +638,11 @@ ges_layer_get_clips (GESLayer * layer)
  * ges_layer_is_empty:
  * @layer: The #GESLayer to check
  *
- * Convenience method to check if @layer is empty (doesn't contain any clip),
- * or not.
+ * Convenience method to check if the layer is empty (doesn't contain
+ * any #GESClip), or not.
  *
- * Returns: %TRUE if @layer is empty, %FALSE if it already contains at least
- * one #GESClip
+ * Returns: %TRUE if @layer is empty, %FALSE if it contains at least
+ * one clip.
  */
 gboolean
 ges_layer_is_empty (GESLayer * layer)
@@ -562,51 +653,70 @@ ges_layer_is_empty (GESLayer * layer)
 }
 
 /**
- * ges_layer_add_clip:
- * @layer: a #GESLayer
- * @clip: (transfer floating): the #GESClip to add.
- *
- * Adds the given clip to the layer. Sets the clip's parent, and thus
- * takes ownership of the clip.
+ * ges_layer_add_clip_full:
+ * @layer: The #GESLayer
+ * @clip: (transfer floating): The clip to add
+ * @error: (nullable): Return location for an error
  *
- * An clip can only be added to one layer.
+ * Adds the given clip to the layer. If the method succeeds, the layer
+ * will take ownership of the clip.
  *
- * Calling this method will construct and properly set all the media related
- * elements on @clip. If you need to know when those objects (actually #GESTrackElement)
- * are constructed, you should connect to the container::child-added signal which
- * is emited right after those elements are ready to be used.
+ * This method will fail and return %FALSE if @clip already resides in
+ * some layer. It can also fail if the additional clip breaks some
+ * compositional rules (see #GESTimelineElement).
  *
- * Returns: %TRUE if the clip was properly added to the layer, or %FALSE
- * if the @layer refuses to add the clip.
+ * Returns: %TRUE if @clip was properly added to @layer, or %FALSE
+ * if @layer refused to add @clip.
+ * Since: 1.18
  */
 gboolean
-ges_layer_add_clip (GESLayer * layer, GESClip * clip)
+ges_layer_add_clip_full (GESLayer * layer, GESClip * clip, GError ** error)
 {
+  GList *tmp, *prev_children, *new_children;
   GESAsset *asset;
   GESLayerPrivate *priv;
   GESLayer *current_layer;
+  GESTimeline *timeline;
+  GESContainer *container;
+  GError *timeline_error = NULL;
 
   g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
   g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
+
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+  container = GES_CONTAINER (clip);
 
   GST_DEBUG_OBJECT (layer, "adding clip:%p", clip);
+  gst_object_ref_sink (clip);
 
   priv = layer->priv;
   current_layer = ges_clip_get_layer (clip);
   if (G_UNLIKELY (current_layer)) {
-    GST_WARNING ("Clip %p already belongs to another layer", clip);
-    gst_object_ref_sink (clip);
+    GST_WARNING_OBJECT (layer, "Clip %" GES_FORMAT " already belongs to "
+        "another layer", GES_ARGS (clip));
+    gst_object_unref (clip);
     gst_object_unref (current_layer);
+    return FALSE;
+  }
 
+  if (timeline && timeline != layer->timeline) {
+    /* if a clip is not in any layer, its timeline should not be set */
+    GST_ERROR_OBJECT (layer, "Clip %" GES_FORMAT " timeline %"
+        GST_PTR_FORMAT " does not match that of the layer %"
+        GST_PTR_FORMAT, GES_ARGS (clip), timeline, layer->timeline);
+    gst_object_unref (clip);
     return FALSE;
   }
 
+  timeline = layer->timeline;
+
   asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
   if (asset == NULL) {
     gchar *id;
     NewAssetUData *mudata = g_slice_new (NewAssetUData);
 
-    mudata->clip = gst_object_ref_sink (clip);
+    mudata->clip = clip;
     mudata->layer = layer;
 
     GST_DEBUG_OBJECT (layer, "%" GST_PTR_FORMAT " as no reference to any "
@@ -635,8 +745,6 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
 
     g_slice_free (NewAssetUData, mudata);
     gst_clear_object (&asset);
-  } else {
-    gst_object_ref_sink (clip);
   }
 
   /* Take a reference to the clip and store it stored by start/priority */
@@ -660,51 +768,104 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
 
   ges_layer_resync_priorities (layer);
 
-  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip),
-      layer->timeline);
-
-  /* emit 'clip-added' */
+  /* FIXME: ideally we would only emit if we are going to return TRUE.
+   * However, for backward-compatibility, we ensure the "clip-added"
+   * signal is released before the clip's "child-added" signal, which is
+   * invoked by ges_timeline_add_clip */
   g_signal_emit (layer, ges_layer_signals[OBJECT_ADDED], 0, clip);
 
-  if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING) && layer->timeline
-      && !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
-          GES_TIMELINE_ELEMENT (clip),
-          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip),
-          GES_TIMELINE_ELEMENT_START (clip),
-          GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
-    GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT, GES_ARGS (clip));
+  prev_children = ges_container_get_children (container, FALSE);
+
+  if (timeline && !ges_timeline_add_clip (timeline, clip, &timeline_error)) {
+    GST_INFO_OBJECT (layer, "Could not add the clip %" GES_FORMAT
+        " to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), timeline);
+
+    if (timeline_error) {
+      if (error) {
+        *error = timeline_error;
+      } else {
+        GST_WARNING_OBJECT (timeline, "Adding the clip %" GES_FORMAT
+            " to the timeline failed: %s", GES_ARGS (clip),
+            timeline_error->message);
+        g_error_free (timeline_error);
+      }
+    }
+
+    /* remove any track elements that were newly created */
+    new_children = ges_container_get_children (container, FALSE);
+    for (tmp = new_children; tmp; tmp = tmp->next) {
+      if (!g_list_find (prev_children, tmp->data))
+        ges_container_remove (container, tmp->data);
+    }
+    g_list_free_full (prev_children, gst_object_unref);
+    g_list_free_full (new_children, gst_object_unref);
+
+    /* FIXME: change emit signal to FALSE once we are able to delay the
+     * "clip-added" signal until after ges_timeline_add_clip */
     ges_layer_remove_clip_internal (layer, clip, TRUE);
     return FALSE;
   }
 
+  g_list_free_full (prev_children, gst_object_unref);
+
+  for (tmp = container->children; tmp; tmp = tmp->next) {
+    GESTrack *track = ges_track_element_get_track (tmp->data);
+
+    if (track)
+      ges_track_element_set_layer_active (tmp->data,
+          ges_layer_get_active_for_track (layer, track));
+  }
+
   return TRUE;
 }
 
 /**
- * ges_layer_add_asset:
- * @layer: a #GESLayer
- * @asset: The asset to add to
- * @start: The start value to set on the new #GESClip,
- * if @start == GST_CLOCK_TIME_NONE, it will be set to
- * the current duration of @layer
- * @inpoint: The inpoint value to set on the new #GESClip
- * @duration: The duration value to set on the new #GESClip
- * @track_types: The #GESTrackType to set on the the new #GESClip
- *
- * Creates Clip from asset, adds it to layer and
- * returns a reference to it.
- *
- * Returns: (transfer none): Created #GESClip
+ * ges_layer_add_clip:
+ * @layer: The #GESLayer
+ * @clip: (transfer floating): The clip to add
+ *
+ * See ges_layer_add_clip_full(), which also gives an error.
+ *
+ * Returns: %TRUE if @clip was properly added to @layer, or %FALSE
+ * if @layer refused to add @clip.
+ */
+gboolean
+ges_layer_add_clip (GESLayer * layer, GESClip * clip)
+{
+  return ges_layer_add_clip_full (layer, clip, NULL);
+}
+
+/**
+ * ges_layer_add_asset_full:
+ * @layer: The #GESLayer
+ * @asset: The asset to extract the new clip from
+ * @start: The #GESTimelineElement:start value to set on the new clip
+ * If `start == #GST_CLOCK_TIME_NONE`, it will be added to the end
+ * of @layer, i.e. it will be set to @layer's duration
+ * @inpoint: The #GESTimelineElement:in-point value to set on the new
+ * clip
+ * @duration: The #GESTimelineElement:duration value to set on the new
+ * clip
+ * @track_types: The #GESClip:supported-formats to set on the the new
+ * clip, or #GES_TRACK_TYPE_UNKNOWN to use the default
+ * @error: (nullable): Return location for an error
+ *
+ * Extracts a new clip from an asset and adds it to the layer with
+ * the given properties.
+ *
+ * Returns: (transfer none): The newly created clip.
+ * Since: 1.18
  */
 GESClip *
-ges_layer_add_asset (GESLayer * layer,
+ges_layer_add_asset_full (GESLayer * layer,
     GESAsset * asset, GstClockTime start, GstClockTime inpoint,
-    GstClockTime duration, GESTrackType track_types)
+    GstClockTime duration, GESTrackType track_types, GError ** error)
 {
   GESClip *clip;
 
   g_return_val_if_fail (GES_IS_LAYER (layer), NULL);
   g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
+  g_return_val_if_fail (!error || !*error, NULL);
   g_return_val_if_fail (g_type_is_a (ges_asset_get_extractable_type
           (asset), GES_TYPE_CLIP), NULL);
 
@@ -733,7 +894,7 @@ ges_layer_add_asset (GESLayer * layer,
     _set_duration0 (GES_TIMELINE_ELEMENT (clip), duration);
   }
 
-  if (!ges_layer_add_clip (layer, clip)) {
+  if (!ges_layer_add_clip_full (layer, clip, error)) {
     return NULL;
   }
 
@@ -741,11 +902,38 @@ ges_layer_add_asset (GESLayer * layer,
 }
 
 /**
+ * ges_layer_add_asset:
+ * @layer: The #GESLayer
+ * @asset: The asset to extract the new clip from
+ * @start: The #GESTimelineElement:start value to set on the new clip
+ * If `start == #GST_CLOCK_TIME_NONE`, it will be added to the end
+ * of @layer, i.e. it will be set to @layer's duration
+ * @inpoint: The #GESTimelineElement:in-point value to set on the new
+ * clip
+ * @duration: The #GESTimelineElement:duration value to set on the new
+ * clip
+ * @track_types: The #GESClip:supported-formats to set on the the new
+ * clip, or #GES_TRACK_TYPE_UNKNOWN to use the default
+ *
+ * See ges_layer_add_asset_full(), which also gives an error.
+ *
+ * Returns: (transfer none): The newly created clip.
+ */
+GESClip *
+ges_layer_add_asset (GESLayer * layer,
+    GESAsset * asset, GstClockTime start, GstClockTime inpoint,
+    GstClockTime duration, GESTrackType track_types)
+{
+  return ges_layer_add_asset_full (layer, asset, start, inpoint, duration,
+      track_types, NULL);
+}
+
+/**
  * ges_layer_new:
  *
- * Creates a new #GESLayer.
+ * Creates a new layer.
  *
- * Returns: (transfer floating): A new #GESLayer
+ * Returns: (transfer floating): A new layer.
  */
 GESLayer *
 ges_layer_new (void)
@@ -755,12 +943,13 @@ ges_layer_new (void)
 
 /**
  * ges_layer_get_timeline:
- * @layer: The #GESLayer to get the parent #GESTimeline from
+ * @layer: The #GESLayer
  *
- * Get the #GESTimeline in which #GESLayer currently is.
+ * Gets the timeline that the layer is a part of.
  *
- * Returns: (transfer none) (nullable): the #GESTimeline in which #GESLayer
- * currently is or %NULL if not in any timeline yet.
+ * Returns: (transfer none) (nullable): The timeline that @layer
+ * is currently part of, or %NULL if it is not associated with any
+ * timeline.
  */
 GESTimeline *
 ges_layer_get_timeline (GESLayer * layer)
@@ -788,13 +977,14 @@ ges_layer_set_timeline (GESLayer * layer, GESTimeline * timeline)
 
 /**
  * ges_layer_get_clips_in_interval:
- * @layer: a #GESLayer
- * @start: start of the interval
- * @end: end of the interval
+ * @layer: The #GESLayer
+ * @start: Start of the interval
+ * @end: End of the interval
  *
- * Gets the clips which appear between @start and @end on @layer.
+ * Gets the clips within the layer that appear between @start and @end.
  *
- * Returns: (transfer full) (element-type GESClip): a #GList of clips intersecting [@start, @end) interval on @layer.
+ * Returns: (transfer full) (element-type GESClip): A list of #GESClip-s
+ * that intersect the interval `[start, end)` in @layer.
  */
 GList *
 ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start,
@@ -828,3 +1018,91 @@ ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start,
   }
   return intersecting_clips;
 }
+
+/**
+ * ges_layer_get_active_for_track:
+ * @layer: The #GESLayer
+ * @track: The #GESTrack to check if @layer is currently active for
+ *
+ * Gets whether the layer is active for the given track. See
+ * ges_layer_set_active_for_tracks().
+ *
+ * Returns: %TRUE if @layer is active for @track, or %FALSE otherwise.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_layer_get_active_for_track (GESLayer * layer, GESTrack * track)
+{
+  LayerActivnessData *d;
+
+  g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+  g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
+  g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track),
+      FALSE);
+
+  d = g_hash_table_lookup (layer->priv->tracks_activness, track);
+
+  return d ? d->active : TRUE;
+}
+
+/**
+ * ges_layer_set_active_for_tracks:
+ * @layer: The #GESLayer
+ * @active: Whether elements in @tracks should be active or not
+ * @tracks: (transfer none) (element-type GESTrack) (allow-none): The list of
+ * tracks @layer should be (de-)active in, or %NULL to include all the tracks
+ * in the @layer's timeline
+ *
+ * Activate or deactivate track elements in @tracks (or in all tracks if @tracks
+ * is %NULL).
+ *
+ * When a layer is deactivated for a track, all the #GESTrackElement-s in
+ * the track that belong to a #GESClip in the layer will no longer be
+ * active in the track, regardless of their individual
+ * #GESTrackElement:active value.
+ *
+ * Note that by default a layer will be active for all of its
+ * timeline's tracks.
+ *
+ * Returns: %TRUE if the operation worked %FALSE otherwise.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_layer_set_active_for_tracks (GESLayer * layer, gboolean active,
+    GList * tracks)
+{
+  GList *tmp, *owned_tracks = NULL;
+  GPtrArray *changed_tracks = NULL;
+
+  g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+
+  if (!tracks && layer->timeline)
+    owned_tracks = tracks = ges_timeline_get_tracks (layer->timeline);
+
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    GESTrack *track = tmp->data;
+
+    /* Handle setting timeline later */
+    g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track),
+        FALSE);
+
+    if (ges_layer_get_active_for_track (layer, track) != active) {
+      if (changed_tracks == NULL)
+        changed_tracks = g_ptr_array_new ();
+      g_ptr_array_add (changed_tracks, track);
+    }
+    g_hash_table_insert (layer->priv->tracks_activness, track,
+        layer_activness_data_new (track, layer, active));
+  }
+
+  if (changed_tracks) {
+    g_signal_emit (layer, ges_layer_signals[ACTIVE_CHANGED], 0, active,
+        changed_tracks);
+    g_ptr_array_unref (changed_tracks);
+  }
+  g_list_free_full (owned_tracks, gst_object_unref);
+
+  return TRUE;
+}
index 5c6bd17..c9d2d06 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_LAYER
-#define _GES_LAYER
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_LAYER ges_layer_get_type()
-
-#define GES_LAYER(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_LAYER, GESLayer))
-
-#define GES_LAYER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_LAYER, GESLayerClass))
-
-#define GES_IS_LAYER(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_LAYER))
-
-#define GES_IS_LAYER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_LAYER))
-
-#define GES_LAYER_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_LAYER, GESLayerClass))
-
-typedef struct _GESLayerPrivate GESLayerPrivate;
+GES_DECLARE_TYPE(Layer, layer, LAYER);
 
 /**
  * GESLayer:
@@ -70,7 +53,7 @@ struct _GESLayer {
  * @get_objects: method to get the objects contained in the layer
  *
  * Subclasses can override the @get_objects if they can provide a more
- * efficient way of providing the list of contained #GESClip(s).
+ * efficient way of providing the list of contained #GESClip-s.
  */
 struct _GESLayerClass {
   /*< private >*/
@@ -82,66 +65,82 @@ struct _GESLayerClass {
 
   /*< private >*/
   /* Signals */
-  void (*object_added)         (GESLayer * layer, GESClip * object);
-  void (*object_removed)       (GESLayer * layer, GESClip * object);
+  void (*object_added) (GESLayer * layer, GESClip * object);
+  void (*object_removed) (GESLayer * layer, GESClip * object);
 
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
 };
 
 GES_API
-GType ges_layer_get_type (void);
+GESLayer*     ges_layer_new                   (void);
 
 GES_API
-GESLayer* ges_layer_new (void);
-
+void          ges_layer_set_timeline          (GESLayer * layer,
+                                               GESTimeline * timeline);
 GES_API
-void     ges_layer_set_timeline  (GESLayer * layer,
-                                          GESTimeline * timeline);
-
-GES_API GESTimeline *
-ges_layer_get_timeline           (GESLayer * layer);
+GESTimeline * ges_layer_get_timeline          (GESLayer * layer);
 
 GES_API
-gboolean ges_layer_add_clip    (GESLayer * layer,
-                                          GESClip * clip);
+gboolean      ges_layer_add_clip              (GESLayer * layer,
+                                               GESClip * clip);
 GES_API
-GESClip * ges_layer_add_asset   (GESLayer *layer,
-                                                       GESAsset *asset,
-                                                       GstClockTime start,
-                                                       GstClockTime inpoint,
-                                                       GstClockTime duration,
-                                                       GESTrackType track_types);
-
+gboolean      ges_layer_add_clip_full         (GESLayer * layer,
+                                               GESClip * clip,
+                                               GError ** error);
+GES_API
+GESClip *     ges_layer_add_asset             (GESLayer *layer,
+                                               GESAsset *asset,
+                                               GstClockTime start,
+                                               GstClockTime inpoint,
+                                               GstClockTime duration,
+                                               GESTrackType track_types);
 GES_API
-gboolean ges_layer_remove_clip (GESLayer * layer,
-                                          GESClip * clip);
+GESClip *     ges_layer_add_asset_full        (GESLayer *layer,
+                                               GESAsset *asset,
+                                               GstClockTime start,
+                                               GstClockTime inpoint,
+                                               GstClockTime duration,
+                                               GESTrackType track_types,
+                                               GError ** error);
 
 GES_API
-void     ges_layer_set_priority  (GESLayer * layer,
-                                          guint priority);
+gboolean      ges_layer_remove_clip           (GESLayer * layer,
+                                               GESClip * clip);
+
+GES_DEPRECATED_FOR(ges_timeline_move_layer)
+void          ges_layer_set_priority          (GESLayer * layer,
+                                               guint priority);
 
 GES_API
-gboolean ges_layer_is_empty      (GESLayer * layer);
+gboolean      ges_layer_is_empty              (GESLayer * layer);
 
 GES_API
-GList* ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start, GstClockTime end);
+GList*        ges_layer_get_clips_in_interval (GESLayer * layer,
+                                               GstClockTime start,
+                                               GstClockTime end);
 
 GES_API
-guint   ges_layer_get_priority  (GESLayer * layer);
+guint         ges_layer_get_priority          (GESLayer * layer);
 
 GES_API
-gboolean ges_layer_get_auto_transition (GESLayer * layer);
+gboolean      ges_layer_get_auto_transition   (GESLayer * layer);
 
 GES_API
-void ges_layer_set_auto_transition (GESLayer * layer,
-                                            gboolean auto_transition);
+void          ges_layer_set_auto_transition   (GESLayer * layer,
+                                               gboolean auto_transition);
 
 GES_API
-GList*   ges_layer_get_clips   (GESLayer * layer);
+GList*        ges_layer_get_clips             (GESLayer * layer);
+GES_API
+GstClockTime  ges_layer_get_duration          (GESLayer *layer);
 GES_API
-GstClockTime ges_layer_get_duration (GESLayer *layer);
+gboolean      ges_layer_set_active_for_tracks (GESLayer *layer,
+                                               gboolean active,
+                                               GList *tracks);
 
-G_END_DECLS
+GES_API
+gboolean      ges_layer_get_active_for_track  (GESLayer *layer,
+                                               GESTrack *track);
 
-#endif /* _GES_LAYER */
+G_END_DECLS
diff --git a/ges/ges-marker-list.c b/ges/ges-marker-list.c
new file mode 100644 (file)
index 0000000..30ed78e
--- /dev/null
@@ -0,0 +1,632 @@
+/* GStreamer Editing Services
+
+ * Copyright (C) <2019> Mathieu Duponchelle <mathieu@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION: gesmarkerlist
+ * @title: GESMarkerList
+ * @short_description: implements a list of markers with metadata asociated to time positions
+ * @see_also: #GESMarker
+ *
+ * A #GESMarker can be colored by setting the #GES_META_MARKER_COLOR meta.
+ *
+ * Since: 1.18
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ges-marker-list.h"
+#include "ges.h"
+#include "ges-internal.h"
+#include "ges-meta-container.h"
+
+static void ges_meta_container_interface_init (GESMetaContainerInterface *
+    iface);
+
+struct _GESMarker
+{
+  GObject parent;
+  GstClockTime position;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GESMarker, ges_marker, G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER,
+        ges_meta_container_interface_init));
+
+enum
+{
+  PROP_MARKER_0,
+  PROP_MARKER_POSITION,
+  PROP_MARKER_LAST
+};
+
+static GParamSpec *marker_properties[PROP_MARKER_LAST];
+
+/* GObject Standard vmethods*/
+static void
+ges_marker_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GESMarker *marker = GES_MARKER (object);
+
+  switch (property_id) {
+    case PROP_MARKER_POSITION:
+      g_value_set_uint64 (value, marker->position);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+static void
+ges_marker_init (GESMarker * self)
+{
+  ges_meta_container_register_static_meta (GES_META_CONTAINER (self),
+      GES_META_READ_WRITE, GES_META_MARKER_COLOR, G_TYPE_UINT);
+}
+
+static void
+ges_marker_class_init (GESMarkerClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = ges_marker_get_property;
+  /**
+   * GESMarker:position:
+   *
+   * Current position (in nanoseconds) of the #GESMarker
+   *
+   * Since: 1.18
+   */
+  marker_properties[PROP_MARKER_POSITION] =
+      g_param_spec_uint64 ("position", "Position",
+      "The position of the marker", 0, G_MAXUINT64,
+      GST_CLOCK_TIME_NONE, G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_MARKER_POSITION,
+      marker_properties[PROP_MARKER_POSITION]);
+
+}
+
+static void
+ges_meta_container_interface_init (GESMetaContainerInterface * iface)
+{
+}
+
+/* GESMarkerList */
+
+struct _GESMarkerList
+{
+  GObject parent;
+
+  GSequence *markers;
+  GHashTable *markers_iters;
+  GESMarkerFlags flags;
+};
+
+enum
+{
+  PROP_MARKER_LIST_0,
+  PROP_MARKER_LIST_FLAGS,
+  PROP_MARKER_LIST_LAST
+};
+
+static GParamSpec *list_properties[PROP_MARKER_LIST_LAST];
+
+enum
+{
+  MARKER_ADDED,
+  MARKER_REMOVED,
+  MARKER_MOVED,
+  LAST_SIGNAL
+};
+
+static guint ges_marker_list_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GESMarkerList, ges_marker_list, G_TYPE_OBJECT);
+
+static void
+ges_marker_list_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GESMarkerList *self = GES_MARKER_LIST (object);
+
+  switch (property_id) {
+    case PROP_MARKER_LIST_FLAGS:
+      g_value_set_flags (value, self->flags);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+static void
+ges_marker_list_set_property (GObject * object,
+    guint property_id, const GValue * value, GParamSpec * pspec)
+{
+  GESMarkerList *self = GES_MARKER_LIST (object);
+
+  switch (property_id) {
+    case PROP_MARKER_LIST_FLAGS:
+      self->flags = g_value_get_flags (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+remove_marker (gpointer data)
+{
+  GESMarker *marker = (GESMarker *) data;
+
+  g_object_unref (marker);
+}
+
+static void
+ges_marker_list_init (GESMarkerList * self)
+{
+  self->markers = g_sequence_new (remove_marker);
+  self->markers_iters = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+ges_marker_list_finalize (GObject * object)
+{
+  GESMarkerList *self = GES_MARKER_LIST (object);
+
+  g_sequence_free (self->markers);
+  g_hash_table_unref (self->markers_iters);
+
+  G_OBJECT_CLASS (ges_marker_list_parent_class)->finalize (object);
+}
+
+static void
+ges_marker_list_class_init (GESMarkerListClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ges_marker_list_finalize;
+  object_class->get_property = ges_marker_list_get_property;
+  object_class->set_property = ges_marker_list_set_property;
+
+/**
+  * GESMarkerList:flags:
+  *
+  * Flags indicating how markers on the list should be treated.
+  *
+  * Since: 1.20
+  */
+  list_properties[PROP_MARKER_LIST_FLAGS] =
+      g_param_spec_flags ("flags", "Flags",
+      "Functionalities the marker list should be used for",
+      GES_TYPE_MARKER_FLAGS, GES_MARKER_FLAG_NONE,
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+  g_object_class_install_property (object_class, PROP_MARKER_LIST_FLAGS,
+      list_properties[PROP_MARKER_LIST_FLAGS]);
+
+/**
+  * GESMarkerList::marker-added:
+  * @marker-list: the #GESMarkerList
+  * @position: the position of the added marker
+  * @marker: the #GESMarker that was added.
+  *
+  * Will be emitted after the marker was added to the marker-list.
+  * Since: 1.18
+  */
+  ges_marker_list_signals[MARKER_ADDED] =
+      g_signal_new ("marker-added", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
+      G_TYPE_NONE, 2, G_TYPE_UINT64, GES_TYPE_MARKER);
+
+/**
+  * GESMarkerList::marker-removed:
+  * @marker_list: the #GESMarkerList
+  * @marker: the #GESMarker that was removed.
+  *
+  * Will be emitted after the marker was removed the marker-list.
+  * Since: 1.18
+  */
+  ges_marker_list_signals[MARKER_REMOVED] =
+      g_signal_new ("marker-removed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_MARKER);
+
+/**
+  * GESMarkerList::marker-moved:
+  * @marker_list: the #GESMarkerList
+  * @previous_position: the previous position of the marker
+  * @new_position: the new position of the marker
+  * @marker: the #GESMarker that was moved.
+  *
+  * Will be emitted after the marker was moved to.
+  * Since: 1.18
+  */
+  ges_marker_list_signals[MARKER_MOVED] =
+      g_signal_new ("marker-moved", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
+      G_TYPE_NONE, 3, G_TYPE_UINT64, G_TYPE_UINT64, GES_TYPE_MARKER);
+}
+
+static gint
+cmp_marker (gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer data)
+{
+  GESMarker *marker_a = (GESMarker *) a;
+  GESMarker *marker_b = (GESMarker *) b;
+
+  if (marker_a->position < marker_b->position)
+    return -1;
+  else if (marker_a->position == marker_b->position)
+    return 0;
+  else
+    return 1;
+}
+
+/**
+ * ges_marker_list_new:
+ *
+ * Creates a new #GESMarkerList.
+
+ * Returns: A new #GESMarkerList
+ * Since: 1.18
+ */
+GESMarkerList *
+ges_marker_list_new (void)
+{
+  GESMarkerList *ret;
+
+  ret = (GESMarkerList *) g_object_new (GES_TYPE_MARKER_LIST, NULL);
+
+  return ret;
+}
+
+/**
+ * ges_marker_list_add:
+ * @position: The position of the new marker
+ *
+ * Returns: (transfer none): The newly-added marker, the list keeps ownership
+ * of the marker
+ * Since: 1.18
+ */
+GESMarker *
+ges_marker_list_add (GESMarkerList * self, GstClockTime position)
+{
+  GESMarker *ret;
+  GSequenceIter *iter;
+
+  g_return_val_if_fail (GES_IS_MARKER_LIST (self), NULL);
+
+  ret = (GESMarker *) g_object_new (GES_TYPE_MARKER, NULL);
+
+  ret->position = position;
+
+  iter = g_sequence_insert_sorted (self->markers, ret, cmp_marker, NULL);
+
+  g_hash_table_insert (self->markers_iters, ret, iter);
+
+  g_signal_emit (self, ges_marker_list_signals[MARKER_ADDED], 0, position, ret);
+
+  return ret;
+}
+
+/**
+ * ges_marker_list_size:
+ *
+ * Returns: The number of markers in @list
+ * Since: 1.18
+ */
+guint
+ges_marker_list_size (GESMarkerList * self)
+{
+  g_return_val_if_fail (GES_IS_MARKER_LIST (self), 0);
+
+  return g_sequence_get_length (self->markers);
+}
+
+/**
+ * ges_marker_list_remove:
+ *
+ * Removes @marker from @list, this decreases the refcount of the
+ * marker by 1.
+ *
+ * Returns: %TRUE if the marker could be removed, %FALSE otherwise
+ *   (if the marker was not present in the list for example)
+ * Since: 1.18
+ */
+gboolean
+ges_marker_list_remove (GESMarkerList * self, GESMarker * marker)
+{
+  GSequenceIter *iter;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (GES_IS_MARKER_LIST (self), FALSE);
+
+  if (!g_hash_table_lookup_extended (self->markers_iters,
+          marker, NULL, (gpointer *) & iter))
+    goto done;
+  g_assert (iter != NULL);
+  g_hash_table_remove (self->markers_iters, marker);
+
+  g_signal_emit (self, ges_marker_list_signals[MARKER_REMOVED], 0, marker);
+
+  g_sequence_remove (iter);
+
+  ret = TRUE;
+
+done:
+  return ret;
+}
+
+/**
+ * ges_marker_list_get_markers:
+ *
+ * Returns: (element-type GESMarker) (transfer full): a #GList
+ * of the #GESMarker within the GESMarkerList. The user will have
+ * to unref each #GESMarker and free the #GList.
+ *
+ * Since: 1.18
+ */
+GList *
+ges_marker_list_get_markers (GESMarkerList * self)
+{
+  GESMarker *marker;
+  GSequenceIter *iter;
+  GList *ret;
+
+  g_return_val_if_fail (GES_IS_MARKER_LIST (self), NULL);
+  ret = NULL;
+
+  for (iter = g_sequence_get_begin_iter (self->markers);
+      !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
+    marker = GES_MARKER (g_sequence_get (iter));
+
+    ret = g_list_append (ret, g_object_ref (marker));
+  }
+
+  return ret;
+}
+
+/*
+ * ges_marker_list_get_closest:
+ * @position: The position which we want to find the closest marker to
+ *
+ * Returns: (transfer full): The marker found to be the closest
+ * to the given position. If two markers are at equal distance from position,
+ * the "earlier" one will be returned.
+ */
+GESMarker *
+ges_marker_list_get_closest (GESMarkerList * self, GstClockTime position)
+{
+  GESMarker *new_marker, *ret = NULL;
+  GstClockTime distance_next, distance_prev;
+  GSequenceIter *iter;
+
+  if (g_sequence_is_empty (self->markers))
+    goto done;
+
+  new_marker = (GESMarker *) g_object_new (GES_TYPE_MARKER, NULL);
+  new_marker->position = position;
+  iter = g_sequence_search (self->markers, new_marker, cmp_marker, NULL);
+  g_object_unref (new_marker);
+
+  if (g_sequence_iter_is_begin (iter)) {
+    /* We know the sequence isn't empty, this is safe */
+    ret = g_sequence_get (iter);
+  } else if (g_sequence_iter_is_end (iter)) {
+    /* We know the sequence isn't empty, this is safe */
+    ret = g_sequence_get (g_sequence_iter_prev (iter));
+  } else {
+    GESMarker *next_marker, *prev_marker;
+
+    prev_marker = g_sequence_get (g_sequence_iter_prev (iter));
+    next_marker = g_sequence_get (iter);
+
+    distance_next = next_marker->position - position;
+    distance_prev = position - prev_marker->position;
+
+    ret = distance_prev <= distance_next ? prev_marker : next_marker;
+  }
+
+done:
+  if (ret)
+    return g_object_ref (ret);
+  return NULL;
+}
+
+/**
+ * ges_marker_list_move:
+ *
+ * Moves a @marker in a @list to a new @position
+ *
+ * Returns: %TRUE if the marker could be moved, %FALSE otherwise
+ *   (if the marker was not present in the list for example)
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_marker_list_move (GESMarkerList * self, GESMarker * marker,
+    GstClockTime position)
+{
+  GSequenceIter *iter;
+  gboolean ret = FALSE;
+  GstClockTime previous_position;
+
+  g_return_val_if_fail (GES_IS_MARKER_LIST (self), FALSE);
+
+  if (!g_hash_table_lookup_extended (self->markers_iters,
+          marker, NULL, (gpointer *) & iter)) {
+    GST_WARNING ("GESMarkerList doesn't contain GESMarker");
+    goto done;
+  }
+
+  previous_position = marker->position;
+  marker->position = position;
+
+  g_signal_emit (self, ges_marker_list_signals[MARKER_MOVED], 0,
+      previous_position, position, marker);
+
+  g_sequence_sort_changed (iter, cmp_marker, NULL);
+
+  ret = TRUE;
+
+done:
+  return ret;
+}
+
+gboolean
+ges_marker_list_deserialize (GValue * dest, const gchar * s)
+{
+  gboolean ret = FALSE;
+  GstCaps *caps = NULL;
+  GESMarkerList *list = ges_marker_list_new ();
+  guint caps_len, i = 0;
+  gsize string_len;
+  gchar *escaped, *caps_str;
+  GstStructure *data_s;
+  gint flags;
+
+  string_len = strlen (s);
+  if (G_UNLIKELY (*s != '"' || string_len < 2 || s[string_len - 1] != '"')) {
+    /* "\"" is not an accepted string, so len must be at least 2 */
+    GST_ERROR ("Failed deserializing marker list: expected string to start "
+        "and end with '\"'");
+    goto done;
+  }
+  escaped = g_strdup (s + 1);
+  escaped[string_len - 2] = '\0';
+  /* removed trailing '"' */
+  caps_str = g_strcompress (escaped);
+  g_free (escaped);
+
+  caps = gst_caps_from_string (caps_str);
+  g_free (caps_str);
+  if (G_UNLIKELY (caps == NULL)) {
+    GST_ERROR ("Failed deserializing marker list: could not extract caps");
+    goto done;
+  }
+
+  caps_len = gst_caps_get_size (caps);
+  if (G_UNLIKELY (caps_len == 0)) {
+    GST_DEBUG ("Got empty caps: %s", s);
+    goto done;
+  }
+
+  data_s = gst_caps_get_structure (caps, i);
+  if (gst_structure_has_name (data_s, "marker-list-flags")) {
+    if (!gst_structure_get_int (data_s, "flags", &flags)) {
+      GST_ERROR_OBJECT (dest,
+          "Failed deserializing marker list: unexpected structure %"
+          GST_PTR_FORMAT, data_s);
+      goto done;
+    }
+
+    list->flags = flags;
+    i += 1;
+  }
+
+  if (G_UNLIKELY ((caps_len - i) % 2)) {
+    GST_ERROR ("Failed deserializing marker list: incomplete marker caps");
+  }
+
+  for (; i < caps_len - 1; i += 2) {
+    const GstStructure *pos_s = gst_caps_get_structure (caps, i);
+    const GstStructure *meta_s = gst_caps_get_structure (caps, i + 1);
+    GstClockTime position;
+    GESMarker *marker;
+    gchar *metas;
+
+    if (!gst_structure_has_name (pos_s, "marker-times")) {
+      GST_ERROR_OBJECT (dest,
+          "Failed deserializing marker list: unexpected structure %"
+          GST_PTR_FORMAT, pos_s);
+      goto done;
+    }
+
+    if (!gst_structure_get_uint64 (pos_s, "position", &position)) {
+      GST_ERROR_OBJECT (dest,
+          "Failed deserializing marker list: unexpected structure %"
+          GST_PTR_FORMAT, pos_s);
+      goto done;
+    }
+
+    marker = ges_marker_list_add (list, position);
+
+    metas = gst_structure_to_string (meta_s);
+    ges_meta_container_add_metas_from_string (GES_META_CONTAINER (marker),
+        metas);
+    g_free (metas);
+  }
+
+  ret = TRUE;
+
+done:
+  if (caps)
+    gst_caps_unref (caps);
+
+  if (!ret)
+    g_object_unref (list);
+  else
+    g_value_take_object (dest, list);
+
+  return ret;
+}
+
+gchar *
+ges_marker_list_serialize (const GValue * v)
+{
+  GESMarkerList *list = GES_MARKER_LIST (g_value_get_object (v));
+  GSequenceIter *iter;
+  GstCaps *caps = gst_caps_new_empty ();
+  gchar *caps_str, *escaped, *res;
+  GstStructure *s;
+
+  s = gst_structure_new ("marker-list-flags", "flags", G_TYPE_INT,
+      list->flags, NULL);
+  gst_caps_append_structure (caps, s);
+
+  iter = g_sequence_get_begin_iter (list->markers);
+
+  while (!g_sequence_iter_is_end (iter)) {
+    GESMarker *marker = (GESMarker *) g_sequence_get (iter);
+    gchar *metas;
+
+    metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (marker));
+
+    s = gst_structure_new ("marker-times", "position", G_TYPE_UINT64,
+        marker->position, NULL);
+    gst_caps_append_structure (caps, s);
+    s = gst_structure_from_string (metas, NULL);
+    gst_caps_append_structure (caps, s);
+
+    g_free (metas);
+
+    iter = g_sequence_iter_next (iter);
+  }
+
+  caps_str = gst_caps_to_string (caps);
+  escaped = g_strescape (caps_str, NULL);
+  g_free (caps_str);
+  res = g_strdup_printf ("\"%s\"", escaped);
+  g_free (escaped);
+  gst_caps_unref (caps);
+
+  return res;
+}
diff --git a/ges/ges-marker-list.h b/ges/ges-marker-list.h
new file mode 100644 (file)
index 0000000..51c7ed1
--- /dev/null
@@ -0,0 +1,70 @@
+/* GStreamer Editing Services
+
+ * Copyright (C) <2019> Mathieu Duponchelle <mathieu@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <ges/ges-types.h>
+
+G_BEGIN_DECLS
+
+#define GES_TYPE_MARKER ges_marker_get_type ()
+
+/**
+ * GESMarker:
+ *
+ * A timed #GESMetaContainer object.
+ *
+ * Since: 1.18
+ */
+GES_API
+G_DECLARE_FINAL_TYPE (GESMarker, ges_marker, GES, MARKER, GObject)
+#define GES_TYPE_MARKER_LIST ges_marker_list_get_type ()
+
+/**
+ * GESMarkerList:
+ *
+ * A list of #GESMarker
+ *
+ * Since: 1.18
+ */
+GES_API
+G_DECLARE_FINAL_TYPE (GESMarkerList, ges_marker_list, GES, MARKER_LIST, GObject)
+
+GES_API
+GESMarkerList * ges_marker_list_new (void);
+
+GES_API
+GESMarker * ges_marker_list_add (GESMarkerList * list, GstClockTime position);
+
+GES_API
+gboolean ges_marker_list_remove (GESMarkerList * list, GESMarker *marker);
+
+GES_API
+guint ges_marker_list_size (GESMarkerList * list);
+
+
+GES_API
+GList * ges_marker_list_get_markers (GESMarkerList *list);
+
+GES_API
+gboolean ges_marker_list_move (GESMarkerList *list, GESMarker *marker, GstClockTime position);
+
+G_END_DECLS
index 4c37e27..85fd902 100644 (file)
 #include <gst/gst.h>
 
 #include "ges-meta-container.h"
+#include "ges-marker-list.h"
 
 /**
-* SECTION: gesmetacontainer
-* @short_description: An interface for storing meta
-*
-* Interface that allows reading and writing meta
-*/
+ * SECTION: gesmetacontainer
+ * @title: GESMetaContainer Interface
+ * @short_description: An interface for storing metadata
+ *
+ * A #GObject that implements #GESMetaContainer can have metadata set on
+ * it, that is data that is unimportant to its function within GES, but
+ * may hold some useful information. In particular,
+ * ges_meta_container_set_meta() can be used to store any #GValue under
+ * any generic field (specified by a string key). The same method can also
+ * be used to remove the field by passing %NULL. A number of convenience
+ * methods are also provided to make it easier to set common value types.
+ * The metadata can then be read with ges_meta_container_get_meta() and
+ * similar convenience methods.
+ *
+ * ## Registered Fields
+ *
+ * By default, any #GValue can be set for a metadata field. However, you
+ * can register some fields as static, that is they only allow values of a
+ * specific type to be set under them, using
+ * ges_meta_container_register_meta() or
+ * ges_meta_container_register_static_meta(). The set #GESMetaFlag will
+ * determine whether the value can be changed, but even if it can be
+ * changed, it must be changed to a value of the same type.
+ *
+ * Internally, some GES objects will be initialized with static metadata
+ * fields. These will correspond to some standard keys, such as
+ * #GES_META_VOLUME.
+ */
 
 static GQuark ges_meta_key;
 
 G_DEFINE_INTERFACE_WITH_CODE (GESMetaContainer, ges_meta_container,
     G_TYPE_OBJECT, ges_meta_key =
-    g_quark_from_static_string ("ges-meta-container-data");
-    );
+    g_quark_from_static_string ("ges-meta-container-data"););
 
 enum
 {
@@ -65,18 +88,19 @@ ges_meta_container_default_init (GESMetaContainerInterface * iface)
 {
 
   /**
-   * GESMetaContainer::notify:
-   * @container: a #GESMetaContainer
-   * @prop: the key of the value that changed
-   * @value: the #GValue containing the new value
+   * GESMetaContainer::notify-meta:
+   * @container: A #GESMetaContainer
+   * @key: The key for the @container field that changed
+   * @value: (nullable): The new value under @key
    *
-   * The notify signal is used to be notify of changes of values
-   * of some metadatas
+   * This is emitted for a meta container whenever the metadata under one
+   * of its fields changes, is set for the first time, or is removed. In
+   * the latter case, @value will be %NULL.
    */
   _signals[NOTIFY_SIGNAL] =
       g_signal_new ("notify-meta", G_TYPE_FROM_INTERFACE (iface),
       G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED |
-      G_SIGNAL_NO_HOOKS, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VALUE);
 }
 
@@ -147,12 +171,13 @@ _append_foreach (GQuark field_id, const GValue * value, GESMetaContainer * self)
 
 /**
  * ges_meta_container_foreach:
- * @container: container to iterate over
- * @func: (scope call): function to be called for each metadata
- * @user_data: (closure): user specified data
+ * @container: A #GESMetaContainer
+ * @func: (scope call): A function to call on each of @container's set
+ * metadata fields
+ * @user_data: (closure): User data to send to @func
  *
- * Calls the given function for each metadata inside the meta container. Note
- * that if there is no metadata, the function won't be called at all.
+ * Calls the given function on each of the meta container's set metadata
+ * fields.
  */
 void
 ges_meta_container_foreach (GESMetaContainer * container,
@@ -174,7 +199,6 @@ ges_meta_container_foreach (GESMetaContainer * container,
       (GstStructureForeachFunc) structure_foreach_wrapper, &foreach_data);
 }
 
-/* _can_write_value should have been checked before calling */
 static gboolean
 _register_meta (GESMetaContainer * container, GESMetaFlag flags,
     const gchar * meta_item, GType type)
@@ -200,6 +224,7 @@ _register_meta (GESMetaContainer * container, GESMetaFlag flags,
   return TRUE;
 }
 
+/* _can_write_value should have been checked before calling */
 static gboolean
 _set_value (GESMetaContainer * container, const gchar * meta_item,
     const GValue * value)
@@ -284,133 +309,146 @@ ges_meta_container_set_ ## name (GESMetaContainer *container,      \
 
 /**
  * ges_meta_container_set_boolean:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given boolean value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (boolean, gboolean, G_TYPE_BOOLEAN, boolean);
 
 /**
  * ges_meta_container_set_int:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given int value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (int, gint, G_TYPE_INT, int);
 
 /**
  * ges_meta_container_set_uint:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given uint value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (uint, guint, G_TYPE_UINT, uint);
 
 /**
  * ges_meta_container_set_int64:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given int64 value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (int64, gint64, G_TYPE_INT64, int64);
 
 /**
  * ges_meta_container_set_uint64:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given uint64 value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (uint64, guint64, G_TYPE_UINT64, uint64);
 
 /**
  * ges_meta_container_set_float:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given float value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (float, float, G_TYPE_FLOAT, float);
 
 /**
  * ges_meta_container_set_double:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given double value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (double, double, G_TYPE_DOUBLE, double);
 
 /**
  * ges_meta_container_set_date:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given date value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (date, const GDate *, G_TYPE_DATE, boxed);
 
 /**
  * ges_meta_container_set_date_time:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to the
+ * given date time value.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 CREATE_SETTER (date_time, const GstDateTime *, GST_TYPE_DATE_TIME, boxed);
 
 /**
-* ges_meta_container_set_string:
-* @container: Target container
-* @meta_item: Name of the meta item to set
-* @value: Value to set
-*
-* Sets the value of a given meta item
-*
-* Return: %TRUE if the meta could be added, %FALSE otherwise
-*/
+ * ges_meta_container_set_string:
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: The value to set under @meta_item
+ *
+ * Sets the value of the specified field of the meta container to the
+ * given string value.
+ *
+ * Returns: %TRUE if @value was set under @meta_item for @container.
+ */
 CREATE_SETTER (string, const gchar *, G_TYPE_STRING, string);
 
 /**
  * ges_meta_container_set_meta:
- * @container: Target container
- * @meta_item: Name of the meta item to set
- * @value: (allow-none): Value to set
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @value: (nullable): The value to set under @meta_item, or %NULL to
+ * remove the corresponding field
  *
- * Sets the value of a given meta item
+ * Sets the value of the specified field of the meta container to a
+ * copy of the given value. If the given @value is %NULL, the field
+ * given by @meta_item is removed and %TRUE is returned.
  *
- * Return: %TRUE if the meta could be added, %FALSE otherwise
+ * Returns: %TRUE if @value was set under @meta_item for @container.
  */
 gboolean
 ges_meta_container_set_meta (GESMetaContainer * container,
@@ -435,13 +473,58 @@ ges_meta_container_set_meta (GESMetaContainer * container,
 }
 
 /**
+ * ges_meta_container_set_marker_list:
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to set
+ * @list: The value to set under @meta_item
+ *
+ * Sets the value of the specified field of the meta container to the
+ * given marker list value.
+ *
+ * Returns: %TRUE if @value was set under @meta_item for @container.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_meta_container_set_marker_list (GESMetaContainer * container,
+    const gchar * meta_item, const GESMarkerList * list)
+{
+  gboolean ret;
+  GValue v = G_VALUE_INIT;
+  g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE);
+  g_return_val_if_fail (meta_item != NULL, FALSE);
+
+  if (list == NULL) {
+    GstStructure *structure = _meta_container_get_structure (container);
+    gst_structure_remove_field (structure, meta_item);
+
+    g_signal_emit (container, _signals[NOTIFY_SIGNAL], 0, meta_item, list);
+
+    return TRUE;
+  }
+
+  g_return_val_if_fail (GES_IS_MARKER_LIST ((gpointer) list), FALSE);
+
+  if (_can_write_value (container, meta_item, GES_TYPE_MARKER_LIST) == FALSE)
+    return FALSE;
+
+  g_value_init_from_instance (&v, (gpointer) list);
+
+  ret = _set_value (container, meta_item, &v);
+
+  g_value_unset (&v);
+
+  return ret;
+}
+
+/**
  * ges_meta_container_metas_to_string:
- * @container: a #GESMetaContainer
+ * @container: A #GESMetaContainer
  *
- * Serializes a meta container to a string.
+ * Serializes the set metadata fields of the meta container to a string.
  *
- * Returns: (nullable): a newly-allocated string, or NULL in case of an error.
- * The string must be freed with g_free() when no longer needed.
+ * Returns: (transfer full): A serialized @container, or %NULL if an error
+ * occurred.
  */
 gchar *
 ges_meta_container_metas_to_string (GESMetaContainer * container)
@@ -457,12 +540,15 @@ ges_meta_container_metas_to_string (GESMetaContainer * container)
 
 /**
  * ges_meta_container_add_metas_from_string:
- * @container: Target container
- * @str: a string created with ges_meta_container_metas_to_string()
+ * @container: A #GESMetaContainer
+ * @str: A string to deserialize and add to @container
  *
- * Deserializes a meta container.
+ * Deserializes the given string, and adds and sets the found fields and
+ * their values on the container. The string should be the return of
+ * ges_meta_container_metas_to_string().
  *
- * Returns: TRUE on success, FALSE if there was an error.
+ * Returns: %TRUE if the fields in @str was successfully deserialized
+ * and added to @container.
  */
 gboolean
 ges_meta_container_add_metas_from_string (GESMetaContainer * container,
@@ -485,6 +571,61 @@ ges_meta_container_add_metas_from_string (GESMetaContainer * container,
   return TRUE;
 }
 
+/**
+ * ges_meta_container_register_static_meta:
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @type: The required value type for the registered field
+ *
+ * Registers a static metadata field on the container to only hold the
+ * specified type. After calling this, setting a value under this field
+ * can only succeed if its type matches the registered type of the field.
+ *
+ * Unlike ges_meta_container_register_meta(), no (initial) value is set
+ * for this field, which means you can use this method to reserve the
+ * space to be _optionally_ set later.
+ *
+ * Note that if a value has already been set for the field being
+ * registered, then its type must match the registering type, and its
+ * value will be left in place. If the field has no set value, then
+ * you will likely want to include #GES_META_WRITABLE in @flags to allow
+ * the value to be set later.
+ *
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold @type values, with the given @flags.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_meta_container_register_static_meta (GESMetaContainer * container,
+    GESMetaFlag flags, const gchar * meta_item, GType type)
+{
+  GstStructure *structure;
+
+  g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE);
+  g_return_val_if_fail (meta_item != NULL, FALSE);
+
+  /* If the meta is already in use, and is of a different type, then we
+   * want to fail since, unlike ges_meta_container_register_meta, we will
+   * not be overwriting this value! If we didn't fail, the user could have
+   * a false sense that this meta will always be of the reserved type.
+   */
+  structure = _meta_container_get_structure (container);
+  if (gst_structure_has_field (structure, meta_item) &&
+      gst_structure_get_field_type (structure, meta_item) != type) {
+    gchar *value_string =
+        g_strdup_value_contents (gst_structure_get_value (structure,
+            meta_item));
+    GST_WARNING_OBJECT (container,
+        "Meta %s already assigned a value of %s, which is a different type",
+        meta_item, value_string);
+    g_free (value_string);
+    return FALSE;
+  }
+  return _register_meta (container, flags, meta_item, type);
+}
+
 #define CREATE_REGISTER_STATIC(name, value_ctype, value_gtype, setter_name) \
 gboolean                                                                      \
 ges_meta_container_register_meta_ ## name (GESMetaContainer *container,\
@@ -510,167 +651,211 @@ ges_meta_container_register_meta_ ## name (GESMetaContainer *container,\
 
 /**
  * ges_meta_container_register_meta_boolean:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given boolean value, and registers the field to only hold a boolean
+ * typed value. After calling this, only boolean values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold boolean typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (boolean, gboolean, G_TYPE_BOOLEAN, boolean);
 
 /**
  * ges_meta_container_register_meta_int:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given int value, and registers the field to only hold an int
+ * typed value. After calling this, only int values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold int typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (int, gint, G_TYPE_INT, int);
 
 /**
  * ges_meta_container_register_meta_uint:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given uint value, and registers the field to only hold a uint
+ * typed value. After calling this, only uint values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold uint typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (uint, guint, G_TYPE_UINT, uint);
 
 /**
  * ges_meta_container_register_meta_int64:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given int64 value, and registers the field to only hold an int64
+ * typed value. After calling this, only int64 values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold int64 typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (int64, gint64, G_TYPE_INT64, int64);
 
 /**
  * ges_meta_container_register_meta_uint64:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given uint64 value, and registers the field to only hold a uint64
+ * typed value. After calling this, only uint64 values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold uint64 typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (uint64, guint64, G_TYPE_UINT64, uint64);
 
 /**
  * ges_meta_container_register_meta_float:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given float value, and registers the field to only hold a float
+ * typed value. After calling this, only float values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold float typed values, with the given @flags,
+ * and the field was successfully set to @value.
 */
 CREATE_REGISTER_STATIC (float, float, G_TYPE_FLOAT, float);
 
 /**
  * ges_meta_container_register_meta_double:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given double value, and registers the field to only hold a double
+ * typed value. After calling this, only double values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold double typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (double, double, G_TYPE_DOUBLE, double);
 
 /**
  * ges_meta_container_register_meta_date:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: (allow-none): Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given date value, and registers the field to only hold a date
+ * typed value. After calling this, only date values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold date typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (date, const GDate *, G_TYPE_DATE, boxed);
 
 /**
  * ges_meta_container_register_meta_date_time:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: (allow-none): Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given date time value, and registers the field to only hold a date time
+ * typed value. After calling this, only date time values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold date time typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (date_time, const GstDateTime *, GST_TYPE_DATE_TIME,
     boxed);
 
 /**
  * ges_meta_container_register_meta_string:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: (allow-none): Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given string value, and registers the field to only hold a string
+ * typed value. After calling this, only string values can be set for
+ * this field. The given flags can be set to make this field only
+ * readable after calling this method.
  *
- * Return: %TRUE if the meta could be registered, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold string typed values, with the given @flags,
+ * and the field was successfully set to @value.
  */
 CREATE_REGISTER_STATIC (string, const gchar *, G_TYPE_STRING, string);
 
 /**
  * ges_meta_container_register_meta:
- * @container: Target container
- * @flags: The #GESMetaFlag to be used
- * @meta_item: Name of the meta item to set
- * @value: Value to set
+ * @container: A #GESMetaContainer
+ * @flags: Flags to be used for the registered field
+ * @meta_item: The key for the @container field to register
+ * @value: The value to set for the registered field
  *
- * Sets a static meta on @container. This method lets you define static
- * metadatas, which means that the type of the registered will be the only
- * type accepted for this meta on that particular @container.
+ * Sets the value of the specified field of the meta container to the
+ * given value, and registers the field to only hold a value of the
+ * same type. After calling this, only values of the same type as @value
+ * can be set for this field. The given flags can be set to make this
+ * field only readable after calling this method.
  *
- * Return: %TRUE if the static meta could be added, %FALSE otherwise
+ * Returns: %TRUE if the @meta_item field was successfully registered on
+ * @container to only hold @value types, with the given @flags, and the
+ * field was successfully set to @value.
  */
 gboolean
 ges_meta_container_register_meta (GESMetaContainer * container,
@@ -685,6 +870,23 @@ ges_meta_container_register_meta (GESMetaContainer * container,
   return _set_value (container, meta_item, value);
 }
 
+/**
+ * ges_meta_container_check_meta_registered:
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to check
+ * @flags: (out) (nullable): A destination to get the registered flags of
+ * the field, or %NULL to ignore
+ * @type: (out) (nullable): A destination to get the registered type of
+ * the field, or %NULL to ignore
+ *
+ * Checks whether the specified field has been registered as static, and
+ * gets the registered type and flags of the field, as used in
+ * ges_meta_container_register_meta() and
+ * ges_meta_container_register_static_meta().
+ *
+ * Returns: %TRUE if the @meta_item field has been registered on
+ * @container.
+ */
 gboolean
 ges_meta_container_check_meta_registered (GESMetaContainer * container,
     const gchar * meta_item, GESMetaFlag * flags, GType * type)
@@ -734,56 +936,81 @@ ges_meta_container_get_ ## name (GESMetaContainer *container,    \
 
 /**
  * ges_meta_container_get_boolean:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Gets the current boolean value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
+ *
+ * Returns: %TRUE if the boolean value under @meta_item was copied
+ * to @dest.
  */
 CREATE_GETTER (boolean, gboolean *);
 
 /**
  * ges_meta_container_get_int:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
+ *
+ * Gets the current int value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Returns: %TRUE if the int value under @meta_item was copied
+ * to @dest.
  */
 CREATE_GETTER (int, gint *);
 
 /**
  * ges_meta_container_get_uint:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
+ *
+ * Gets the current uint value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Returns: %TRUE if the uint value under @meta_item was copied
+ * to @dest.
  */
 CREATE_GETTER (uint, guint *);
 
 /**
  * ges_meta_container_get_double:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Gets the current double value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
+ *
+ * Returns: %TRUE if the double value under @meta_item was copied
+ * to @dest.
  */
 CREATE_GETTER (double, gdouble *);
 
 /**
  * ges_meta_container_get_int64:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
+ *
+ * Gets the current int64 value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
  *
- * Gets the value of a given meta item, returns %FALSE if @meta_item
- * can not be found.
+ * Returns: %TRUE if the int64 value under @meta_item was copied
+ * to @dest.
  */
 gboolean
 ges_meta_container_get_int64 (GESMetaContainer * container,
@@ -809,12 +1036,17 @@ ges_meta_container_get_int64 (GESMetaContainer * container,
 
 /**
  * ges_meta_container_get_uint64:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
  *
- * Gets the value of a given meta item, returns %FALSE if @meta_item
- * can not be found.
+ * Gets the current uint64 value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
+ *
+ * Returns: %TRUE if the uint64 value under @meta_item was copied
+ * to @dest.
  */
 gboolean
 ges_meta_container_get_uint64 (GESMetaContainer * container,
@@ -840,12 +1072,17 @@ ges_meta_container_get_uint64 (GESMetaContainer * container,
 
 /**
  * ges_meta_container_get_float:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
+ *
+ * Gets the current float value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
  *
- * Gets the value of a given meta item, returns %FALSE if @meta_item
- * can not be found.
+ * Returns: %TRUE if the float value under @meta_item was copied
+ * to @dest.
  */
 gboolean
 ges_meta_container_get_float (GESMetaContainer * container,
@@ -871,11 +1108,15 @@ ges_meta_container_get_float (GESMetaContainer * container,
 
 /**
  * ges_meta_container_get_string:
- * @container: Target container
- * @meta_item: Name of the meta item to get
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ *
+ * Gets the current string value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Returns: (transfer none): The string value under @meta_item, or %NULL
+ * if it could not be fetched.
  */
 const gchar *
 ges_meta_container_get_string (GESMetaContainer * container,
@@ -893,13 +1134,13 @@ ges_meta_container_get_string (GESMetaContainer * container,
 
 /**
  * ges_meta_container_get_meta:
- * @container: Target container
- * @key: The key name of the meta to retrieve
+ * @container: A #GESMetaContainer
+ * @key: The key for the @container field to get
  *
- * Gets the value of a given meta item, returns NULL if @key
- * can not be found.
+ * Gets the current value of the specified field of the meta container.
  *
- * Returns: the #GValue corresponding to the meta with the given @key.
+ * Returns: (transfer none): The value under @key, or %NULL if @container
+ * does not have the field set.
  */
 const GValue *
 ges_meta_container_get_meta (GESMetaContainer * container, const gchar * key)
@@ -915,23 +1156,67 @@ ges_meta_container_get_meta (GESMetaContainer * container, const gchar * key)
 }
 
 /**
+ * ges_meta_container_get_marker_list:
+ * @container: A #GESMetaContainer
+ * @key: The key for the @container field to get
+ *
+ * Gets the current marker list value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
+ *
+ * Returns: (transfer full): A copy of the marker list value under @key,
+ * or %NULL if it could not be fetched.
+ * Since: 1.18
+ */
+GESMarkerList *
+ges_meta_container_get_marker_list (GESMetaContainer * container,
+    const gchar * key)
+{
+  GstStructure *structure;
+  const GValue *v;
+
+  g_return_val_if_fail (GES_IS_META_CONTAINER (container), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+
+  structure = _meta_container_get_structure (container);
+
+  v = gst_structure_get_value (structure, key);
+
+  if (v == NULL) {
+    return NULL;
+  }
+
+  return GES_MARKER_LIST (g_value_dup_object (v));
+}
+
+/**
  * ges_meta_container_get_date:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Gets the current date value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
+ *
+ * Returns: %TRUE if the date value under @meta_item was copied
+ * to @dest.
  */
 CREATE_GETTER (date, GDate **);
 
 /**
  * ges_meta_container_get_date_time:
- * @container: Target container
- * @meta_item: Name of the meta item to get
- * @dest: (out): Destination to which value of meta item will be copied
+ * @container: A #GESMetaContainer
+ * @meta_item: The key for the @container field to get
+ * @dest: (out): Destination into which the value under @meta_item
+ * should be copied.
+ *
+ * Gets the current date time value of the specified field of the meta
+ * container. If the field does not have a set value, or it is of the
+ * wrong type, the method will fail.
  *
- * Gets the value of a given meta item, returns NULL if @meta_item
- * can not be found.
+ * Returns: %TRUE if the date time value under @meta_item was copied
+ * to @dest.
  */
 CREATE_GETTER (date_time, GstDateTime **);
index b801e07..bc80595 100644 (file)
@@ -16,9 +16,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-
-#ifndef _GES_META_CONTAINER
-#define _GES_META_CONTAINER
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_META_CONTAINER                 (ges_meta_container_get_type ())
-#define GES_META_CONTAINER(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_META_CONTAINER, GESMetaContainer))
-#define GES_IS_META_CONTAINER(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_META_CONTAINER))
 #define GES_META_CONTAINER_GET_INTERFACE (inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GES_TYPE_META_CONTAINER, GESMetaContainerInterface))
+GES_API
+G_DECLARE_INTERFACE(GESMetaContainer, ges_meta_container, GES, META_CONTAINER, GObject);
 
 /**
  * GES_META_FORMATTER_NAME:
  *
- * Name of a formatter it is used as ID of Formater assets (string)
- *
- * The name of the formatter
+ * The name of a formatter, used as the #GESAsset:id for #GESFormatter
+ * assets (string).
  */
 #define GES_META_FORMATTER_NAME                       "name"
 
 /**
  * GES_META_DESCRIPTION:
  *
- * The description of an object, can be used in various context (string)
- *
- * The description
+ * The description of the object, to be used in various contexts (string).
  */
 #define GES_META_DESCRIPTION                         "description"
 
 /**
  * GES_META_FORMATTER_MIMETYPE:
  *
- * Mimetype used for the file produced by a  formatter (string)
- *
- * The mime type
+ * The mimetype used for the file produced by a #GESFormatter (string).
  */
 #define GES_META_FORMATTER_MIMETYPE                   "mimetype"
 
 /**
  * GES_META_FORMATTER_EXTENSION:
  *
- * The extension of the files produced by a formatter (string)
+ * The file extension of files produced by a #GESFormatter (string).
  */
 #define GES_META_FORMATTER_EXTENSION                  "extension"
 
 /**
  * GES_META_FORMATTER_VERSION:
  *
- * The version of a formatter (double)
- *
- * The formatter version
+ * The version of a #GESFormatter (double).
  */
 #define GES_META_FORMATTER_VERSION                    "version"
 
 /**
  * GES_META_FORMATTER_RANK:
  *
- * The rank of a formatter (GstRank)
- *
- * The rank of a formatter
+ * The rank of a #GESFormatter (a #GstRank).
  */
 #define GES_META_FORMATTER_RANK                       "rank"
 
 /**
  * GES_META_VOLUME:
  *
- * The volume, can be used for audio track or layers
- *
- * The volume for a track or a layer, it is register as a float
+ * The volume for a #GESTrack or a #GESLayer (float).
  */
 #define GES_META_VOLUME                              "volume"
 
 /**
  * GES_META_VOLUME_DEFAULT:
  *
- * The default volume
- *
- * The default volume for a track or a layer as a float
+ * The default volume for a #GESTrack or a #GESLayer as a float.
  */
 #define GES_META_VOLUME_DEFAULT                       1.0
 
 /**
  * GES_META_FORMAT_VERSION:
  *
- * The version of the format in which a project is serialized
+ * The version of the format in which a project is serialized (string).
  */
 #define GES_META_FORMAT_VERSION                       "format-version"
 
+/**
+ * GES_META_MARKER_COLOR:
+ *
+ * The ARGB color of a #GESMarker (an AARRGGBB hex as a uint).
+ */
+#define GES_META_MARKER_COLOR                         "marker-color"
+
 typedef struct _GESMetaContainer          GESMetaContainer;
 typedef struct _GESMetaContainerInterface GESMetaContainerInterface;
 
@@ -118,9 +110,6 @@ struct _GESMetaContainerInterface {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_meta_container_get_type (void);
-
 GES_API gboolean
 ges_meta_container_set_boolean     (GESMetaContainer *container,
                                         const gchar* meta_item,
@@ -177,6 +166,17 @@ ges_meta_container_set_meta            (GESMetaContainer * container,
                                         const GValue *value);
 
 GES_API gboolean
+ges_meta_container_set_marker_list     (GESMetaContainer * container,
+                                        const gchar * meta_item,
+                                        const GESMarkerList *list);
+
+GES_API gboolean
+ges_meta_container_register_static_meta (GESMetaContainer * container,
+                                         GESMetaFlag flags,
+                                         const gchar * meta_item,
+                                         GType type);
+
+GES_API gboolean
 ges_meta_container_register_meta_boolean (GESMetaContainer *container,
                                           GESMetaFlag flags,
                                           const gchar* meta_item,
@@ -297,10 +297,22 @@ GES_API const gchar *
 ges_meta_container_get_string      (GESMetaContainer * container,
                                         const gchar * meta_item);
 
+GES_API GESMarkerList *
+ges_meta_container_get_marker_list (GESMetaContainer * container,
+                                    const gchar * key);
+
 GES_API const GValue *
 ges_meta_container_get_meta            (GESMetaContainer * container,
                                         const gchar * key);
-
+/**
+ * GESMetaForeachFunc:
+ * @container: A #GESMetaContainer
+ * @key: The key for one of @container's fields
+ * @value: The set value under @key
+ * @user_data: User data
+ *
+ * A method to be called on all of a meta container's fields.
+ */
 typedef void
 (*GESMetaForeachFunc)                  (const GESMetaContainer *container,
                                         const gchar *key,
@@ -320,4 +332,3 @@ ges_meta_container_add_metas_from_string (GESMetaContainer *container,
                                           const gchar *str);
 
 G_END_DECLS
-#endif /* _GES_META_CONTAINER */
index f6b6c4b..25f600f 100644 (file)
  * @title: GESMultiFileSource
  * @short_description: outputs the video stream from a sequence of images.
  *
- * Outputs the video stream from a given image sequence. The start frame
- * chosen will be determined by the in-point property on the track element.
+ * Outputs the video stream from a given image sequence. The start frame chosen
+ * will be determined by the in-point property on the track element.
+ *
+ * This should not be used anymore, the `imagesequence://` protocol should be
+ * used instead. Check the #imagesequencesrc GStreamer element for more
+ * information.
+ *
+ * Deprecated: 1.18: Use #GESUriSource instead
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -167,7 +173,7 @@ ges_multi_file_uri_new (const gchar * uri)
 }
 
 static GstElement *
-ges_multi_file_source_create_source (GESTrackElement * track_element)
+ges_multi_file_source_create_source (GESSource * source)
 {
   GESMultiFileSource *self;
   GstElement *bin, *src, *decodebin;
@@ -178,7 +184,7 @@ ges_multi_file_source_create_source (GESTrackElement * track_element)
   GESUriSourceAsset *asset;
   GESMultiFileURI *uri_data;
 
-  self = (GESMultiFileSource *) track_element;
+  self = (GESMultiFileSource *) source;
 
   asset =
       GES_URI_SOURCE_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (self)));
@@ -224,7 +230,7 @@ static void
 ges_multi_file_source_class_init (GESMultiFileSourceClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESVideoSourceClass *source_class = GES_VIDEO_SOURCE_CLASS (klass);
+  GESSourceClass *source_class = GES_SOURCE_CLASS (klass);
 
   object_class->get_property = ges_multi_file_source_get_property;
   object_class->set_property = ges_multi_file_source_set_property;
@@ -257,9 +263,7 @@ ges_multi_file_source_init (GESMultiFileSource * self)
   self->priv = ges_multi_file_source_get_instance_private (self);
 }
 
-/**
- * ges_multi_file_source_new:
- * @uri: the URI the source should control
+/* @uri: the URI the source should control
  *
  * Creates a new #GESMultiFileSource for the provided @uri.
  *
@@ -268,6 +272,12 @@ ges_multi_file_source_init (GESMultiFileSource * self)
 GESMultiFileSource *
 ges_multi_file_source_new (gchar * uri)
 {
-  return g_object_new (GES_TYPE_MULTI_FILE_SOURCE, "uri", uri,
-      "track-type", GES_TRACK_TYPE_VIDEO, NULL);
+  GESMultiFileSource *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_MULTI_FILE_SOURCE, uri, NULL);
+
+  res = GES_MULTI_FILE_SOURCE (ges_asset_extract (asset, NULL));
+  res->uri = g_strdup (uri);
+  gst_object_unref (asset);
+
+  return res;
 }
index ebd52aa..33da6bd 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_MULTI_FILE_SOURCE
-#define _GES_MULTI_FILE_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 
 G_BEGIN_DECLS
 #define GES_TYPE_MULTI_FILE_SOURCE ges_multi_file_source_get_type()
-#define GES_MULTI_FILE_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_MULTI_FILE_SOURCE, GESMultiFileSource))
-#define GES_MULTI_FILE_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_MULTI_FILE_SOURCE, GESMultiFileSourceClass))
-#define GES_IS_MULTI_FILE_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_MULTI_FILE_SOURCE))
-#define GES_IS_MULTI_FILE_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_MULTI_FILE_SOURCE))
-#define GES_MULTI_FILE_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_MULTI_FILE_SOURCE, GESMultiFileSourceClass))
-typedef struct _GESMultiFileSourcePrivate GESMultiFileSourcePrivate;
+GES_DECLARE_TYPE(MultiFileSource, multi_file_source, MULTI_FILE_SOURCE);
 
 /**
  * GESMultiFileSource:
@@ -63,12 +52,8 @@ struct _GESMultiFileSourceClass
 };
 
 GES_API
-GType ges_multi_file_source_get_type (void);
-
-GES_API
 GESMultiFileSource *ges_multi_file_source_new (gchar * uri);
 
 #define GES_MULTI_FILE_URI_PREFIX "multifile://"
 
 G_END_DECLS
-#endif /* _GES_MULTI_FILE_SOURCE */
index 205a4df..cb4e195 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_OPERATION_CLIP
-#define _GES_OPERATION_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_OPERATION_CLIP ges_operation_clip_get_type()
-
-#define GES_OPERATION_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_OPERATION_CLIP, GESOperationClip))
-
-#define GES_OPERATION_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_OPERATION_CLIP, GESOperationClipClass))
-
-#define GES_IS_OPERATION_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_OPERATION_CLIP))
-
-#define GES_IS_OPERATION_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_OPERATION_CLIP))
-
-#define GES_OPERATION_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_OPERATION_CLIP, GESOperationClipClass))
-
-typedef struct _GESOperationClipPrivate GESOperationClipPrivate;
+GES_DECLARE_TYPE(OperationClip, operation_clip, OPERATION_CLIP);
 
 /**
  * GESOperationClip:
@@ -74,10 +57,4 @@ struct _GESOperationClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_operation_clip_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_OPERATION_CLIP */
-
index 15a7102..4b0bece 100644 (file)
 #include "ges-track-element.h"
 #include "ges-operation.h"
 
-struct _GESOperationPrivate
-{
-  /* Dummy variable */
-  void *nothing;
-};
-
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESOperation, ges_operation,
-    GES_TYPE_TRACK_ELEMENT);
+G_DEFINE_ABSTRACT_TYPE (GESOperation, ges_operation, GES_TYPE_TRACK_ELEMENT);
 
 static void
 ges_operation_class_init (GESOperationClass * klass)
@@ -51,5 +44,4 @@ ges_operation_class_init (GESOperationClass * klass)
 static void
 ges_operation_init (GESOperation * self)
 {
-  self->priv = ges_operation_get_instance_private (self);
 }
index 9bf7cc5..6d25a89 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_OPERATION
-#define _GES_OPERATION
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_OPERATION ges_operation_get_type()
-
-#define GES_OPERATION(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_OPERATION, GESOperation))
-
-#define GES_OPERATION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_OPERATION, GESOperationClass))
-
-#define GES_IS_OPERATION(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_OPERATION))
-
-#define GES_IS_OPERATION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_OPERATION))
-
-#define GES_OPERATION_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_OPERATION, GESOperationClass))
-
-typedef struct _GESOperationPrivate GESOperationPrivate;
+GES_DECLARE_TYPE(Operation, operation, OPERATION);
 
 /**
  * GESOperation:
@@ -76,9 +59,4 @@ struct _GESOperationClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_operation_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_OPERATION */
index 94e7c06..d556bf0 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_OVERLAY_CLIP
-#define _GES_OVERLAY_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_OVERLAY_CLIP ges_overlay_clip_get_type()
-
-#define GES_OVERLAY_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_OVERLAY_CLIP, GESOverlayClip))
-
-#define GES_OVERLAY_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_OVERLAY_CLIP, GESOverlayClipClass))
-
-#define GES_IS_OVERLAY_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_OVERLAY_CLIP))
-
-#define GES_IS_OVERLAY_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_OVERLAY_CLIP))
-
-#define GES_OVERLAY_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_OVERLAY_CLIP, GESOverlayClipClass))
-
-typedef struct _GESOverlayClipPrivate GESOverlayClipPrivate;
+GES_DECLARE_TYPE(OverlayClip, overlay_clip, OVERLAY_CLIP);
 
 /**
  * GESOverlayClip:
@@ -72,11 +55,4 @@ struct _GESOverlayClipClass {
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
 };
-
-GES_API
-GType ges_overlay_clip_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_OVERLAY_CLIP */
-
index 79248f1..9934059 100644 (file)
  * SECTION:gespipeline
  * @title: GESPipeline
  * @short_description: Convenience GstPipeline for editing.
+ * @symbols:
+ * - ges_play_sink_convert_frame
  *
- * #GESPipeline allows developers to view and render #GESTimeline
- * in a simple fashion.
- * Its usage is inspired by the 'playbin' element from gst-plugins-base.
+ * A #GESPipeline can take an audio-video #GESTimeline and conveniently
+ * link its #GESTrack-s to an internal #playsink element, for
+ * preview/playback, and an internal #encodebin element, for rendering.
+ * You can switch between these modes using ges_pipeline_set_mode().
+ *
+ * You can choose the specific audio and video sinks used for previewing
+ * the timeline by setting the #GESPipeline:audio-sink and
+ * #GESPipeline:video-sink properties.
+ *
+ * You can set the encoding and save location used in rendering by calling
+ * ges_pipeline_set_render_settings().
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -298,7 +308,8 @@ ges_pipeline_class_init (GESPipelineClass * klass)
   /**
    * GESPipeline:audio-sink:
    *
-   * Audio sink for the preview.
+   * The audio sink used for preview. This exposes the
+   * #playsink:audio-sink property of the internal #playsink.
    */
   properties[PROP_AUDIO_SINK] = g_param_spec_object ("audio-sink", "Audio Sink",
       "Audio sink for the preview.",
@@ -307,7 +318,8 @@ ges_pipeline_class_init (GESPipelineClass * klass)
   /**
    * GESPipeline:video-sink:
    *
-   * Video sink for the preview.
+   * The video sink used for preview. This exposes the
+   * #playsink:video-sink property of the internal #playsink.
    */
   properties[PROP_VIDEO_SINK] = g_param_spec_object ("video-sink", "Video Sink",
       "Video sink for the preview.",
@@ -316,29 +328,35 @@ ges_pipeline_class_init (GESPipelineClass * klass)
   /**
    * GESPipeline:timeline:
    *
-   * Timeline to use in this pipeline. See also
-   * ges_pipeline_set_timeline() for more info.
+   * The timeline used by this pipeline, whose content it will play and
+   * render, or %NULL if the pipeline does not yet have a timeline.
+   *
+   * Note that after you set the timeline for the first time, subsequent
+   * calls to change the timeline will fail.
    */
   properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
-      "Timeline to use in this pipeline. See also "
-      "ges_pipeline_set_timeline() for more info.",
+      "The timeline to use in this pipeline.",
       GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
   /**
    * GESPipeline:mode:
    *
-   * Pipeline mode. See ges_pipeline_set_mode() for more
-   * info.
+   * The pipeline's mode. In preview mode (for audio or video, or both)
+   * the pipeline can display the timeline's content to an end user. In
+   * rendering mode the pipeline can encode the timeline's content and
+   * save it to a file.
    */
   properties[PROP_MODE] = g_param_spec_flags ("mode", "Mode",
-      "Pipeline mode. See ges_pipeline_set_mode() for more info.",
+      "The pipeline's mode.",
       GES_TYPE_PIPELINE_FLAGS, DEFAULT_TIMELINE_MODE,
       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
   /**
-   * GESPipeline::audio-filter
+   * GESPipeline:audio-filter:
    *
-   * The audio filter(s) to apply during playback right before the audio sink
+   * The audio filter(s) to apply during playback in preview mode,
+   * immediately before the #GESPipeline:audio-sink. This exposes the
+   * #playsink:audio-filter property of the internal #playsink.
    *
    * Since: 1.6.0
    */
@@ -348,9 +366,11 @@ ges_pipeline_class_init (GESPipelineClass * klass)
       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
   /**
-   * GESPipeline::video-filter
+   * GESPipeline:video-filter:
    *
-   * The video filter(s) to apply during playback right before the video sink
+   * The video filter(s) to apply during playback in preview mode,
+   * immediately before the #GESPipeline:video-sink. This exposes the
+   * #playsink:video-filter property of the internal #playsink.
    *
    * Since: 1.6.0
    */
@@ -404,9 +424,9 @@ no_encodebin:
 /**
  * ges_pipeline_new:
  *
- * Creates a new conveninence #GESPipeline.
+ * Creates a new pipeline.
  *
- * Returns: (transfer floating): the new #GESPipeline.
+ * Returns: (transfer floating): The newly created pipeline.
  */
 GESPipeline *
 ges_pipeline_new (void)
@@ -422,37 +442,8 @@ static gboolean
 _track_is_compatible_with_profile (GESPipeline * self, GESTrack * track,
     GstEncodingProfile * prof)
 {
-  if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) {
-    if (self->priv->mode == GES_PIPELINE_MODE_SMART_RENDER) {
-      GstCaps *ocaps, *rcaps;
-
-      GST_DEBUG ("Smart Render mode, setting input caps");
-      ocaps = gst_encoding_profile_get_input_caps (prof);
-      ocaps = gst_caps_make_writable (ocaps);
-      if (track->type == GES_TRACK_TYPE_AUDIO)
-        rcaps = gst_caps_new_empty_simple ("audio/x-raw");
-      else
-        rcaps = gst_caps_new_empty_simple ("video/x-raw");
-      gst_caps_append (ocaps, rcaps);
-      ges_track_set_caps (track, ocaps);
-      gst_caps_unref (ocaps);
-    } else {
-      GstCaps *caps = NULL;
-
-      /* Raw preview or rendering mode */
-      if (track->type == GES_TRACK_TYPE_VIDEO)
-        caps = gst_caps_new_empty_simple ("video/x-raw");
-      else if (track->type == GES_TRACK_TYPE_AUDIO)
-        caps = gst_caps_new_empty_simple ("audio/x-raw");
-
-      if (caps) {
-        ges_track_set_caps (track, caps);
-        gst_caps_unref (caps);
-      }
-    }
-
+  if (TRACK_COMPATIBLE_PROFILE (track->type, prof))
     return TRUE;
-  }
 
   return FALSE;
 }
@@ -760,6 +751,7 @@ _link_track (GESPipeline * self, GESTrack * track)
   GstCaps *caps;
   GstPadLinkReturn lret;
   gboolean reconfigured = FALSE;
+  gboolean ignore;
 
   pad = ges_timeline_get_pad_for_track (self->priv->timeline, track);
   if (G_UNLIKELY (!pad)) {
@@ -774,18 +766,32 @@ _link_track (GESPipeline * self, GESTrack * track)
       GST_DEBUG_PAD_NAME (pad), caps);
   gst_caps_unref (caps);
 
+  /* FIXME: provide a way for the user to select which audio, video or
+   * text track to use in preview mode when a timeline has multiple audio,
+   * video or text tracks. Also provide a way to switch between these. */
+
   /* Don't connect track if it's not going to be used */
-  if (track->type == GES_TRACK_TYPE_VIDEO &&
-      !(self->priv->mode & GES_PIPELINE_MODE_PREVIEW_VIDEO) &&
-      !(self->priv->mode & GES_PIPELINE_MODE_RENDER) &&
-      !(self->priv->mode & GES_PIPELINE_MODE_SMART_RENDER)) {
-    GST_DEBUG_OBJECT (self, "Video track... but we don't need it. Not linking");
+  ignore = TRUE;
+  /* only support audio and video. Technically, preview mode could support
+   * text quite easily, but this isn't yet the case for rendering using
+   * encodebin */
+  if (track->type == GES_TRACK_TYPE_AUDIO ||
+      track->type == GES_TRACK_TYPE_VIDEO) {
+    if (IN_RENDERING_MODE (self))
+      ignore = FALSE;
+    else if (track->type == GES_TRACK_TYPE_VIDEO &&
+        self->priv->mode & GES_PIPELINE_MODE_PREVIEW_VIDEO)
+      ignore = FALSE;
+    else if (track->type == GES_TRACK_TYPE_AUDIO &&
+        self->priv->mode & GES_PIPELINE_MODE_PREVIEW_AUDIO)
+      ignore = FALSE;
   }
-  if (track->type == GES_TRACK_TYPE_AUDIO &&
-      !(self->priv->mode & GES_PIPELINE_MODE_PREVIEW_AUDIO) &&
-      !(self->priv->mode & GES_PIPELINE_MODE_RENDER) &&
-      !(self->priv->mode & GES_PIPELINE_MODE_SMART_RENDER)) {
-    GST_DEBUG_OBJECT (self, "Audio track... but we don't need it. Not linking");
+
+  if (ignore) {
+    gst_object_unref (pad);
+    GST_DEBUG_OBJECT (self, "Ignoring track (type %u). Not linking",
+        track->type);
+    return;
   }
 
   /* Get an existing chain or create it */
@@ -843,14 +849,14 @@ _link_track (GESPipeline * self, GESTrack * track)
 
     /* Request a sinkpad from playsink */
     if (G_UNLIKELY (!(sinkpad =
-                gst_element_get_request_pad (self->priv->playsink,
+                gst_element_request_pad_simple (self->priv->playsink,
                     sinkpad_name)))) {
       GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
           (NULL), ("Could not get a pad from playsink for %s", sinkpad_name));
       goto error;
     }
 
-    tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
+    tmppad = gst_element_request_pad_simple (chain->tee, "src_%u");
     lret = gst_pad_link_full (tmppad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
     if (G_UNLIKELY (lret != GST_PAD_LINK_OK)) {
       gst_object_unref (tmppad);
@@ -906,7 +912,7 @@ _link_track (GESPipeline * self, GESTrack * track)
       GST_INFO_OBJECT (track, "Linked to %" GST_PTR_FORMAT, sinkpad);
     }
 
-    tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
+    tmppad = gst_element_request_pad_simple (chain->tee, "src_%u");
     if (G_UNLIKELY (gst_pad_link_full (tmppad, chain->encodebinpad,
                 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
       GST_ERROR_OBJECT (self, "Couldn't link track pad to encodebin");
@@ -980,16 +986,23 @@ _unlink_track (GESPipeline * self, GESTrack * track)
 
 /**
  * ges_pipeline_set_timeline:
- * @pipeline: a #GESPipeline
- * @timeline: (transfer full): the #GESTimeline to set on the @pipeline.
+ * @pipeline: A #GESPipeline
+ * @timeline: (transfer full): The timeline to set for @pipeline
  *
- * Sets the timeline to use in this pipeline.
+ * Takes the given timeline and sets it as the #GESPipeline:timeline for
+ * the pipeline.
  *
- * The reference to the @timeline will be stolen by the @pipeline.
+ * Note that you should only call this method once on a given pipeline
+ * because a pipeline can not have its #GESPipeline:timeline changed after
+ * it has been set.
  *
- * Returns: %TRUE if the @timeline could be successfully set on the @pipeline,
- * else %FALSE.
+ * Returns: %TRUE if @timeline was successfully given to @pipeline.
  */
+/* FIXME: allow us to set a new timeline and a NULL timeline */
+/* FIXME 2.0: since the method can fail, (transfer none) would be more
+ * appropriate for the timeline argument (currently, it is only
+ * transferred if the method is successful, which makes the memory
+ * transfer mixed!) */
 gboolean
 ges_pipeline_set_timeline (GESPipeline * pipeline, GESTimeline * timeline)
 {
@@ -1019,19 +1032,18 @@ ges_pipeline_set_timeline (GESPipeline * pipeline, GESTimeline * timeline)
 
 /**
  * ges_pipeline_set_render_settings:
- * @pipeline: a #GESPipeline
- * @output_uri: the URI to which the timeline will be rendered
- * @profile: the #GstEncodingProfile to use to render the timeline.
- *
- * Specify where the pipeline shall be rendered and with what settings.
+ * @pipeline: A #GESPipeline
+ * @output_uri: The URI to save the #GESPipeline:timeline rendering
+ * result to
+ * @profile: The encoding to use for rendering the #GESPipeline:timeline
  *
- * A copy of @profile and @output_uri will be done internally, the caller can
- * safely free those values afterwards.
+ * Specifies encoding setting to be used by the pipeline to render its
+ * #GESPipeline:timeline, and where the result should be written to.
  *
- * This method must be called before setting the pipeline mode to
- * #GES_PIPELINE_MODE_RENDER
+ * This method **must** be called before setting the pipeline mode to
+ * #GES_PIPELINE_MODE_RENDER.
  *
- * Returns: %TRUE if the settings were aknowledged properly, else %FALSE
+ * Returns: %TRUE if the settings were successfully set on @pipeline.
  */
 gboolean
 ges_pipeline_set_render_settings (GESPipeline * pipeline,
@@ -1039,12 +1051,13 @@ ges_pipeline_set_render_settings (GESPipeline * pipeline,
 {
   GError *err = NULL;
   GstEncodingProfile *set_profile;
+  guint n_videotracks = 0, n_audiotracks = 0;
 
   g_return_val_if_fail (GES_IS_PIPELINE (pipeline), FALSE);
   CHECK_THREAD (pipeline);
 
   /*  FIXME Properly handle multi track, for now GESPipeline
-   *  only hanles single track per type, so we should just set the
+   *  only handles single track per type, so we should just set the
    *  presence to 1.
    */
   if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
@@ -1054,33 +1067,62 @@ ges_pipeline_set_render_settings (GESPipeline * pipeline,
     GList *tmptrack, *tracks =
         ges_timeline_get_tracks (pipeline->priv->timeline);
 
+    for (tmptrack = tracks; tmptrack; tmptrack = tmptrack->next) {
+      if (GES_IS_AUDIO_TRACK (tmptrack->data))
+        n_audiotracks++;
+      else if (GES_IS_VIDEO_TRACK (tmptrack->data))
+        n_videotracks++;
+    }
+    g_list_free_full (tracks, gst_object_unref);
+
     for (; tmpprofiles; tmpprofiles = tmpprofiles->next) {
-      for (tmptrack = tracks; tmptrack; tmptrack = tmptrack->next) {
-        if ((GST_IS_ENCODING_AUDIO_PROFILE (tmpprofiles->data) &&
-                GES_IS_AUDIO_TRACK (tmptrack->data)) ||
-            (GST_IS_ENCODING_VIDEO_PROFILE (tmpprofiles->data) &&
-                GES_IS_VIDEO_TRACK (tmptrack->data))) {
-          GST_DEBUG_OBJECT (pipeline, "Setting presence to 1!");
-          gst_encoding_profile_set_presence (tmpprofiles->data, 1);
-          gst_encoding_profile_set_allow_dynamic_output (tmpprofiles->data,
-              FALSE);
+      if (!gst_encoding_profile_is_enabled (tmpprofiles->data))
+        continue;
+
+      if (GST_IS_ENCODING_AUDIO_PROFILE (tmpprofiles->data)) {
+        if (n_audiotracks) {
+          n_audiotracks--;
+        } else {
+          GST_INFO_OBJECT (pipeline, "No audio track but got an audio profile, "
+              " make it optional: %" GST_PTR_FORMAT, tmpprofiles);
+          gst_encoding_profile_set_presence (tmpprofiles->data, 0);
+
+          continue;
+        }
+      } else if (GST_IS_ENCODING_VIDEO_PROFILE (tmpprofiles->data)) {
+        if (n_videotracks) {
+          n_videotracks--;
+        } else {
+          GST_INFO_OBJECT (pipeline, "No video track but got a video profile, "
+              " make it optional: %" GST_PTR_FORMAT, tmpprofiles);
+          gst_encoding_profile_set_presence (tmpprofiles->data, 0);
+
+          continue;
         }
+      } else {
+        continue;
       }
-    }
 
-    g_list_free_full (tracks, gst_object_unref);
+      GST_DEBUG_OBJECT (pipeline, "Setting presence to 1!");
+      gst_encoding_profile_set_single_segment (tmpprofiles->data, TRUE);
+      if (gst_encoding_profile_get_presence (tmpprofiles->data) == 0)
+        gst_encoding_profile_set_presence (tmpprofiles->data, 1);
+      gst_encoding_profile_set_allow_dynamic_output (tmpprofiles->data, FALSE);
+    }
   }
 
   /* Clear previous URI sink if it existed */
-  /* FIXME : We should figure out if it was added to the pipeline,
-   * and if so, remove it. */
   if (pipeline->priv->urisink) {
-    gst_object_unref (pipeline->priv->urisink);
+    GstObject *sink_parent =
+        gst_object_get_parent (GST_OBJECT (pipeline->priv->urisink));
+    if (sink_parent == GST_OBJECT (pipeline))
+      gst_bin_remove (GST_BIN (pipeline), pipeline->priv->urisink);
     pipeline->priv->urisink = NULL;
+    gst_clear_object (&sink_parent);
   }
 
   pipeline->priv->urisink =
-      gst_element_make_from_uri (GST_URI_SINK, output_uri, "urisink", &err);
+      gst_element_make_from_uri (GST_URI_SINK, output_uri, NULL, &err);
   if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
     GST_ERROR_OBJECT (pipeline, "Couldn't not create sink for URI %s: '%s'",
         output_uri, ((err
@@ -1103,7 +1145,7 @@ ges_pipeline_set_render_settings (GESPipeline * pipeline,
     return FALSE;
   }
 
-  /* We got a referencer when getting back the profile */
+  /* We got a reference when getting back the profile */
   pipeline->priv->profile = profile;
 
   return TRUE;
@@ -1111,9 +1153,11 @@ ges_pipeline_set_render_settings (GESPipeline * pipeline,
 
 /**
  * ges_pipeline_get_mode:
- * @pipeline: a #GESPipeline
+ * @pipeline: A #GESPipeline
  *
- * Returns: the #GESPipelineFlags currently in use.
+ * Gets the #GESPipeline:mode of the pipeline.
+ *
+ * Returns: The current mode of @pipeline.
  **/
 GESPipelineFlags
 ges_pipeline_get_mode (GESPipeline * pipeline)
@@ -1123,17 +1167,21 @@ ges_pipeline_get_mode (GESPipeline * pipeline)
 
 /**
  * ges_pipeline_set_mode:
- * @pipeline: a #GESPipeline
- * @mode: the #GESPipelineFlags to use
+ * @pipeline: A #GESPipeline
+ * @mode: The mode to set for @pipeline
+ *
+ * Sets the #GESPipeline:mode of the pipeline.
  *
- * switches the @pipeline to the specified @mode. The default mode when
- * creating a #GESPipeline is #GES_PIPELINE_MODE_PREVIEW.
+ * Note that the pipeline will be set to #GST_STATE_NULL during this call to
+ * perform the necessary changes. You will need to set the state again yourself
+ * after calling this.
  *
- * Note: The @pipeline will be set to #GST_STATE_NULL during this call due to
- * the internal changes that happen. The caller will therefore have to
- * set the @pipeline to the requested state after calling this method.
+ * > **NOTE**: [Rendering settings](ges_pipeline_set_render_settings) need to be
+ * > set before setting @mode to #GES_PIPELINE_MODE_RENDER or
+ * > #GES_PIPELINE_MODE_SMART_RENDER, the call to this method will fail
+ * > otherwise.
  *
- * Returns: %TRUE if the mode was properly set, else %FALSE.
+ * Returns: %TRUE if the mode of @pipeline was successfully set to @mode.
  **/
 gboolean
 ges_pipeline_set_mode (GESPipeline * pipeline, GESPipelineFlags mode)
@@ -1177,25 +1225,10 @@ ges_pipeline_set_mode (GESPipeline * pipeline, GESPipelineFlags mode)
   if ((pipeline->priv->mode &
           (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER)) &&
       !(mode & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER))) {
-    GList *tmp;
-    GstCaps *caps;
-
-    for (tmp = pipeline->priv->timeline->tracks; tmp; tmp = tmp->next) {
-      GESTrackType type = GES_TRACK (tmp->data)->type;
-
-      if (type == GES_TRACK_TYPE_AUDIO)
-        caps = gst_caps_new_empty_simple ("audio/x-raw");
-      else if (type == GES_TRACK_TYPE_VIDEO)
-        caps = gst_caps_new_empty_simple ("video/x-raw");
-      else
-        continue;
-
-      ges_track_set_caps (GES_TRACK (tmp->data), caps);
-      gst_caps_unref (caps);
-    }
 
     /* Disable render bin */
     GST_DEBUG ("Disabling rendering bin");
+    ges_timeline_thaw_commit (pipeline->priv->timeline);
     gst_object_ref (pipeline->priv->encodebin);
     gst_object_ref (pipeline->priv->urisink);
     gst_bin_remove_many (GST_BIN_CAST (pipeline),
@@ -1217,7 +1250,8 @@ ges_pipeline_set_mode (GESPipeline * pipeline, GESPipelineFlags mode)
       (mode & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER))) {
     /* Adding render bin */
     GST_DEBUG ("Adding render bin");
-
+    /* in render mode the commit needs to be locked, see #136 */
+    ges_timeline_freeze_commit (pipeline->priv->timeline);
     if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
       GST_ERROR_OBJECT (pipeline, "Output URI not set !");
       return FALSE;
@@ -1237,6 +1271,11 @@ ges_pipeline_set_mode (GESPipeline * pipeline, GESPipelineFlags mode)
         pipeline->priv->urisink, "sink", GST_PAD_LINK_CHECK_NOTHING);
   }
 
+  if (pipeline->priv->timeline) {
+    ges_timeline_set_smart_rendering (pipeline->priv->timeline,
+        (mode & GES_PIPELINE_MODE_SMART_RENDER) != 0);
+  }
+
   /* FIXUPS */
   /* FIXME
    * If we are rendering, set playsink to sync=False,
@@ -1249,17 +1288,20 @@ ges_pipeline_set_mode (GESPipeline * pipeline, GESPipelineFlags mode)
 
 /**
  * ges_pipeline_get_thumbnail:
- * @self: a #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
- * @caps: (transfer none): caps specifying current format. Use %GST_CAPS_ANY
- * for native size.
+ * @self: A #GESPipeline in #GST_STATE_PLAYING or #GST_STATE_PAUSED
+ * @caps: (transfer none): Some caps to specifying the desired format, or
+ * #GST_CAPS_ANY to use the native format
+ *
+ * Gets a sample from the pipeline of the currently displayed image in
+ * preview, in the specified format.
  *
- * Returns a #GstSample with the currently playing image in the format specified by
- * caps. The caller should free the sample with #gst_sample_unref when finished. If ANY
- * caps are specified, the information will be returned in the whatever format
- * is currently used by the sink. This information can be retrieve from caps
- * associated with the buffer.
+ * Note that if you use "ANY" caps for @caps, then the current format of
+ * the image is used. You can retrieve these caps from the returned sample
+ * with gst_sample_get_caps().
  *
- * Returns: (transfer full) (nullable): a #GstSample or %NULL
+ * Returns: (transfer full): A sample of @self's current image preview in
+ * the format given by @caps, or %NULL if an error prevented fetching the
+ * sample.
  */
 
 GstSample *
@@ -1282,18 +1324,21 @@ ges_pipeline_get_thumbnail (GESPipeline * self, GstCaps * caps)
 
 /**
  * ges_pipeline_save_thumbnail:
- * @self: a #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
- * @width: the requested width or -1 for native size
- * @height: the requested height or -1 for native size
- * @format: a string specifying the desired mime type (for example,
- * image/jpeg)
- * @location: the path to save the thumbnail
+ * @self: A #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
+ * @width: The requested pixel width of the image, or -1 to use the native
+ * size
+ * @height: The requested pixel height of the image, or -1 to use the
+ * native size
+ * @format: The desired mime type (for example, "image/jpeg")
+ * @location: The path to save the thumbnail to
  * @error: (out) (allow-none) (transfer full): An error to be set in case
- * something wrong happens or %NULL
+ * something goes wrong, or %NULL to ignore
  *
- * Saves the current frame to the specified @location.
+ * Saves the currently displayed image of the pipeline in preview to the
+ * given location, in the specified dimensions and format.
  *
- * Returns: %TRUE if the thumbnail was properly save, else %FALSE.
+ * Returns: %TRUE if @self's current image preview was successfully saved
+ * to @location using the given @format, @height and @width.
  */
 gboolean
 ges_pipeline_save_thumbnail (GESPipeline * self, int width, int
@@ -1340,20 +1385,21 @@ ges_pipeline_save_thumbnail (GESPipeline * self, int width, int
 
 /**
  * ges_pipeline_get_thumbnail_rgb24:
- * @self: a #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
- * @width: the requested width or -1 for native size
- * @height: the requested height or -1 for native size
+ * @self: A #GESPipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
+ * @width: The requested pixel width of the image, or -1 to use the native
+ * size
+ * @height: The requested pixel height of the image, or -1 to use the
+ * native size
  *
- * A convenience method for @ges_pipeline_get_thumbnail which
- * returns a buffer in 24-bit RGB, optionally scaled to the specified width
- * and height. If -1 is specified for either dimension, it will be left at
- * native size. You can retreive this information from the caps associated
- * with the buffer.
+ * Gets a sample from the pipeline of the currently displayed image in
+ * preview, in the 24-bit "RGB" format and of the desired width and
+ * height.
  *
- * The caller is responsible for unreffing the returned sample with
- * #gst_sample_unref.
+ * See ges_pipeline_get_thumbnail().
  *
- * Returns: (transfer full) (nullable): a #GstSample or %NULL
+ * Returns: (transfer full): A sample of @self's current image preview in
+ * the "RGB" format, scaled to @width and @height, or %NULL if an error
+ * prevented fetching the sample.
  */
 
 GstSample *
@@ -1381,15 +1427,11 @@ ges_pipeline_get_thumbnail_rgb24 (GESPipeline * self, gint width, gint height)
 
 /**
  * ges_pipeline_preview_get_video_sink:
- * @self: a #GESPipeline
+ * @self: A #GESPipeline
  *
- * Obtains a pointer to playsink's video sink element that is used for
- * displaying video when the #GESPipeline is in %GES_PIPELINE_MODE_PREVIEW
+ * Gets the #GESPipeline:video-sink of the pipeline.
  *
- * The caller is responsible for unreffing the returned element with
- * #gst_object_unref.
- *
- * Returns: (transfer full): a pointer to the playsink video sink #GstElement
+ * Returns: (transfer full): The video sink used by @self for preview.
  */
 GstElement *
 ges_pipeline_preview_get_video_sink (GESPipeline * self)
@@ -1406,11 +1448,10 @@ ges_pipeline_preview_get_video_sink (GESPipeline * self)
 
 /**
  * ges_pipeline_preview_set_video_sink:
- * @self: a #GESPipeline in %GST_STATE_NULL
- * @sink: (transfer none): a video sink #GstElement
+ * @self: A #GESPipeline in #GST_STATE_NULL
+ * @sink: (transfer none): A video sink for @self to use for preview
  *
- * Sets playsink's video sink element that is used for displaying video when
- * the #GESPipeline is in %GES_PIPELINE_MODE_PREVIEW
+ * Sets the #GESPipeline:video-sink of the pipeline.
  */
 void
 ges_pipeline_preview_set_video_sink (GESPipeline * self, GstElement * sink)
@@ -1423,15 +1464,11 @@ ges_pipeline_preview_set_video_sink (GESPipeline * self, GstElement * sink)
 
 /**
  * ges_pipeline_preview_get_audio_sink:
- * @self: a #GESPipeline
- *
- * Obtains a pointer to playsink's audio sink element that is used for
- * displaying audio when the #GESPipeline is in %GES_PIPELINE_MODE_PREVIEW
+ * @self: A #GESPipeline
  *
- * The caller is responsible for unreffing the returned element with
- * #gst_object_unref.
+ * Gets the #GESPipeline:audio-sink of the pipeline.
  *
- * Returns: (transfer full): a pointer to the playsink audio sink #GstElement
+ * Returns: (transfer full): The audio sink used by @self for preview.
  */
 GstElement *
 ges_pipeline_preview_get_audio_sink (GESPipeline * self)
@@ -1448,11 +1485,10 @@ ges_pipeline_preview_get_audio_sink (GESPipeline * self)
 
 /**
  * ges_pipeline_preview_set_audio_sink:
- * @self: a #GESPipeline in %GST_STATE_NULL
- * @sink: (transfer none): a audio sink #GstElement
+ * @self: A #GESPipeline in #GST_STATE_NULL
+ * @sink: (transfer none): A audio sink for @self to use for preview
  *
- * Sets playsink's audio sink element that is used for displaying audio when
- * the #GESPipeline is in %GES_PIPELINE_MODE_PREVIEW
+ * Sets the #GESPipeline:audio-sink of the pipeline.
  */
 void
 ges_pipeline_preview_set_audio_sink (GESPipeline * self, GstElement * sink)
index 6e564d5..0d3ff4b 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_PIPELINE
-#define _GES_PIPELINE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_PIPELINE ges_pipeline_get_type()
-
-#define GES_PIPELINE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_PIPELINE, GESPipeline))
-
-#define GES_PIPELINE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_PIPELINE, GESPipelineClass))
-
-#define GES_IS_PIPELINE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_PIPELINE))
-
-#define GES_IS_PIPELINE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_PIPELINE))
-
-#define GES_PIPELINE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_PIPELINE, GESPipelineClass))
-
-typedef struct _GESPipelinePrivate GESPipelinePrivate;
+GES_DECLARE_TYPE(Pipeline, pipeline, PIPELINE);
 
 /**
  * GESPipeline:
@@ -76,9 +59,6 @@ struct _GESPipelineClass {
 };
 
 GES_API
-GType ges_pipeline_get_type (void);
-
-GES_API
 GESPipeline* ges_pipeline_new (void);
 
 GES_API
@@ -123,6 +103,3 @@ ges_pipeline_preview_set_audio_sink (GESPipeline * self,
     GstElement * sink);
 
 G_END_DECLS
-
-#endif /* _GES_PIPELINE */
-
index 38fd0a2..5bc6b8d 100644 (file)
@@ -229,6 +229,8 @@ parse_metadatas (GESFormatter * self)
           (gchar *) xmlGetProp (node, cur_attr->name));
     }
   }
+
+  xmlXPathFreeObject (xpathObj);
 }
 
 static void
@@ -437,7 +439,7 @@ track_element_added_cb (GESClip * clip,
     priv->sources_to_load = g_list_remove (priv->sources_to_load, clip);
     if (!priv->sources_to_load && GES_FORMATTER (formatter)->project)
       ges_project_set_loaded (GES_FORMATTER (formatter)->project,
-          GES_FORMATTER (formatter));
+          GES_FORMATTER (formatter), NULL);
   }
 
   /* Disconnect the signal */
@@ -474,7 +476,7 @@ make_source (GESFormatter * self, GList * reflist, GHashTable * source_table)
       layer = ges_layer_new ();
       g_object_set (layer, "auto-transition", TRUE, "priority", prio, NULL);
       ges_timeline_add_layer (self->timeline, layer);
-      g_hash_table_insert (priv->layers_table, g_memdup (&prio,
+      g_hash_table_insert (priv->layers_table, g_memdup2 (&prio,
               sizeof (guint64)), layer);
     }
 
@@ -539,7 +541,11 @@ make_source (GESFormatter * self, GList * reflist, GHashTable * source_table)
       effect_table =
           g_hash_table_lookup (props_table, (gchar *) "effect_props");
 
-      ges_container_add (GES_CONTAINER (src), GES_TIMELINE_ELEMENT (effect));
+      if (!ges_container_add (GES_CONTAINER (src),
+              GES_TIMELINE_ELEMENT (effect))) {
+        GST_ERROR ("%p could not add %p while"
+            " reloading, this should never happen", src, effect);
+      }
 
       if (!g_strcmp0 (active, (gchar *) "(bool)False"))
         ges_track_element_set_active (GES_TRACK_ELEMENT (effect), FALSE);
@@ -668,7 +674,7 @@ load_pitivi_file_from_uri (GESFormatter * self,
    */
   if (!g_hash_table_size (priv->clips_table) && GES_FORMATTER (self)->project) {
     ges_project_set_loaded (GES_FORMATTER (self)->project,
-        GES_FORMATTER (self));
+        GES_FORMATTER (self), NULL);
   } else {
     if (!make_clips (self)) {
       GST_ERROR ("Couldn't deserialise the project properly");
index f32af5d..ca25c2b 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_PITIVI_FORMATTER
-#define _GES_PITIVI_FORMATTER
+#pragma once
 
 G_BEGIN_DECLS
 
 #define GES_TYPE_PITIVI_FORMATTER ges_pitivi_formatter_get_type()
-
-#define GES_PITIVI_FORMATTER(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_PITIVI_FORMATTER, GESPitiviFormatter))
-
-#define GES_PITIVI_FORMATTER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_PITIVI_FORMATTER, GESPitiviFormatterClass))
-
-#define GES_IS_PITIVI_FORMATTER(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_PITIVI_FORMATTER))
-
-#define GES_IS_PITIVI_FORMATTER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_PITIVI_FORMATTER))
-
-#define GES_PITIVI_FORMATTER_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_PITIVI_FORMATTER, GESPitiviFormatterClass))
-
-typedef struct _GESPitiviFormatterPrivate GESPitiviFormatterPrivate;
-
+GES_DECLARE_TYPE(PitiviFormatter, pitivi_formatter, PITIVI_FORMATTER);
 
 /**
- * GESPitiviFormatter:
+ * GESPitiviFormatter: (attributes doc.skip=true):
  *
  * Serializes a #GESTimeline to a file using the xptv Pitivi file format
  */
@@ -58,6 +40,9 @@ struct _GESPitiviFormatter {
   gpointer _ges_reserved[GES_PADDING];
 };
 
+/**
+ * GESPitiviFormatterClass: (attributes doc.skip=true):
+ */
 struct _GESPitiviFormatterClass
 {
   /*< private >*/
@@ -68,10 +53,6 @@ struct _GESPitiviFormatterClass
 };
 
 GES_API
-GType ges_pitivi_formatter_get_type (void);
-GES_API
 GESPitiviFormatter *ges_pitivi_formatter_new (void);
 
 G_END_DECLS
-
-#endif /* _GES_PITIVI_FORMATTER */
index ccfff57..3651c1a 100644 (file)
@@ -18,9 +18,7 @@
  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  */
-
-#ifndef __GST_GES_PRELUDE_H__
-#define __GST_GES_PRELUDE_H__
+#pragma once
 
 #include <gst/gst.h>
 
 # endif
 #endif
 
-#endif /* __GST_GES_PRELUDE_H__ */
+#ifndef GST_DISABLE_DEPRECATED
+#define GES_DEPRECATED GES_API
+#define GES_DEPRECATED_FOR(f) GES_API
+#else
+#define GES_DEPRECATED G_DEPRECATED GES_API
+#define GES_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) GES_API
+#endif
index 278fb6e..286d3aa 100644 (file)
@@ -23,7 +23,7 @@
  * @short_description: A GESAsset that is used to manage projects
  *
  * The #GESProject is used to control a set of #GESAsset and is a
- * #GESAsset with #GES_TYPE_TIMELINE as @extractable_type itself. That
+ * #GESAsset with `GES_TYPE_TIMELINE` as @extractable_type itself. That
  * means that you can extract #GESTimeline from a project as followed:
  *
  * |[
  * It lets you request new asset, and it informs you about new assets through
  * a set of signals. Also it handles problem such as missing files/missing
  * #GstElement and lets you try to recover from those.
+ *
+ * ## Subprojects
+ *
+ * In order to add a subproject, the only thing to do is to add the subproject
+ * to the main project:
+ *
+ * ``` c
+ * ges_project_add_asset (project, GES_ASSET (subproject));
+ * ```
+ * then the subproject will be serialized in the project files. To use
+ * the subproject in a timeline, you should use a #GESUriClip with the
+ * same subproject URI.
+ *
+ * When loading a project with subproject, subprojects URIs will be temporary
+ * writable local files. If you want to edit the subproject timeline,
+ * you should retrieve the subproject from the parent project asset list and
+ * extract the timeline with ges_asset_extract() and save it at
+ * the same temporary location.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -54,9 +72,6 @@
 static GPtrArray *new_paths = NULL;
 static GHashTable *tried_uris = NULL;
 
-/* TODO We should rely on both extractable_type and @id to identify
- * a Asset, not only @id
- */
 struct _GESProjectPrivate
 {
   GHashTable *assets;
@@ -80,7 +95,9 @@ typedef struct EmitLoadedInIdle
 
 enum
 {
+  LOADING_SIGNAL,
   LOADED_SIGNAL,
+  ERROR_LOADING,
   ERROR_LOADING_ASSET,
   ASSET_ADDED_SIGNAL,
   ASSET_REMOVED_SIGNAL,
@@ -95,6 +112,18 @@ static guint _signals[LAST_SIGNAL] = { 0 };
 
 static guint nb_projects = 0;
 
+/* Find the type that implemented the GESExtractable interface */
+static inline const gchar *
+_extractable_type_name (GType type)
+{
+  while (1) {
+    if (g_type_is_a (g_type_parent (type), GES_TYPE_EXTRACTABLE))
+      type = g_type_parent (type);
+    else
+      return g_type_name (type);
+  }
+}
+
 enum
 {
   PROP_0,
@@ -116,7 +145,16 @@ _emit_loaded_in_idle (EmitLoadedInIdle * data)
   return FALSE;
 }
 
-static void
+/**
+ * ges_project_add_formatter:
+ * @project: The project to add a formatter to
+ * @formatter: A formatter used by @project
+ *
+ * Adds a formatter as used to load @project
+ *
+ * Since: 1.18
+ */
+void
 ges_project_add_formatter (GESProject * project, GESFormatter * formatter)
 {
   GESProjectPrivate *priv = GES_PROJECT (project)->priv;
@@ -152,7 +190,8 @@ ges_project_set_uri (GESProject * project, const gchar * uri)
 
   priv = project->priv;
   if (priv->uri) {
-    GST_WARNING_OBJECT (project, "Trying to rest URI, this is prohibited");
+    if (g_strcmp0 (priv->uri, uri))
+      GST_WARNING_OBJECT (project, "Trying to reset URI, this is prohibited");
 
     return;
   }
@@ -179,19 +218,27 @@ _load_project (GESProject * project, GESTimeline * timeline, GError ** error)
 
   priv = GES_PROJECT (project)->priv;
 
+  g_signal_emit (project, _signals[LOADING_SIGNAL], 0, timeline);
   if (priv->uri == NULL) {
-    EmitLoadedInIdle *data = g_slice_new (EmitLoadedInIdle);
+    const gchar *id = ges_asset_get_id (GES_ASSET (project));
+
+    if (id && gst_uri_is_valid (id)) {
+      ges_project_set_uri (project, ges_asset_get_id (GES_ASSET (project)));
+      GST_INFO_OBJECT (project, "Using asset ID %s as URI.", priv->uri);
+    } else {
+      EmitLoadedInIdle *data = g_slice_new (EmitLoadedInIdle);
 
-    GST_LOG_OBJECT (project, "%s, Loading an empty timeline %s"
-        " as no URI set yet", GST_OBJECT_NAME (timeline),
-        ges_asset_get_id (GES_ASSET (project)));
+      GST_INFO_OBJECT (project, "%s, Loading an empty timeline %s"
+          " as no URI set yet", GST_OBJECT_NAME (timeline),
+          ges_asset_get_id (GES_ASSET (project)));
 
-    data->timeline = gst_object_ref (timeline);
-    data->project = gst_object_ref (project);
+      data->timeline = gst_object_ref (timeline);
+      data->project = gst_object_ref (project);
 
-    /* Make sure the signal is emitted after the functions ends */
-    g_idle_add ((GSourceFunc) _emit_loaded_in_idle, data);
-    return TRUE;
+      /* Make sure the signal is emitted after the functions ends */
+      ges_idle_add ((GSourceFunc) _emit_loaded_in_idle, data, NULL);
+      return TRUE;
+    }
   }
 
   if (priv->formatter_asset == NULL)
@@ -457,7 +504,7 @@ ges_project_class_init (GESProjectClass * klass)
   _signals[ASSET_ADDED_SIGNAL] =
       g_signal_new ("asset-added", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_added),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_ASSET);
 
   /**
    * GESProject::asset-loading:
@@ -469,7 +516,7 @@ ges_project_class_init (GESProjectClass * klass)
   _signals[ASSET_LOADING_SIGNAL] =
       g_signal_new ("asset-loading", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_loading),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_ASSET);
 
   /**
    * GESProject::asset-removed:
@@ -479,18 +526,29 @@ ges_project_class_init (GESProjectClass * klass)
   _signals[ASSET_REMOVED_SIGNAL] =
       g_signal_new ("asset-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, asset_removed),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_ASSET);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_ASSET);
+
+  /**
+   * GESProject::loading:
+   * @project: the #GESProject that is starting to load a timeline
+   * @timeline: The #GESTimeline that started loading
+   *
+   * Since: 1.18
+   */
+  _signals[LOADING_SIGNAL] =
+      g_signal_new ("loading", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESProjectClass, loading),
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE);
 
   /**
    * GESProject::loaded:
-   * @project: the #GESProject that is done loading a project.
-   * @timeline: The #GESTimeline that complete loading
+   * @project: the #GESProject that is done loading a timeline.
+   * @timeline: The #GESTimeline that completed loading
    */
   _signals[LOADED_SIGNAL] =
       g_signal_new ("loaded", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESProjectClass, loaded),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
-      1, GES_TYPE_TIMELINE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TIMELINE);
 
   /**
    * GESProject::missing-uri:
@@ -522,7 +580,7 @@ ges_project_class_init (GESProjectClass * klass)
   _signals[MISSING_URI_SIGNAL] =
       g_signal_new ("missing-uri", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, missing_uri),
-      _uri_missing_accumulator, NULL, g_cclosure_marshal_generic,
+      _uri_missing_accumulator, NULL, NULL,
       G_TYPE_STRING, 2, G_TYPE_ERROR, GES_TYPE_ASSET);
 
   /**
@@ -540,9 +598,22 @@ ges_project_class_init (GESProjectClass * klass)
   _signals[ERROR_LOADING_ASSET] =
       g_signal_new ("error-loading-asset", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESProjectClass, loading_error),
-      NULL, NULL, g_cclosure_marshal_generic,
+      NULL, NULL, NULL,
       G_TYPE_NONE, 3, G_TYPE_ERROR, G_TYPE_STRING, G_TYPE_GTYPE);
 
+  /**
+   * GESProject::error-loading:
+   * @project: the #GESProject on which a problem happend when creted a #GESAsset
+   * @timeline: The timeline that failed loading
+   * @error: The #GError defining the error that occured
+   *
+   * Since: 1.18
+   */
+  _signals[ERROR_LOADING] =
+      g_signal_new ("error-loading", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+      G_TYPE_NONE, 2, GES_TYPE_TIMELINE, G_TYPE_ERROR);
+
   object_class->dispose = _dispose;
   object_class->finalize = _finalize;
 
@@ -567,17 +638,34 @@ ges_project_init (GESProject * project)
       g_free, NULL);
 }
 
+static gchar *
+ges_project_internal_extractable_type_id (GType extractable_type,
+    const gchar * id)
+{
+  return g_strdup_printf ("%s:%s", _extractable_type_name (extractable_type),
+      id);
+}
+
+static gchar *
+ges_project_internal_asset_id (GESAsset * asset)
+{
+  return
+      ges_project_internal_extractable_type_id (ges_asset_get_extractable_type
+      (asset), ges_asset_get_id (asset));
+}
+
 static void
 _send_error_loading_asset (GESProject * project, GESAsset * asset,
     GError * error)
 {
+  gchar *internal_id = ges_project_internal_asset_id (asset);
   const gchar *id = ges_asset_get_id (asset);
 
   GST_DEBUG_OBJECT (project, "Sending error loading asset for %s", id);
-  g_hash_table_remove (project->priv->loading_assets, id);
-  g_hash_table_add (project->priv->loaded_with_error, g_strdup (id));
-  g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, error, id,
-      ges_asset_get_extractable_type (asset));
+  g_hash_table_remove (project->priv->loading_assets, internal_id);
+  g_hash_table_add (project->priv->loaded_with_error, internal_id);
+  g_signal_emit (project, _signals[ERROR_LOADING_ASSET], 0, error,
+      id, ges_asset_get_extractable_type (asset));
 }
 
 gchar *
@@ -586,6 +674,7 @@ ges_project_try_updating_id (GESProject * project, GESAsset * asset,
 {
   gchar *new_id = NULL;
   const gchar *id;
+  gchar *internal_id;
 
   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
   g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
@@ -617,7 +706,9 @@ ges_project_try_updating_id (GESProject * project, GESAsset * asset,
     GST_DEBUG_OBJECT (project, "No new id found for %s", id);
   }
 
-  g_hash_table_remove (project->priv->loading_assets, id);
+  internal_id = ges_project_internal_asset_id (asset);
+  g_hash_table_remove (project->priv->loading_assets, internal_id);
+  g_free (internal_id);
 
   if (new_id == NULL)
     _send_error_loading_asset (project, asset, error);
@@ -647,10 +738,11 @@ new_asset_cb (GESAsset * source, GAsyncResult * res, GESProject * project)
     return;
   }
 
-  ges_asset_set_proxy (NULL, asset);
-  ges_project_add_asset (project, asset);
-  if (asset)
+  if (asset) {
+    ges_asset_finish_proxy (asset);
+    ges_project_add_asset (project, asset);
     gst_object_unref (asset);
+  }
 }
 
 /**
@@ -663,8 +755,15 @@ new_asset_cb (GESAsset * source, GAsyncResult * res, GESProject * project)
  * Returns: %TRUE if the signale could be emitted %FALSE otherwize
  */
 gboolean
-ges_project_set_loaded (GESProject * project, GESFormatter * formatter)
+ges_project_set_loaded (GESProject * project, GESFormatter * formatter,
+    GError * error)
 {
+  if (error) {
+    GST_ERROR_OBJECT (project, "Emit project error-loading %s", error->message);
+    g_signal_emit (project, _signals[ERROR_LOADING], 0, formatter->timeline,
+        error);
+  }
+
   GST_INFO_OBJECT (project, "Emit project loaded");
   if (GST_STATE (formatter->timeline) < GST_STATE_PAUSED) {
     timeline_fill_gaps (formatter->timeline);
@@ -686,8 +785,8 @@ ges_project_add_loading_asset (GESProject * project, GType extractable_type,
   GESAsset *asset;
 
   if ((asset = ges_asset_cache_lookup (extractable_type, id))) {
-    if (g_hash_table_insert (project->priv->loading_assets, g_strdup (id),
-            gst_object_ref (asset)))
+    if (g_hash_table_insert (project->priv->loading_assets,
+            ges_project_internal_asset_id (asset), gst_object_ref (asset)))
       g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, asset);
   }
 }
@@ -715,17 +814,23 @@ gboolean
 ges_project_create_asset (GESProject * project, const gchar * id,
     GType extractable_type)
 {
+  gchar *internal_id;
   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
       FALSE);
 
   if (id == NULL)
     id = g_type_name (extractable_type);
+  internal_id = ges_project_internal_extractable_type_id (extractable_type, id);
+
+  if (g_hash_table_lookup (project->priv->assets, internal_id) ||
+      g_hash_table_lookup (project->priv->loading_assets, internal_id) ||
+      g_hash_table_lookup (project->priv->loaded_with_error, internal_id)) {
 
-  if (g_hash_table_lookup (project->priv->assets, id) ||
-      g_hash_table_lookup (project->priv->loading_assets, id) ||
-      g_hash_table_lookup (project->priv->loaded_with_error, id))
+    g_free (internal_id);
     return FALSE;
+  }
+  g_free (internal_id);
 
   /* TODO Add a GCancellable somewhere in our API */
   ges_asset_request_async (extractable_type, id, NULL,
@@ -753,7 +858,7 @@ ges_project_create_asset_sync (GESProject * project, const gchar * id,
     GType extractable_type, GError ** error)
 {
   GESAsset *asset;
-  gchar *possible_id = NULL;
+  gchar *possible_id = NULL, *internal_id;
   gboolean retry = TRUE;
 
   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
@@ -763,11 +868,18 @@ ges_project_create_asset_sync (GESProject * project, const gchar * id,
   if (id == NULL)
     id = g_type_name (extractable_type);
 
-  if ((asset = g_hash_table_lookup (project->priv->assets, id)))
-    return asset;
-  else if (g_hash_table_lookup (project->priv->loading_assets, id) ||
-      g_hash_table_lookup (project->priv->loaded_with_error, id))
+  internal_id = ges_project_internal_extractable_type_id (extractable_type, id);
+  if ((asset = g_hash_table_lookup (project->priv->assets, internal_id))) {
+    g_free (internal_id);
+
+    return gst_object_ref (asset);
+  } else if (g_hash_table_lookup (project->priv->loading_assets, internal_id) ||
+      g_hash_table_lookup (project->priv->loaded_with_error, internal_id)) {
+    g_free (internal_id);
+
     return NULL;
+  }
+  g_free (internal_id);
 
   /* TODO Add a GCancellable somewhere in our API */
   while (retry) {
@@ -780,10 +892,11 @@ ges_project_create_asset_sync (GESProject * project, const gchar * id,
 
     if (asset) {
       retry = FALSE;
-
-      if ((!g_hash_table_lookup (project->priv->assets,
-                  ges_asset_get_id (asset))))
+      internal_id =
+          ges_project_internal_extractable_type_id (extractable_type, id);
+      if ((!g_hash_table_lookup (project->priv->assets, internal_id)))
         g_signal_emit (project, _signals[ASSET_LOADING_SIGNAL], 0, asset);
+      g_free (internal_id);
 
       if (possible_id) {
         g_free (possible_id);
@@ -812,7 +925,7 @@ ges_project_create_asset_sync (GESProject * project, const gchar * id,
   }
 
   if (!ges_asset_get_proxy_target (asset))
-    ges_asset_set_proxy (NULL, asset);
+    ges_asset_finish_proxy (asset);
 
   ges_project_add_asset (project, asset);
 
@@ -824,7 +937,7 @@ ges_project_create_asset_sync (GESProject * project, const gchar * id,
  * @project: A #GESProject
  * @asset: (transfer none): A #GESAsset to add to @project
  *
- * Adds a #Asset to @project, the project will keep a reference on
+ * Adds a #GESAsset to @project, the project will keep a reference on
  * @asset.
  *
  * Returns: %TRUE if the asset could be added %FALSE it was already
@@ -833,15 +946,18 @@ ges_project_create_asset_sync (GESProject * project, const gchar * id,
 gboolean
 ges_project_add_asset (GESProject * project, GESAsset * asset)
 {
+  gchar *internal_id;
   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
 
-  if (g_hash_table_lookup (project->priv->assets, ges_asset_get_id (asset)))
+  internal_id = ges_project_internal_asset_id (asset);
+  if (g_hash_table_lookup (project->priv->assets, internal_id)) {
+    g_free (internal_id);
     return TRUE;
+  }
 
-  g_hash_table_insert (project->priv->assets,
-      g_strdup (ges_asset_get_id (asset)), gst_object_ref (asset));
-
-  g_hash_table_remove (project->priv->loading_assets, ges_asset_get_id (asset));
+  g_hash_table_insert (project->priv->assets, internal_id,
+      gst_object_ref (asset));
+  g_hash_table_remove (project->priv->loading_assets, internal_id);
   GST_DEBUG_OBJECT (project, "Asset added: %s", ges_asset_get_id (asset));
   g_signal_emit (project, _signals[ASSET_ADDED_SIGNAL], 0, asset);
 
@@ -861,10 +977,13 @@ gboolean
 ges_project_remove_asset (GESProject * project, GESAsset * asset)
 {
   gboolean ret;
+  gchar *internal_id;
 
   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
 
-  ret = g_hash_table_remove (project->priv->assets, ges_asset_get_id (asset));
+  internal_id = ges_project_internal_asset_id (asset);
+  ret = g_hash_table_remove (project->priv->assets, internal_id);
+  g_free (internal_id);
   g_signal_emit (project, _signals[ASSET_REMOVED_SIGNAL], 0, asset);
 
   return ret;
@@ -885,12 +1004,15 @@ ges_project_get_asset (GESProject * project, const gchar * id,
     GType extractable_type)
 {
   GESAsset *asset;
+  gchar *internal_id;
 
   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
   g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
       NULL);
 
-  asset = g_hash_table_lookup (project->priv->assets, id);
+  internal_id = ges_project_internal_extractable_type_id (extractable_type, id);
+  asset = g_hash_table_lookup (project->priv->assets, internal_id);
+  g_free (internal_id);
 
   if (asset)
     return gst_object_ref (asset);
@@ -901,7 +1023,7 @@ ges_project_get_asset (GESProject * project, const gchar * id,
 /**
  * ges_project_list_assets:
  * @project: A #GESProject
- * @filter: Type of assets to list, #GES_TYPE_EXTRACTABLE will list
+ * @filter: Type of assets to list, `GES_TYPE_EXTRACTABLE` will list
  * all assets
  *
  * List all @asset contained in @project filtering per extractable_type
@@ -919,6 +1041,8 @@ ges_project_list_assets (GESProject * project, GType filter)
   gpointer key, value;
 
   g_return_val_if_fail (GES_IS_PROJECT (project), NULL);
+  g_return_val_if_fail (filter == G_TYPE_NONE
+      || g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL);
 
   g_hash_table_iter_init (&iter, project->priv->assets);
   while (g_hash_table_iter_next (&iter, &key, &value)) {
@@ -935,9 +1059,10 @@ ges_project_list_assets (GESProject * project, GType filter)
  * @project: A #GESProject to save
  * @timeline: The #GESTimeline to save, it must have been extracted from @project
  * @uri: The uri where to save @project and @timeline
- * @formatter_asset: (allow-none): The formatter asset to use or %NULL. If %NULL,
- * will try to save in the same format as the one from which the timeline as been loaded
- * or default to the formatter with highest rank
+ * @formatter_asset: (transfer full) (allow-none): The formatter asset to
+ * use or %NULL. If %NULL, will try to save in the same format as the one
+ * from which the timeline as been loaded or default to the best formatter
+ * as defined in #ges_find_formatter_for_uri
  * @overwrite: %TRUE to overwrite file if it exists
  * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
  *
@@ -984,8 +1109,9 @@ ges_project_save (GESProject * project, GESTimeline * timeline,
     goto out;
   }
 
-  if (formatter_asset == NULL)
-    formatter_asset = gst_object_ref (ges_formatter_get_default ());
+  if (formatter_asset == NULL) {
+    formatter_asset = gst_object_ref (ges_find_formatter_for_uri (uri));
+  }
 
   formatter = GES_FORMATTER (ges_asset_extract (formatter_asset, error));
   if (formatter == NULL) {
@@ -1057,7 +1183,7 @@ ges_project_load (GESProject * project, GESTimeline * timeline, GError ** error)
 {
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
   g_return_val_if_fail (GES_IS_PROJECT (project), FALSE);
-  g_return_val_if_fail (ges_project_get_uri (project), FALSE);
+  g_return_val_if_fail (project->priv->uri, FALSE);
   g_return_val_if_fail (timeline->tracks == NULL, FALSE);
 
   if (!_load_project (project, timeline, error))
index 1823984..c3b5b08 100644 (file)
@@ -17,8 +17,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef _GES_PROJECT_
-#define _GES_PROJECT_
+#pragma once
 
 #include <glib-object.h>
 #include <gio/gio.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_PROJECT            ges_project_get_type()
-#define GES_PROJECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_PROJECT, GESProject))
-#define GES_PROJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_PROJECT, GESProjectClass))
-#define GES_IS_PROJECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_PROJECT))
-#define GES_IS_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_PROJECT))
-#define GES_PROJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_PROJECT, GESProjectClass))
-
-typedef struct _GESProjectPrivate GESProjectPrivate;
-
-GES_API
-GType ges_project_get_type (void);
+GES_DECLARE_TYPE(Project, project, PROJECT);
 
 struct _GESProject
 {
@@ -71,8 +61,17 @@ struct _GESProjectClass
                               GType extractable_type);
   gboolean (*loaded)         (GESProject  * self,
                               GESTimeline * timeline);
-
-  gpointer _ges_reserved[GES_PADDING];
+  /**
+   * GESProjectClass::loading:
+   * @self: The self
+   * @timeline: The loading timeline
+   *
+   * Since: 1.18
+   */
+  void (*loading)           (GESProject  * self,
+                             GESTimeline * timeline);
+
+  gpointer _ges_reserved[GES_PADDING - 1];
 };
 
 GES_API
@@ -124,7 +123,7 @@ const GList *ges_project_list_encoding_profiles (GESProject *project);
 GES_API
 gboolean ges_add_missing_uri_relocation_uri    (const gchar * uri,
                                                 gboolean recurse);
+GES_API
+void ges_project_add_formatter (GESProject * project, GESFormatter * formatter);
 
 G_END_DECLS
-
-#endif  /* _GES_PROJECT */
index 1a99a5e..00e5810 100644 (file)
@@ -35,6 +35,9 @@
  *
  * Returns: (transfer full): A #GstSample containing the last frame from
  * @playsink in the format defined by the @caps
+ *
+ * Deprecated: 1.18: Use the "convert-sample" action signal of
+ * #playsink instead.
  */
 GstSample *
 ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps)
index f49209a..07133aa 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GES_SCREENSHOT_H__
-#define __GES_SCREENSHOT_H__
+#pragma once
 
 #include <gst/gst.h>
 #include <ges/ges-prelude.h>
 
 G_BEGIN_DECLS
 
-GES_API GstSample *
+GES_DEPRECATED GstSample *
 ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps);
 
 G_END_DECLS
-
-#endif /* __GES_SCREENSHOT_H__ */
index 7b0d404..e1c4a5f 100644 (file)
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
  */
 
-#ifndef _GES_SMART_ADDER_H_
-#define _GES_SMART_ADDER_H_
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
@@ -68,4 +67,3 @@ GES_API
 GstElement*   ges_smart_adder_new      (GESTrack *track);
 
 G_END_DECLS
-#endif /* _GES_SMART_ADDER_H_ */
index c228ba0..636f169 100644 (file)
 #include "ges-types.h"
 #include "ges-internal.h"
 #include "ges-smart-video-mixer.h"
+#include <gst/base/base.h>
 
 #define GES_TYPE_SMART_MIXER_PAD             (ges_smart_mixer_pad_get_type ())
-#define GES_SMART_MIXER_PAD(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_SMART_MIXER_PAD, GESSmartMixerPad))
-#define GES_SMART_MIXER_PAD_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_SMART_MIXER_PAD, GESSmartMixerPadClass))
-#define GES_IS_SMART_MIXER_PAD(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_SMART_MIXER_PAD))
-#define GES_IS_SMART_MIXER_PAD_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_SMART_MIXER_PAD))
-#define GES_SMART_MIXER_PAD_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_SMART_MIXER_PAD, GESSmartMixerPadClass))
-
 typedef struct _GESSmartMixerPad GESSmartMixerPad;
 typedef struct _GESSmartMixerPadClass GESSmartMixerPadClass;
+GES_DECLARE_TYPE (SmartMixerPad, smart_mixer_pad, SMART_MIXER_PAD);
 
 struct _GESSmartMixerPad
 {
@@ -54,8 +50,6 @@ enum
   PROP_PAD_ALPHA,
 };
 
-static GType ges_smart_mixer_pad_get_type (void);
-
 G_DEFINE_TYPE (GESSmartMixerPad, ges_smart_mixer_pad, GST_TYPE_GHOST_PAD);
 
 static void
@@ -130,29 +124,64 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
 
 typedef struct _PadInfos
 {
+  gint refcount;
+
   GESSmartMixer *self;
   GstPad *mixer_pad;
-  GstElement *bin;
-  gulong probe_id;
+  GstPad *ghostpad;
 } PadInfos;
 
 static void
-destroy_pad (PadInfos * infos)
+pad_infos_unref (PadInfos * infos)
 {
-  gst_pad_remove_probe (infos->mixer_pad, infos->probe_id);
+  if (g_atomic_int_dec_and_test (&infos->refcount)) {
+    GST_DEBUG_OBJECT (infos->mixer_pad, "Releasing pad");
+    if (infos->mixer_pad) {
+      gst_element_release_request_pad (infos->self->mixer, infos->mixer_pad);
+      gst_object_unref (infos->mixer_pad);
+    }
 
-  if (G_LIKELY (infos->bin)) {
-    gst_element_set_state (infos->bin, GST_STATE_NULL);
-    gst_element_unlink (infos->bin, infos->self->mixer);
-    gst_bin_remove (GST_BIN (infos->self), infos->bin);
+    g_free (infos);
   }
+}
+
+static PadInfos *
+pad_infos_new (void)
+{
+  PadInfos *info = g_new0 (PadInfos, 1);
+  g_atomic_int_set (&info->refcount, 1);
+
+  return info;
+}
 
-  if (infos->mixer_pad) {
-    gst_element_release_request_pad (infos->self->mixer, infos->mixer_pad);
-    gst_object_unref (infos->mixer_pad);
+static PadInfos *
+pad_infos_ref (PadInfos * info)
+{
+  g_atomic_int_inc (&info->refcount);
+  return info;
+}
+
+static gboolean
+ges_smart_mixer_sinkpad_event_func (GstPad * pad, GstObject * parent,
+    GstEvent * event)
+{
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEGMENT:
+    {
+      const GstSegment *seg;
+
+      gst_event_parse_segment (event, &seg);
+
+      GST_OBJECT_LOCK (pad);
+      ((GESSmartMixerPad *) pad)->segment = *seg;
+      GST_OBJECT_UNLOCK (pad);
+      break;
+    }
+    default:
+      break;
   }
 
-  g_slice_free (PadInfos, infos);
+  return gst_pad_event_default (pad, parent, event);
 }
 
 GstPad *
@@ -161,7 +190,7 @@ ges_smart_mixer_get_mixer_pad (GESSmartMixer * self, GstPad ** mixerpad)
   PadInfos *info;
   GstPad *sinkpad;
 
-  sinkpad = gst_element_get_request_pad (GST_ELEMENT (self), "sink_%u");
+  sinkpad = gst_element_request_pad_simple (GST_ELEMENT (self), "sink_%u");
 
   if (sinkpad == NULL)
     return NULL;
@@ -172,50 +201,35 @@ ges_smart_mixer_get_mixer_pad (GESSmartMixer * self, GstPad ** mixerpad)
   return sinkpad;
 }
 
-/* These metadata will get set by the upstream framepositioner element,
-   added in the video sources' bin */
-static GstPadProbeReturn
-parse_metadata (GstPad * mixer_pad, GstPadProbeInfo * info,
+static void
+set_pad_properties_from_positioner_meta (GstPad * mixer_pad, GstSample * sample,
     GESSmartMixerPad * ghost)
 {
   GstFramePositionerMeta *meta;
+  GstBuffer *buf = gst_sample_get_buffer (sample);
   GESSmartMixer *self = GES_SMART_MIXER (GST_OBJECT_PARENT (ghost));
 
   meta =
-      (GstFramePositionerMeta *) gst_buffer_get_meta ((GstBuffer *) info->data,
+      (GstFramePositionerMeta *) gst_buffer_get_meta (buf,
       gst_frame_positioner_meta_api_get_type ());
 
   if (!meta) {
     GST_WARNING ("The current source should use a framepositioner");
-    return GST_PAD_PROBE_OK;
+    return;
   }
 
-  if (!self->disable_zorder_alpha) {
+  if (!self->is_transition) {
     g_object_set (mixer_pad, "alpha", meta->alpha,
         "zorder", meta->zorder, NULL);
   } else {
     gint64 stream_time;
     gdouble transalpha;
 
-    GST_OBJECT_LOCK (ghost);
-    if (ghost->segment.format == GST_FORMAT_UNDEFINED) {
-      const GstSegment *seg;
-      GstEvent *segev;
-
-      GST_OBJECT_UNLOCK (ghost);
-      segev = gst_pad_get_sticky_event (GST_PAD (ghost), GST_EVENT_SEGMENT, 0);
-      gst_event_parse_segment (segev, &seg);
-      gst_event_unref (segev);
-      GST_OBJECT_LOCK (ghost);
-
-      ghost->segment = *seg;
-
-    }
-
-    stream_time = gst_segment_to_stream_time (&ghost->segment, GST_FORMAT_TIME,
-        GST_BUFFER_PTS (info->data));
-    GST_OBJECT_UNLOCK (ghost);
+    stream_time = gst_segment_to_stream_time (gst_sample_get_segment (sample),
+        GST_FORMAT_TIME, GST_BUFFER_PTS (buf));
 
+    /* When used in a transition we aggregate the alpha value value if the
+     * transition pad and the alpha value from upstream frame positioner */
     if (GST_CLOCK_TIME_IS_VALID (stream_time))
       gst_object_sync_values (GST_OBJECT (ghost), stream_time);
 
@@ -224,9 +238,8 @@ parse_metadata (GstPad * mixer_pad, GstPadProbeInfo * info,
   }
 
   g_object_set (mixer_pad, "xpos", meta->posx, "ypos",
-      meta->posy, "width", meta->width, "height", meta->height, NULL);
-
-  return GST_PAD_PROBE_OK;
+      meta->posy, "width", meta->width, "height", meta->height,
+      "operator", meta->operator, NULL);
 }
 
 /****************************************************
@@ -236,11 +249,9 @@ static GstPad *
 _request_new_pad (GstElement * element, GstPadTemplate * templ,
     const gchar * name, const GstCaps * caps)
 {
-  GstPad *videoconvert_srcpad, *videoconvert_sinkpad, *tmpghost;
-  PadInfos *infos = g_slice_new0 (PadInfos);
+  PadInfos *infos = pad_infos_new ();
   GESSmartMixer *self = GES_SMART_MIXER (element);
   GstPad *ghost;
-  GstElement *videoconvert;
 
   infos->mixer_pad = gst_element_request_pad (self->mixer,
       gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self->mixer),
@@ -248,46 +259,28 @@ _request_new_pad (GstElement * element, GstPadTemplate * templ,
 
   if (infos->mixer_pad == NULL) {
     GST_WARNING_OBJECT (element, "Could not get any pad from GstMixer");
-    g_slice_free (PadInfos, infos);
+    pad_infos_unref (infos);
 
     return NULL;
   }
 
   infos->self = self;
 
-  infos->bin = gst_bin_new (NULL);
-  videoconvert = gst_element_factory_make ("videoconvert", NULL);
-
-  gst_bin_add (GST_BIN (infos->bin), videoconvert);
-
-  videoconvert_sinkpad = gst_element_get_static_pad (videoconvert, "sink");
-  tmpghost = GST_PAD (gst_ghost_pad_new (NULL, videoconvert_sinkpad));
-  gst_object_unref (videoconvert_sinkpad);
-  gst_pad_set_active (tmpghost, TRUE);
-  gst_element_add_pad (GST_ELEMENT (infos->bin), tmpghost);
-
-  gst_bin_add (GST_BIN (self), infos->bin);
   ghost = g_object_new (ges_smart_mixer_pad_get_type (), "name", name,
-      "direction", GST_PAD_DIRECTION (tmpghost), NULL);
-  gst_ghost_pad_construct (GST_GHOST_PAD (ghost));
-  gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ghost), tmpghost);
+      "direction", GST_PAD_DIRECTION (infos->mixer_pad), NULL);
+  infos->ghostpad = ghost;
+  gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ghost), infos->mixer_pad);
   gst_pad_set_active (ghost, TRUE);
   if (!gst_element_add_pad (GST_ELEMENT (self), ghost))
     goto could_not_add;
 
-  videoconvert_srcpad = gst_element_get_static_pad (videoconvert, "src");
-  tmpghost = GST_PAD (gst_ghost_pad_new (NULL, videoconvert_srcpad));
-  gst_object_unref (videoconvert_srcpad);
-  gst_pad_set_active (tmpghost, TRUE);
-  gst_element_add_pad (GST_ELEMENT (infos->bin), tmpghost);
-  gst_pad_link (tmpghost, infos->mixer_pad);
-
-  infos->probe_id =
-      gst_pad_add_probe (infos->mixer_pad, GST_PAD_PROBE_TYPE_BUFFER,
-      (GstPadProbeCallback) parse_metadata, ghost, NULL);
+  gst_pad_set_event_function (GST_PAD (ghost),
+      ges_smart_mixer_sinkpad_event_func);
 
   LOCK (self);
   g_hash_table_insert (self->pads_infos, ghost, infos);
+  g_hash_table_insert (self->pads_infos, infos->mixer_pad,
+      pad_infos_ref (infos));
   UNLOCK (self);
 
   GST_DEBUG_OBJECT (self, "Returning new pad %" GST_PTR_FORMAT, ghost);
@@ -296,19 +289,38 @@ _request_new_pad (GstElement * element, GstPadTemplate * templ,
 could_not_add:
   {
     GST_ERROR_OBJECT (self, "could not add pad");
-    destroy_pad (infos);
+    pad_infos_unref (infos);
     return NULL;
   }
 }
 
+static PadInfos *
+ges_smart_mixer_find_pad_info (GESSmartMixer * self, GstPad * pad)
+{
+  PadInfos *info;
+
+  LOCK (self);
+  info = g_hash_table_lookup (self->pads_infos, pad);
+  UNLOCK (self);
+
+  if (info)
+    pad_infos_ref (info);
+
+  return info;
+}
+
 static void
 _release_pad (GstElement * element, GstPad * pad)
 {
   GstPad *peer;
+  GESSmartMixer *self = GES_SMART_MIXER (element);
+  PadInfos *info = ges_smart_mixer_find_pad_info (self, pad);
+
   GST_DEBUG_OBJECT (element, "Releasing pad %" GST_PTR_FORMAT, pad);
 
   LOCK (element);
   g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos, pad);
+  g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos, info->mixer_pad);
   peer = gst_pad_get_peer (pad);
   if (peer) {
     gst_pad_unlink (peer, pad);
@@ -318,6 +330,45 @@ _release_pad (GstElement * element, GstPad * pad)
   gst_pad_set_active (pad, FALSE);
   gst_element_remove_pad (element, pad);
   UNLOCK (element);
+
+  pad_infos_unref (info);
+}
+
+static gboolean
+compositor_sync_properties_with_meta (GstElement * compositor,
+    GstPad * sinkpad, GESSmartMixer * self)
+{
+  PadInfos *info = ges_smart_mixer_find_pad_info (self, sinkpad);
+  GstSample *sample;
+
+  if (!info) {
+    GST_WARNING_OBJECT (self, "Couldn't find pad info?!");
+
+    return TRUE;
+  }
+
+  sample = gst_aggregator_peek_next_sample (GST_AGGREGATOR (compositor),
+      GST_AGGREGATOR_PAD (sinkpad));
+
+  if (sample) {
+    set_pad_properties_from_positioner_meta (sinkpad,
+        sample, GES_SMART_MIXER_PAD (info->ghostpad));
+    gst_sample_unref (sample);
+  } else {
+    GST_INFO_OBJECT (sinkpad, "No sample set!");
+  }
+  pad_infos_unref (info);
+
+  return TRUE;
+}
+
+static void
+ges_smart_mixer_samples_selected_cb (GstElement * compositor,
+    GstSegment * segment, GstClockTime pts, GstClockTime dts,
+    GstClockTime duration, GstStructure * info, GESSmartMixer * self)
+{
+  gst_element_foreach_sink_pad (compositor,
+      (GstElementForeachPadFunc) compositor_sync_properties_with_meta, self);
 }
 
 /****************************************************
@@ -350,15 +401,26 @@ static void
 ges_smart_mixer_constructed (GObject * obj)
 {
   GstPad *pad;
-  GstElement *identity;
-
+  GstElement *identity, *videoconvert;
   GESSmartMixer *self = GES_SMART_MIXER (obj);
   gchar *cname = g_strdup_printf ("%s-compositor", GST_OBJECT_NAME (self));
+  GstElement *mixer;
 
   self->mixer =
       gst_element_factory_create (ges_get_compositor_factory (), cname);
   g_free (cname);
-  g_object_set (self->mixer, "background", 1, NULL);
+
+  if (GST_IS_BIN (self->mixer)) {
+    g_object_get (self->mixer, "mixer", &mixer, NULL);
+    g_assert (mixer);
+  } else {
+    mixer = gst_object_ref (self->mixer);
+  }
+
+  g_object_set (mixer, "background", 1, "emit-signals", TRUE, NULL);
+  g_signal_connect (mixer, "samples-selected",
+      G_CALLBACK (ges_smart_mixer_samples_selected_cb), self);
+  gst_object_unref (mixer);
 
   /* See https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/310 */
   GST_FIXME ("Stop dropping allocation query when it is not required anymore.");
@@ -366,10 +428,13 @@ ges_smart_mixer_constructed (GObject * obj)
   g_object_set (identity, "drop-allocation", TRUE, NULL);
   g_assert (identity);
 
-  gst_bin_add_many (GST_BIN (self), self->mixer, identity, NULL);
-  gst_element_link (self->mixer, identity);
+  videoconvert = gst_element_factory_make ("videoconvert", NULL);
+  g_assert (videoconvert);
+
+  gst_bin_add_many (GST_BIN (self), self->mixer, identity, videoconvert, NULL);
+  gst_element_link_many (self->mixer, identity, videoconvert, NULL);
 
-  pad = gst_element_get_static_pad (identity, "src");
+  pad = gst_element_get_static_pad (videoconvert, "src");
   self->srcpad = gst_ghost_pad_new ("src", pad);
   gst_pad_set_active (self->srcpad, TRUE);
   gst_object_unref (pad);
@@ -390,7 +455,7 @@ ges_smart_mixer_class_init (GESSmartMixerClass * klass)
   gst_element_class_add_static_pad_template (element_class, &sink_template);
   gst_element_class_set_static_metadata (element_class, "GES Smart mixer",
       "Generic/Audio",
-      "Use mixer making use of GES informations",
+      "Use mixer making use of GES information",
       "Thibault Saunier <thibault.saunier@collabora.com>");
 
   element_class->request_new_pad = GST_DEBUG_FUNCPTR (_request_new_pad);
@@ -406,7 +471,7 @@ ges_smart_mixer_init (GESSmartMixer * self)
 {
   g_mutex_init (&self->lock);
   self->pads_infos = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-      NULL, (GDestroyNotify) destroy_pad);
+      NULL, (GDestroyNotify) pad_infos_unref);
 }
 
 GstElement *
index 7ddc896..a137c45 100644 (file)
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
  */
 
-#ifndef _GES_SMART_MIXER_H_
-#define _GES_SMART_MIXER_H_
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
@@ -54,7 +53,7 @@ struct _GESSmartMixer
   GMutex lock;
 
   GstCaps *caps;
-  gboolean disable_zorder_alpha;
+  gboolean is_transition;
 
   gpointer _ges_reserved[GES_PADDING];
 };
@@ -68,5 +67,4 @@ ges_smart_mixer_get_mixer_pad (GESSmartMixer *self, GstPad **mixerpad);
 G_GNUC_INTERNAL
 GstElement*   ges_smart_mixer_new      (GESTrack *track);
 
-G_END_DECLS
-#endif /* _GES_SMART_MIXER_H_ */
+G_END_DECLS
\ No newline at end of file
diff --git a/ges/ges-source-clip-asset.c b/ges/ges-source-clip-asset.c
new file mode 100644 (file)
index 0000000..f8b7d33
--- /dev/null
@@ -0,0 +1,54 @@
+/* GStreamer Editing Services
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION: gessourceclipasset
+ * @title: GESSourceClipAsset
+ * @short_description: A GESAsset subclass, baseclass for #GESSourceClip-s extraction
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ges-source-clip-asset.h"
+#include "ges-internal.h"
+
+G_DEFINE_TYPE (GESSourceClipAsset, ges_source_clip_asset, GES_TYPE_CLIP_ASSET);
+
+static gboolean
+get_natural_framerate (GESClipAsset * self, gint * framerate_n,
+    gint * framerate_d)
+{
+  *framerate_n = DEFAULT_FRAMERATE_N;
+  *framerate_d = DEFAULT_FRAMERATE_D;
+  return TRUE;
+}
+
+static void
+ges_source_clip_asset_class_init (GESSourceClipAssetClass * klass)
+{
+  GES_CLIP_ASSET_CLASS (klass)->get_natural_framerate = get_natural_framerate;
+}
+
+static void
+ges_source_clip_asset_init (GESSourceClipAsset * self)
+{
+}
diff --git a/ges/ges-source-clip-asset.h b/ges/ges-source-clip-asset.h
new file mode 100644 (file)
index 0000000..aeb100d
--- /dev/null
@@ -0,0 +1,47 @@
+/* GStreamer Editing Services
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <ges/ges-clip-asset.h>
+
+G_BEGIN_DECLS
+
+#define GES_TYPE_SOURCE_CLIP_ASSET ges_source_clip_asset_get_type ()
+
+/**
+ * GESSourceClipAsset:
+ *
+ * An asset types from which #GESSourceClip will be extracted
+ *
+ * Since: 1.18
+ */
+GES_API
+G_DECLARE_DERIVABLE_TYPE(GESSourceClipAsset, ges_source_clip_asset, GES,
+                         SOURCE_CLIP_ASSET, GESClipAsset);
+
+struct _GESSourceClipAssetClass
+{
+  GESClipAssetClass parent_class;
+  /* FIXME 2.0: Add some padding, meanwhile that would break ABI */
+};
+
+
+G_END_DECLS
\ No newline at end of file
index 755485a..6cb02d3 100644 (file)
 /**
  * SECTION:gessourceclip
  * @title: GESSourceClip
- * @short_description: Base Class for sources of a GESLayer
+ * @short_description: Base Class for sources of a #GESLayer
+ *
+ * #GESSourceClip-s are clips whose core elements are #GESSource-s.
+ *
+ * ## Effects
+ *
+ * #GESSourceClip-s can also have #GESBaseEffect-s added as non-core
+ * elements. These effects are applied to the core sources of the clip
+ * that they share a #GESTrack with. See #GESClip for how to add and move
+ * these effects from the clip.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -44,49 +53,59 @@ enum
   PROP_0,
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GESSourceClip, ges_source_clip, GES_TYPE_CLIP);
+static GESExtractableInterface *parent_extractable_iface = NULL;
 
-static gboolean
-_set_start (GESTimelineElement * element, GstClockTime start)
+static gchar *
+extractable_check_id (GType type, const gchar * id, GError ** error)
 {
-  GESTimelineElement *toplevel =
-      ges_timeline_element_get_toplevel_parent (element);
-
-  gst_object_unref (toplevel);
-  if (element->timeline
-      && !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE)
-      && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-    ges_timeline_move_object_simple (element->timeline, element, NULL,
-        GES_EDGE_NONE, start);
-    return FALSE;
+  if (type == GES_TYPE_SOURCE_CLIP) {
+    g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
+        "Only `time-overlay` is supported as an ID for type: `GESSourceClip`,"
+        " got: '%s'", id);
+    return NULL;
   }
 
-  return
-      GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_start
-      (element, start);
+  return parent_extractable_iface->check_id (type, id, error);
 }
 
-static gboolean
-_set_duration (GESTimelineElement * element, GstClockTime duration)
+static GType
+extractable_get_real_extractable_type (GType wanted_type, const gchar * id)
 {
-  GESTimelineElement *toplevel =
-      ges_timeline_element_get_toplevel_parent (element);
-
-  gst_object_unref (toplevel);
-  if (element->timeline
-      && !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE)
-      && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-    return !timeline_trim_object (element->timeline, element,
-        GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element), NULL, GES_EDGE_END,
-        element->start + duration);
-  }
+  GstStructure *structure;
+
+  if (!id || (wanted_type != GES_TYPE_SOURCE_CLIP
+          && wanted_type != GES_TYPE_TEST_CLIP))
+    return wanted_type;
+
+  structure = gst_structure_new_from_string (id);
+  if (!structure)
+    return wanted_type;
+
+  if (gst_structure_has_name (structure, "time-overlay"))
+    /* Just reusing TestClip to create a GESTimeOverlayClip
+     * as it already does the job! */
+    wanted_type = GES_TYPE_TEST_CLIP;
 
-  return
-      GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_duration
-      (element, duration);
+  gst_structure_free (structure);
+
+  return wanted_type;
 }
 
 static void
+extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->asset_type = GES_TYPE_SOURCE_CLIP_ASSET;
+  iface->get_real_extractable_type = extractable_get_real_extractable_type;
+  iface->check_id = extractable_check_id;
+
+  parent_extractable_iface = g_type_interface_peek_parent (iface);
+}
+
+G_DEFINE_TYPE_WITH_CODE (GESSourceClip, ges_source_clip,
+    GES_TYPE_CLIP, G_ADD_PRIVATE (GESSourceClip)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, extractable_interface_init));
+
+static void
 ges_source_clip_get_property (GObject * object, guint property_id,
     GValue * value, GParamSpec * pspec)
 {
@@ -116,14 +135,12 @@ static void
 ges_source_clip_class_init (GESSourceClipClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
 
   object_class->get_property = ges_source_clip_get_property;
   object_class->set_property = ges_source_clip_set_property;
   object_class->finalize = ges_source_clip_finalize;
 
-  element_class->set_start = _set_start;
-  element_class->set_duration = _set_duration;
+  GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) = TRUE;
 }
 
 static void
index 9037834..d3669db 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_SOURCE_CLIP
-#define _GES_SOURCE_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_SOURCE_CLIP ges_source_clip_get_type()
-
-#define GES_SOURCE_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_SOURCE_CLIP, GESSourceClip))
-
-#define GES_SOURCE_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_SOURCE_CLIP, GESSourceClipClass))
-
-#define GES_IS_SOURCE_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_SOURCE_CLIP))
-
-#define GES_IS_SOURCE_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_SOURCE_CLIP))
-
-#define GES_SOURCE_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_SOURCE_CLIP, GESSourceClipClass))
-
-typedef struct _GESSourceClipPrivate GESSourceClipPrivate;
+GES_DECLARE_TYPE(SourceClip, source_clip, SOURCE_CLIP);
 
 /**
  * GESSourceClip:
@@ -75,10 +58,4 @@ struct _GESSourceClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_source_clip_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_SOURCE_CLIP */
-
index f960349..7386860 100644 (file)
 #include "gstframepositioner.h"
 struct _GESSourcePrivate
 {
-  /*  Dummy variable */
-  GstFramePositioner *positioner;
+  GstElement *topbin;
+  GstElement *first_converter;
+  GstElement *last_converter;
+  GstPad *ghostpad;
+
+  gboolean is_rendering_smartly;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (GESSource, ges_source, GES_TYPE_TRACK_ELEMENT);
@@ -44,117 +48,190 @@ G_DEFINE_TYPE_WITH_PRIVATE (GESSource, ges_source, GES_TYPE_TRACK_ELEMENT);
 /******************************
  *   Internal helper methods  *
  ******************************/
-static void
-_pad_added_cb (GstElement * element, GstPad * srcpad, GstPad * sinkpad)
+static GstElement *
+link_elements (GstElement * bin, GPtrArray * elements)
 {
-  GstPadLinkReturn res;
-  gst_element_no_more_pads (element);
-  res = gst_pad_link (srcpad, sinkpad);
-#ifndef GST_DISABLE_GST_DEBUG
-  if (res != GST_PAD_LINK_OK) {
-    GstCaps *srccaps = NULL;
-    GstCaps *sinkcaps = NULL;
-
-    srccaps = gst_pad_query_caps (srcpad, NULL);
-    sinkcaps = gst_pad_query_caps (sinkpad, NULL);
-
-    GST_WARNING_OBJECT (element, "Could not link source with "
-        "conversion bin: %s (srcpad caps %" GST_PTR_FORMAT
-        " sinkpad caps: %" GST_PTR_FORMAT ")",
-        gst_pad_link_get_name (res), srccaps, sinkcaps);
-    gst_caps_unref (srccaps);
-    gst_caps_unref (sinkcaps);
+  GstElement *element, *prev = NULL, *first = NULL;
+  gint i;
+
+  for (i = 0; i < elements->len; i++) {
+    element = elements->pdata[i];
+    if (!element)
+      continue;
+
+    gst_bin_add (GST_BIN (bin), element);
+    if (prev) {
+      if (!gst_element_link_pads_full (prev, "src", element, "sink",
+              GST_PAD_LINK_CHECK_NOTHING)) {
+        if (!gst_element_link (prev, element)) {
+          g_error ("Could not link %s and %s", GST_OBJECT_NAME (prev),
+              GST_OBJECT_NAME (element));
+        }
+      }
+    }
+    prev = element;
+    if (first == NULL)
+      first = element;
   }
-#endif
+
+  return prev;
 }
 
 static void
-_ghost_pad_added_cb (GstElement * element, GstPad * srcpad, GstElement * bin)
+_set_ghost_pad_target (GESSource * self, GstPad * srcpad, GstElement * element)
 {
-  GstPad *ghost;
+  GstPadLinkReturn link_return;
+  GESSourcePrivate *priv = self->priv;
+  GESSourceClass *source_klass = GES_SOURCE_GET_CLASS (self);
+  gboolean use_converter = ! !priv->first_converter;
+
+  if (source_klass->select_pad && !source_klass->select_pad (self, srcpad)) {
+    GST_INFO_OBJECT (self, "Ignoring pad %" GST_PTR_FORMAT, srcpad);
+    return;
+  }
+
+
+  if (use_converter && priv->is_rendering_smartly) {
+    GstPad *pad = gst_element_get_static_pad (priv->first_converter, "sink");
+    use_converter = gst_pad_can_link (srcpad, pad);
+    gst_object_unref (pad);
+  }
+
+  if (use_converter) {
+    GstPad *converter_src, *sinkpad;
+
+    converter_src = gst_element_get_static_pad (priv->last_converter, "src");
+    if (!gst_ghost_pad_set_target (GST_GHOST_PAD (priv->ghostpad),
+            converter_src)) {
+      GST_ERROR_OBJECT (self, "Could not set ghost target");
+    }
+
+    sinkpad = gst_element_get_static_pad (priv->first_converter, "sink");
+    link_return = gst_pad_link (srcpad, sinkpad);
+#ifndef GST_DISABLE_GST_DEBUG
+    if (link_return != GST_PAD_LINK_OK) {
+      GstCaps *srccaps = NULL;
+      GstCaps *sinkcaps = NULL;
+
+      srccaps = gst_pad_query_caps (srcpad, NULL);
+      sinkcaps = gst_pad_query_caps (sinkpad, NULL);
+
+      GST_ERROR_OBJECT (element, "Could not link source with "
+          "conversion bin: %s (srcpad caps %" GST_PTR_FORMAT
+          " sinkpad caps: %" GST_PTR_FORMAT ")",
+          gst_pad_link_get_name (link_return), srccaps, sinkcaps);
+      gst_caps_unref (srccaps);
+      gst_caps_unref (sinkcaps);
+    }
+#endif
+
+    gst_object_unref (converter_src);
+    gst_object_unref (sinkpad);
+  } else {
+    if (!gst_ghost_pad_set_target (GST_GHOST_PAD (priv->ghostpad), srcpad))
+      GST_ERROR_OBJECT (self, "Could not set ghost target");
+  }
 
-  ghost = gst_ghost_pad_new ("src", srcpad);
-  gst_pad_set_active (ghost, TRUE);
-  gst_element_add_pad (bin, ghost);
   gst_element_no_more_pads (element);
 }
 
+/* @elements: (transfer-full) */
 GstElement *
-ges_source_create_topbin (const gchar * bin_name, GstElement * sub_element, ...)
+ges_source_create_topbin (GESSource * source, const gchar * bin_name,
+    GstElement * sub_element, GPtrArray * elements)
 {
-  va_list argp;
-
-  GstElement *element;
-  GstElement *prev = NULL;
-  GstElement *first = NULL;
+  GstElement *last;
   GstElement *bin;
   GstPad *sub_srcpad;
+  GESSourcePrivate *priv = source->priv;
 
-  va_start (argp, sub_element);
   bin = gst_bin_new (bin_name);
-  gst_bin_add (GST_BIN (bin), sub_element);
+  if (!gst_bin_add (GST_BIN (bin), sub_element)) {
+    GST_ERROR_OBJECT (source, "Could not add sub element: %" GST_PTR_FORMAT,
+        sub_element);
+    gst_object_unref (bin);
+    return NULL;
+  }
 
-  while ((element = va_arg (argp, GstElement *)) != NULL) {
-    gst_bin_add (GST_BIN (bin), element);
-    if (prev) {
-      if (!gst_element_link_pads_full (prev, "src", element, "sink",
-              GST_PAD_LINK_CHECK_NOTHING)) {
-        g_error ("Could not link %s and %s",
-            GST_OBJECT_NAME (prev), GST_OBJECT_NAME (element));
-      }
+  priv->ghostpad = gst_object_ref (gst_ghost_pad_new_no_target ("src",
+          GST_PAD_SRC));
+  gst_pad_set_active (priv->ghostpad, TRUE);
+  gst_element_add_pad (bin, priv->ghostpad);
+  priv->topbin = gst_object_ref (bin);
+  last = link_elements (bin, elements);
+  if (last) {
+    gint i = 0;
 
-    }
-    prev = element;
-    if (first == NULL)
-      first = element;
-  }
+    while (!elements->pdata[i])
+      i++;
 
-  va_end (argp);
+    priv->first_converter = gst_object_ref (elements->pdata[i]);
+    priv->last_converter = gst_object_ref (last);
+  }
 
   sub_srcpad = gst_element_get_static_pad (sub_element, "src");
+  if (sub_srcpad) {
+    _set_ghost_pad_target (source, sub_srcpad, sub_element);
+    gst_object_unref (sub_srcpad);
+  } else {
+    GST_INFO_OBJECT (source, "Waiting for pad added");
+    g_signal_connect_swapped (sub_element, "pad-added",
+        G_CALLBACK (_set_ghost_pad_target), source);
+  }
+  g_ptr_array_free (elements, TRUE);
 
-  if (prev != NULL) {
-    GstPad *srcpad, *sinkpad, *ghost;
+  return bin;
+}
 
-    srcpad = gst_element_get_static_pad (prev, "src");
-    ghost = gst_ghost_pad_new ("src", srcpad);
-    gst_pad_set_active (ghost, TRUE);
-    gst_element_add_pad (bin, ghost);
 
-    sinkpad = gst_element_get_static_pad (first, "sink");
-    if (sub_srcpad)
-      gst_pad_link_full (sub_srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
-    else
-      g_signal_connect (sub_element, "pad-added", G_CALLBACK (_pad_added_cb),
-          sinkpad);
+void
+ges_source_set_rendering_smartly (GESSource * source,
+    gboolean is_rendering_smartly)
+{
 
-    gst_object_unref (srcpad);
-    gst_object_unref (sinkpad);
+  if (is_rendering_smartly) {
+    GESTrack *track = ges_track_element_get_track (GES_TRACK_ELEMENT (source));
 
-  } else if (sub_srcpad) {
-    GstPad *ghost;
+    if (track && ges_track_get_mixing (track)) {
+      GST_DEBUG_OBJECT (source, "Not rendering smartly as track is mixing!");
 
-    ghost = gst_ghost_pad_new ("src", sub_srcpad);
-    gst_pad_set_active (ghost, TRUE);
-    gst_element_add_pad (bin, ghost);
-  } else {
-    g_signal_connect (sub_element, "pad-added",
-        G_CALLBACK (_ghost_pad_added_cb), bin);
+      source->priv->is_rendering_smartly = FALSE;
+      return;
+    }
   }
+  source->priv->is_rendering_smartly = is_rendering_smartly;
+}
 
-  if (sub_srcpad)
-    gst_object_unref (sub_srcpad);
+gboolean
+ges_source_get_rendering_smartly (GESSource * source)
+{
+  return source->priv->is_rendering_smartly;
+}
 
-  return bin;
+static void
+ges_source_dispose (GObject * object)
+{
+  GESSourcePrivate *priv = GES_SOURCE (object)->priv;
+
+  gst_clear_object (&priv->first_converter);
+  gst_clear_object (&priv->last_converter);
+  gst_clear_object (&priv->topbin);
+  gst_clear_object (&priv->ghostpad);
+
+  G_OBJECT_CLASS (ges_source_parent_class)->dispose (object);
 }
 
 static void
 ges_source_class_init (GESSourceClass * klass)
 {
   GESTrackElementClass *track_class = GES_TRACK_ELEMENT_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   track_class->nleobject_factorytype = "nlesource";
   track_class->create_element = NULL;
+  object_class->dispose = ges_source_dispose;
+
+  GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE (klass) = TRUE;
 }
 
 static void
index 0641746..a807667 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_SOURCE
-#define _GES_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_SOURCE ges_source_get_type()
-
-#define GES_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_SOURCE, GESSource))
-
-#define GES_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_SOURCE, GESSourceClass))
-
-#define GES_IS_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_SOURCE))
-
-#define GES_IS_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_SOURCE))
-
-#define GES_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_SOURCE, GESSourceClass))
-
-typedef struct _GESSourcePrivate GESSourcePrivate;
+GES_DECLARE_TYPE(Source, source, SOURCE);
 
 /**
  * GESSource:
@@ -65,21 +48,41 @@ struct _GESSource {
 
 /**
  * GESSourceClass:
- * @create_source: method to return the GstElement to put in the source topbin.
  */
 
 struct _GESSourceClass {
   /*< private >*/
   GESTrackElementClass parent_class;
 
-  /*< private >*/
+  /**
+   * GESSourceClass::select_pad:
+   * @source: The @source for which to check if @pad should be used or not
+   * @pad: The pad to check
+   *
+   * Check whether @pad should be exposed/used.
+   *
+   * Returns: %TRUE if @pad should be used %FALSE otherwise.
+   *
+   * Since: 1.20
+   */
+  gboolean (*select_pad)(GESSource *source, GstPad *pad);
+
+  /**
+   * GESSourceClass::create_source:
+   * @source: The #GESAudioSource
+   *
+   * Creates the GstElement to put in the source topbin. Other elements will be
+   * queued, like a volume. In the case of a AudioUriSource, for example, the
+   * subclass will return a decodebin, and we will append a volume.
+   *
+   * Returns: (transfer floating): The source element to use.
+   *
+   * Since: 1.20
+   */
+  GstElement*  (*create_source)           (GESSource * source);
+
   /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING];
+  gpointer _ges_reserved[GES_PADDING - 2];
 };
 
-GES_API
-GType ges_source_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_SOURCE */
index f2ef280..d84d64b 100644 (file)
@@ -66,6 +66,15 @@ ges_structure_parser_parse_string (GESStructureParser * self,
 }
 
 void
+ges_structure_parser_parse_value (GESStructureParser * self, const gchar * text)
+{
+  /* text starts with '=' */
+  gchar *val_string = g_strconcat ("=(string)", text + 1, NULL);
+  ges_structure_parser_parse_string (self, val_string, FALSE);
+  g_free (val_string);
+}
+
+void
 ges_structure_parser_parse_default (GESStructureParser * self,
     const gchar * text)
 {
@@ -90,25 +99,28 @@ ges_structure_parser_parse_whitespace (GESStructureParser * self)
 static void
 _finish_structure (GESStructureParser * self)
 {
-  if (self->current_string) {
-    GstStructure *structure =
-        gst_structure_new_from_string (self->current_string);
+  GstStructure *structure;
 
-    if (structure == NULL) {
-      GST_ERROR ("Could not parse %s", self->current_string);
+  if (!self->current_string)
+    return;
 
-      self->wrong_strings = g_list_append (self->wrong_strings,
-          g_strdup (self->current_string));
+  structure = gst_structure_new_from_string (self->current_string);
 
-      return;
-    }
+  if (structure == NULL) {
+    GST_ERROR ("Could not parse %s", self->current_string);
 
-    self->structures = g_list_append (self->structures, structure);
-    g_free (self->current_string);
-    self->current_string = NULL;
+    self->wrong_strings = g_list_append (self->wrong_strings,
+        g_strdup (self->current_string));
+
+    return;
   }
+
+  self->structures = g_list_append (self->structures, structure);
+  g_free (self->current_string);
+  self->current_string = NULL;
 }
 
+
 void
 ges_structure_parser_end_of_file (GESStructureParser * self)
 {
@@ -126,15 +138,23 @@ ges_structure_parser_parse_symbol (GESStructureParser * self,
 
   self->add_comma = FALSE;
   if (!g_ascii_strncasecmp (symbol, "clip", 4))
-    ges_structure_parser_parse_string (self, "clip, uri=", TRUE);
+    ges_structure_parser_parse_string (self, "clip, uri=(string)", TRUE);
   else if (!g_ascii_strncasecmp (symbol, "test-clip", 9))
-    ges_structure_parser_parse_string (self, "test-clip, pattern=", TRUE);
+    ges_structure_parser_parse_string (self, "test-clip, pattern=(string)",
+        TRUE);
   else if (!g_ascii_strncasecmp (symbol, "effect", 6))
-    ges_structure_parser_parse_string (self, "effect, bin-description=", TRUE);
+    ges_structure_parser_parse_string (self, "effect, bin-description=(string)",
+        TRUE);
   else if (!g_ascii_strncasecmp (symbol, "transition", 10))
-    ges_structure_parser_parse_string (self, "transition, type=", TRUE);
+    ges_structure_parser_parse_string (self, "transition, type=(string)", TRUE);
   else if (!g_ascii_strncasecmp (symbol, "title", 5))
     ges_structure_parser_parse_string (self, "title, text=(string)", TRUE);
+  else if (!g_ascii_strncasecmp (symbol, "track", 5))
+    ges_structure_parser_parse_string (self, "track, type=(string)", TRUE);
+  else if (!g_ascii_strncasecmp (symbol, "keyframes", 8)) {
+    ges_structure_parser_parse_string (self,
+        "keyframes, property-name=(string)", TRUE);
+  }
 }
 
 void
@@ -153,7 +173,8 @@ ges_structure_parser_parse_setter (GESStructureParser * self,
 
   setter++;
 
-  parsed_setter = g_strdup_printf ("set-property, property=%s, value=", setter);
+  parsed_setter = g_strdup_printf ("set-property, property=(string)%s, "
+      "value=(string)", setter);
   self->add_comma = FALSE;
   ges_structure_parser_parse_string (self, parsed_setter, TRUE);
   g_free (parsed_setter);
@@ -185,6 +206,5 @@ ges_structure_parser_get_error (GESStructureParser * self)
   error = g_error_new_literal (GES_ERROR, 0, msg->str);
   g_string_free (msg, TRUE);
 
-  GST_ERROR ("BoOOOM ");
   return error;
 }
index 9dedbc4..1252655 100644 (file)
@@ -17,8 +17,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef _GES_STRUCTURE_PARSER_H
-#define _GES_STRUCTURE_PARSER_H
+#pragma once
 
 #include <glib.h>
 #include <gst/gst.h>
@@ -53,6 +52,7 @@ G_GNUC_INTERNAL GType ges_structure_parser_get_type (void) G_GNUC_CONST;
 
 G_GNUC_INTERNAL GError * ges_structure_parser_get_error (GESStructureParser *self);
 G_GNUC_INTERNAL void ges_structure_parser_parse_string (GESStructureParser *self, const gchar *string, gboolean is_symbol);
+G_GNUC_INTERNAL void ges_structure_parser_parse_value (GESStructureParser *self, const gchar *string);
 G_GNUC_INTERNAL void ges_structure_parser_parse_default (GESStructureParser *self, const gchar *text);
 G_GNUC_INTERNAL void ges_structure_parser_parse_whitespace (GESStructureParser *self);
 G_GNUC_INTERNAL void ges_structure_parser_parse_symbol (GESStructureParser *self, const gchar *symbol);
@@ -61,5 +61,3 @@ G_GNUC_INTERNAL void ges_structure_parser_end_of_file (GESStructureParser *self)
 
 G_GNUC_INTERNAL GESStructureParser *ges_structure_parser_new(void);
 G_END_DECLS
-
-#endif  /* _GES_STRUCTURE_PARSER_H */
index 5a5f2f0..0ee9ab1 100644 (file)
@@ -22,6 +22,7 @@
 #endif
 
 #include "ges-structured-interface.h"
+#include "ges-internal.h"
 
 #include <string.h>
 
 #define LAST_CONTAINER_QDATA g_quark_from_string("ges-structured-last-container")
 #define LAST_CHILD_QDATA g_quark_from_string("ges-structured-last-child")
 
-static gboolean
-_get_clocktime (GstStructure * structure, const gchar * name, gpointer var)
-{
-  gboolean found = FALSE;
-  GstClockTime *val = (GstClockTime *) var;
-
-  const GValue *gvalue = gst_structure_get_value (structure, name);
-
-  if (gvalue) {
-    if (G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) {
-      *val = (GstClockTime) g_value_get_uint64 (gvalue);
-      found = TRUE;
-    } else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) {
-      *val = (GstClockTime) g_value_get_uint64 (gvalue);
-      found = TRUE;
-    } else if (G_VALUE_TYPE (gvalue) == G_TYPE_UINT) {
-      *val = (GstClockTime) g_value_get_uint (gvalue);
-      found = TRUE;
-    } else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT) {
-      *val = (GstClockTime) g_value_get_int (gvalue);
-      found = TRUE;
-    } else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64) {
-      *val = (GstClockTime) g_value_get_int64 (gvalue);
-      found = TRUE;
-    } else if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE) {
-      gdouble d = g_value_get_double (gvalue);
-
-      found = TRUE;
-      if (d == -1.0)
-        *val = GST_CLOCK_TIME_NONE;
-      else {
-        *val = d * GST_SECOND;
-        *val = GST_ROUND_UP_4 (*val);
-      }
-    }
-  }
-
-  return found;
-}
+#ifdef G_HAVE_ISO_VARARGS
+#define REPORT_UNLESS(condition, errpoint, ...)                                \
+  G_STMT_START {                                                               \
+    if (!(condition)) {                                                        \
+      gchar *tmp = gst_info_strdup_printf(__VA_ARGS__);                            \
+      *error = g_error_new_literal (GES_ERROR, 0, tmp);                        \
+      g_free (tmp);                                                            \
+      goto errpoint;                                                           \
+    }                                                                          \
+  }                                                                            \
+  G_STMT_END
+#else /* G_HAVE_GNUC_VARARGS */
+#ifdef G_HAVE_GNUC_VARARGS
+#define REPORT_UNLESS(condition, errpoint, args...)                            \
+  G_STMT_START {                                                               \
+    if (!(condition)) {                                                        \
+      gchar *tmp = gst_info_strdup_printf(##args);                            \
+      *error = g_error_new_literal (GES_ERROR, 0, tmp);                        \
+      g_free (tmp);                                                            \
+      goto errpoint;                                                           \
+    }                                                                          \
+  }                                                                            \
+  G_STMT_END
+#endif /* G_HAVE_ISO_VARARGS */
+#endif /* G_HAVE_GNUC_VARARGS */
 
 #define GET_AND_CHECK(name,type,var,label) G_STMT_START {\
   gboolean found = FALSE; \
 \
   if (type == GST_TYPE_CLOCK_TIME) {\
-    found = _get_clocktime(structure,name,var);\
+    found = ges_util_structure_get_clocktime (structure,name, (GstClockTime*)var,NULL);\
   }\
   else { \
     found = gst_structure_get (structure, name, type, var, NULL); \
@@ -82,7 +69,7 @@ _get_clocktime (GstStructure * structure, const gchar * name, gpointer var)
     gchar *struct_str = gst_structure_to_string (structure); \
     *error = g_error_new (GES_ERROR, 0, \
         "Could not get the mandatory field '%s'" \
-        " fields in %s", name, struct_str); \
+        " of type %s - fields in %s", name, g_type_name (type), struct_str); \
     g_free (struct_str); \
     goto label;\
   } \
@@ -94,13 +81,57 @@ _get_clocktime (GstStructure * structure, const gchar * name, gpointer var)
     *var = def; \
 } G_STMT_END
 
-#define TRY_GET(name,type,var,def) G_STMT_START {\
-  if (type == GST_TYPE_CLOCK_TIME) {\
-    if (!_get_clocktime(structure,name,var))\
-      *var = def; \
-  } else if  (!gst_structure_get (structure, name, type, var, NULL)) {\
-    *var = def; \
-  } \
+#define TRY_GET_TIME(name, var, var_frames, def) G_STMT_START  {       \
+  if (!ges_util_structure_get_clocktime (structure, name, var, var_frames)) { \
+      *var = def;                                          \
+      *var_frames = GES_FRAME_NUMBER_NONE;                            \
+  }                                                        \
+} G_STMT_END
+
+static gboolean
+_get_structure_value (GstStructure * structure, const gchar * field, GType type,
+    gpointer v)
+{
+  gboolean res = TRUE;
+  const gchar *value_str;
+  const GValue *value;
+  GValue nvalue = G_VALUE_INIT;
+
+  if (gst_structure_get (structure, field, type, v, NULL))
+    return res;
+
+  g_value_init (&nvalue, type);
+  value = gst_structure_get_value (structure, field);
+  if (!value)
+    goto fail;
+
+  if (g_value_transform (value, &nvalue))
+    goto set_and_get_value;
+
+  if (!G_VALUE_HOLDS_STRING (value))
+    goto fail;
+
+  value_str = g_value_get_string (value);
+  if (!gst_value_deserialize (&nvalue, value_str))
+    goto done;
+
+set_and_get_value:
+  gst_structure_set_value (structure, field, &nvalue);
+  gst_structure_get (structure, field, type, v, NULL);
+
+done:
+  g_value_reset (&nvalue);
+  return res;
+
+fail:
+  res = FALSE;
+  goto done;
+}
+
+#define TRY_GET(name, type, var, def) G_STMT_START {\
+  g_assert (type != GST_TYPE_CLOCK_TIME);                      \
+  if (!_get_structure_value (structure, name, type, var))\
+    *var = def;                                             \
 } G_STMT_END
 
 typedef struct
@@ -110,6 +141,21 @@ typedef struct
 } FieldsError;
 
 static gboolean
+enum_from_str (GType type, const gchar * str_enum, guint * enum_value)
+{
+  GValue value = G_VALUE_INIT;
+  g_value_init (&value, type);
+
+  if (!gst_value_deserialize (&value, str_enum))
+    return FALSE;
+
+  *enum_value = g_value_get_enum (&value);
+  g_value_unset (&value);
+
+  return TRUE;
+}
+
+static gboolean
 _check_field (GQuark field_id, const GValue * value, FieldsError * fields_error)
 {
   guint i;
@@ -150,7 +196,6 @@ _check_fields (GstStructure * structure, FieldsError fields_error,
 
     if (error)
       *error = g_error_new_literal (GES_ERROR, 0, msg->str);
-    GST_ERROR ("%s", msg->str);
 
     g_string_free (msg, TRUE);
 
@@ -160,91 +205,281 @@ _check_fields (GstStructure * structure, FieldsError fields_error,
   return TRUE;
 }
 
+static GESTimelineElement *
+find_element_for_property (GESTimeline * timeline, GstStructure * structure,
+    gchar ** property_name, gboolean require_track_element, GError ** error)
+{
+  GList *tmp;
+  const gchar *element_name;
+  GESTimelineElement *element;
+
+  element_name = gst_structure_get_string (structure, "element-name");
+  if (element_name == NULL) {
+    element = g_object_get_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA);
+    if (element)
+      gst_object_ref (element);
+  } else {
+    element = ges_timeline_get_element (timeline, element_name);
+  }
+
+  if (*property_name == NULL) {
+    gchar *tmpstr = *property_name;
+    const gchar *name = gst_structure_get_name (structure);
+
+    REPORT_UNLESS (g_str_has_prefix (name, "set-"), err,
+        "Could not find any property name in %" GST_PTR_FORMAT, structure);
+
+    *property_name = g_strdup (&tmpstr[4]);
+
+    g_free (tmpstr);
+  }
+
+  if (element) {
+    if (!ges_timeline_element_lookup_child (element,
+            *property_name, NULL, NULL)) {
+      gst_clear_object (&element);
+    }
+  }
+
+  if (!element) {
+    element = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA);
+    if (element)
+      gst_object_ref (element);
+  }
+
+  REPORT_UNLESS (GES_IS_TIMELINE_ELEMENT (element), err,
+      "Could not find child %s from %" GST_PTR_FORMAT, element_name, structure);
+
+  if (!require_track_element || GES_IS_TRACK_ELEMENT (element))
+    return element;
+
+
+  REPORT_UNLESS (GES_IS_CONTAINER (element), err,
+      "Could not find child %s from %" GST_PTR_FORMAT, element_name, structure);
+
+  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+    if (ges_timeline_element_lookup_child (tmp->data, *property_name, NULL,
+            NULL)) {
+      gst_object_replace ((GstObject **) & element, tmp->data);
+
+      break;
+    }
+  }
+
+  REPORT_UNLESS (GES_IS_TRACK_ELEMENT (element), err,
+      "Could not find TrackElement from %" GST_PTR_FORMAT, structure);
+
+  return element;
+
+err:
+  g_clear_object (&element);
+  return element;
+}
+
+gboolean
+_ges_save_timeline_if_needed (GESTimeline * timeline, GstStructure * structure,
+    GError ** error)
+{
+  gboolean res = TRUE;
+  const gchar *nested_timeline_id =
+      gst_structure_get_string (structure, "project-uri");
+
+  if (nested_timeline_id) {
+    res = ges_timeline_save_to_uri (timeline, nested_timeline_id, NULL, TRUE,
+        error);
+  }
+
+  return res;
+}
+
+typedef struct
+{
+  GstTimedValueControlSource *source;
+  GstStructure *structure;
+  GError *error;
+  const gchar *property_name;
+  gboolean res;
+} SetKeyframesData;
+
+static gboolean
+un_set_keyframes_foreach (GQuark field_id, const GValue * value,
+    SetKeyframesData * d)
+{
+  GValue v = G_VALUE_INIT;
+  GError **error = &d->error;
+  gchar *tmp;
+  gint i;
+  const gchar *valid_fields[] = {
+    "element-name", "property-name", "value", "timestamp", "project-uri",
+    "binding-type", "source-type", "interpolation-mode", "interpolation-mode",
+    NULL
+  };
+  const gchar *field = g_quark_to_string (field_id);
+  gdouble ts;
+
+  for (i = 0; valid_fields[i]; i++) {
+    if (g_quark_from_string (valid_fields[i]) == field_id)
+      return TRUE;
+  }
+
+  errno = 0;
+  ts = g_strtod (field, &tmp);
+
+  REPORT_UNLESS (errno == 0 && field != tmp, err,
+      "Could not convert `%s` to GstClockTime (%s)", field, g_strerror (errno));
+
+  if (gst_structure_has_name (d->structure, "remove-keyframe")) {
+    REPORT_UNLESS (gst_timed_value_control_source_unset (d->source,
+            ts * GST_SECOND), err, "Could not unset keyframe at %f", ts);
+
+    return TRUE;
+  }
+
+  g_value_init (&v, G_TYPE_DOUBLE);
+  REPORT_UNLESS (g_value_transform (value, &v), err,
+      "Could not convert keyframe %f value %s to double", ts,
+      gst_value_serialize (value));
+
+  REPORT_UNLESS (gst_timed_value_control_source_set (d->source, ts * GST_SECOND,
+          g_value_get_double (&v)), err, "Could not set keyframe %f=%f", ts,
+      g_value_get_double (&v));
+
+  g_value_reset (&v);
+  return TRUE;
+
+err:
+  if (v.g_type)
+    g_value_reset (&v);
+  d->res = FALSE;
+  return FALSE;
+}
+
 
 gboolean
 _ges_add_remove_keyframe_from_struct (GESTimeline * timeline,
     GstStructure * structure, GError ** error)
 {
-  GESTrackElement *element;
+  GESTimelineElement *element = NULL;
 
+  gboolean absolute;
   gdouble value;
   GstClockTime timestamp;
   GstControlBinding *binding = NULL;
   GstTimedValueControlSource *source = NULL;
-  gchar *element_name = NULL, *property_name = NULL;
+  gchar *property_name = NULL;
 
   gboolean ret = FALSE;
+  gboolean setting_value;
 
   const gchar *valid_fields[] =
-      { "element-name", "property-name", "value", "timestamp",
+      { "element-name", "property-name", "value", "timestamp", "project-uri",
     NULL
   };
 
   FieldsError fields_error = { valid_fields, NULL };
 
-  if (!_check_fields (structure, fields_error, error))
-    return FALSE;
+  if (gst_structure_has_field (structure, "value")) {
+    if (!_check_fields (structure, fields_error, error))
+      return FALSE;
+    GET_AND_CHECK ("timestamp", GST_TYPE_CLOCK_TIME, &timestamp, done);
+  } else {
+    REPORT_UNLESS (!gst_structure_has_field (structure, "timestamp"), done,
+        "Doesn't have a `value` field in %" GST_PTR_FORMAT
+        " but has a `timestamp`" " that can't work!", structure);
+  }
 
-  GET_AND_CHECK ("element-name", G_TYPE_STRING, &element_name, done);
   GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, done);
-  GET_AND_CHECK ("value", G_TYPE_DOUBLE, &value, done);
-  GET_AND_CHECK ("timestamp", GST_TYPE_CLOCK_TIME, &timestamp, done);
-
-  element =
-      GES_TRACK_ELEMENT (ges_timeline_get_element (timeline, element_name));
-
-  if (!GES_IS_TRACK_ELEMENT (element)) {
-    *error =
-        g_error_new (GES_ERROR, 0, "Could not find TrackElement %s",
-        element_name);
-
+  if (!(element =
+          find_element_for_property (timeline, structure, &property_name, TRUE,
+              error)))
     goto done;
-  }
 
-  binding = ges_track_element_get_control_binding (element, property_name);
-  if (binding == NULL) {
-    *error = g_error_new (GES_ERROR, 0, "No control binding found for %s:%s"
-        " you should first set-control-binding on it",
-        element_name, property_name);
+  REPORT_UNLESS (binding =
+      ges_track_element_get_control_binding (GES_TRACK_ELEMENT (element),
+          property_name),
+      done, "No control binding found for %" GST_PTR_FORMAT, structure);
 
-    goto done;
+  g_object_get (binding, "control-source", &source, NULL);
+  REPORT_UNLESS (source, done,
+      "No control source found for '%" GST_PTR_FORMAT
+      "' you should first set-control-binding on it", structure);
+  REPORT_UNLESS (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source), done,
+      "You can use add-keyframe"
+      " only on GstTimedValueControlSource not %s",
+      G_OBJECT_TYPE_NAME (source));
+
+  if (!gst_structure_has_field (structure, "value")) {
+    SetKeyframesData d = {
+      source, structure, NULL, property_name, TRUE,
+    };
+    gst_structure_foreach (structure,
+        (GstStructureForeachFunc) un_set_keyframes_foreach, &d);
+    if (!d.res)
+      g_propagate_error (error, d.error);
+
+    return d.res;
   }
 
-  g_object_get (binding, "control-source", &source, NULL);
+  g_object_get (binding, "absolute", &absolute, NULL);
+  if (absolute) {
+    GParamSpec *pspec;
+    const GValue *v;
+    GValue v2 = G_VALUE_INIT;
 
-  if (source == NULL) {
-    *error = g_error_new (GES_ERROR, 0, "No control source found for %s:%s"
-        " you should first set-control-binding on it",
-        element_name, property_name);
+    if (!ges_timeline_element_lookup_child (element, property_name, NULL,
+            &pspec)) {
+      *error =
+          g_error_new (GES_ERROR, 0, "Could not get property %s for %s",
+          property_name, GES_TIMELINE_ELEMENT_NAME (element));
+      goto done;
+    }
 
-    goto done;
-  }
+    v = gst_structure_get_value (structure, "value");
+    if (!v) {
+      gchar *struct_str = gst_structure_to_string (structure);
 
-  if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
-    *error = g_error_new (GES_ERROR, 0, "You can use add-keyframe"
-        " only on GstTimedValueControlSource not %s",
-        G_OBJECT_TYPE_NAME (source));
+      *error = g_error_new (GES_ERROR, 0,
+          "Could not get the mandatory field 'value'"
+          " of type %s - fields in %s", g_type_name (pspec->value_type),
+          struct_str);
+      g_free (struct_str);
+      goto done;
+    }
 
-    goto done;
-  }
+    g_value_init (&v2, G_TYPE_DOUBLE);
+    if (!g_value_transform (v, &v2)) {
+      gchar *struct_str = gst_structure_to_string (structure);
 
-  if (!g_strcmp0 (gst_structure_get_name (structure), "add-keyframe"))
+      *error = g_error_new (GES_ERROR, 0,
+          "Could not get the mandatory field 'value'"
+          " of type %s - fields in %s", g_type_name (pspec->value_type),
+          struct_str);
+      g_free (struct_str);
+      goto done;
+    }
+    value = g_value_get_double (&v2);
+    g_value_reset (&v2);
+  } else
+    GET_AND_CHECK ("value", G_TYPE_DOUBLE, &value, done);
+
+  setting_value =
+      !g_strcmp0 (gst_structure_get_name (structure), "add-keyframe");
+  if (setting_value)
     ret = gst_timed_value_control_source_set (source, timestamp, value);
-  else {
+  else
     ret = gst_timed_value_control_source_unset (source, timestamp);
 
-    if (!ret) {
-      *error =
-          g_error_new (GES_ERROR, 0,
-          "Could not unset value for timestamp: %" GST_TIME_FORMAT,
-          GST_TIME_ARGS (timestamp));
-    }
+  if (!ret) {
+    *error =
+        g_error_new (GES_ERROR, 0,
+        "Could not %sset value for timestamp: %" GST_TIME_FORMAT,
+        setting_value ? "" : "un", GST_TIME_ARGS (timestamp));
   }
+  ret = _ges_save_timeline_if_needed (timeline, structure, error);
 
 done:
-  if (source)
-    gst_object_unref (source);
-  g_free (element_name);
+  gst_clear_object (&source);
+  gst_clear_object (&element);
   g_free (property_name);
 
   return ret;
@@ -323,29 +558,53 @@ ensure_uri (gchar * location)
     return gst_filename_to_uri (location, NULL);
 }
 
+static gboolean
+get_flags_from_string (GType type, const gchar * str_flags, guint * flags)
+{
+  GValue value = G_VALUE_INIT;
+  g_value_init (&value, type);
+
+  if (!gst_value_deserialize (&value, str_flags)) {
+    g_value_unset (&value);
+
+    return FALSE;
+  }
+
+  *flags = g_value_get_flags (&value);
+  g_value_unset (&value);
+
+  return TRUE;
+}
+
 gboolean
 _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
     GError ** error)
 {
-  GESAsset *asset;
-  GESLayer *layer;
+  GESAsset *asset = NULL;
+  GESLayer *layer = NULL;
   GESClip *clip;
   gint layer_priority;
   const gchar *name;
   const gchar *text;
   const gchar *pattern;
+  const gchar *track_types_str;
+  const gchar *nested_timeline_id;
   gchar *asset_id = NULL;
   gchar *check_asset_id = NULL;
   const gchar *type_string;
   GType type;
   gboolean res = FALSE;
+  GESTrackType track_types = GES_TRACK_TYPE_UNKNOWN;
 
+  GESFrameNumber start_frame = GES_FRAME_NUMBER_NONE, inpoint_frame =
+      GES_FRAME_NUMBER_NONE, duration_frame = GES_FRAME_NUMBER_NONE;
   GstClockTime duration = 1 * GST_SECOND, inpoint = 0, start =
       GST_CLOCK_TIME_NONE;
 
   const gchar *valid_fields[] =
       { "asset-id", "pattern", "name", "layer-priority", "layer", "type",
-    "start", "inpoint", "duration", "text", NULL
+    "start", "inpoint", "duration", "text", "track-types", "project-uri",
+    NULL
   };
 
   FieldsError fields_error = { valid_fields, NULL };
@@ -362,9 +621,21 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
   if (layer_priority == -1)
     TRY_GET ("layer", G_TYPE_INT, &layer_priority, -1);
   TRY_GET_STRING ("type", &type_string, "GESUriClip");
-  TRY_GET ("start", GST_TYPE_CLOCK_TIME, &start, GST_CLOCK_TIME_NONE);
-  TRY_GET ("inpoint", GST_TYPE_CLOCK_TIME, &inpoint, 0);
-  TRY_GET ("duration", GST_TYPE_CLOCK_TIME, &duration, GST_CLOCK_TIME_NONE);
+  TRY_GET_TIME ("start", &start, &start_frame, GST_CLOCK_TIME_NONE);
+  TRY_GET_TIME ("inpoint", &inpoint, &inpoint_frame, 0);
+  TRY_GET_TIME ("duration", &duration, &duration_frame, GST_CLOCK_TIME_NONE);
+  TRY_GET_STRING ("track-types", &track_types_str, NULL);
+  TRY_GET_STRING ("project-uri", &nested_timeline_id, NULL);
+
+  if (track_types_str) {
+    if (!get_flags_from_string (GES_TYPE_TRACK_TYPE, track_types_str,
+            &track_types)) {
+      *error =
+          g_error_new (GES_ERROR, 0, "Invalid track types: %s",
+          track_types_str);
+    }
+
+  }
 
   if (!(type = g_type_from_name (type_string))) {
     *error = g_error_new (GES_ERROR, 0, "This type doesn't exist : %s",
@@ -379,6 +650,7 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
     asset_id = g_strdup (check_asset_id);
   }
 
+  gst_structure_set (structure, "asset-id", G_TYPE_STRING, asset_id, NULL);
   asset = _ges_get_asset_from_timeline (timeline, type, asset_id, error);
   if (!asset) {
     res = FALSE;
@@ -407,13 +679,31 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
     goto beach;
   }
 
+  if (GES_FRAME_NUMBER_IS_VALID (start_frame))
+    start = ges_timeline_get_frame_time (timeline, start_frame);
+
+  if (GES_FRAME_NUMBER_IS_VALID (inpoint_frame)) {
+    inpoint =
+        ges_clip_asset_get_frame_time (GES_CLIP_ASSET (asset), inpoint_frame);
+    if (!GST_CLOCK_TIME_IS_VALID (inpoint)) {
+      *error =
+          g_error_new (GES_ERROR, 0, "Could not get inpoint from frame %"
+          G_GINT64_FORMAT, inpoint_frame);
+      goto beach;
+    }
+  }
+
+  if (GES_FRAME_NUMBER_IS_VALID (duration_frame)) {
+    duration = ges_timeline_get_frame_time (timeline, duration_frame);
+  }
+
   if (GES_IS_URI_CLIP_ASSET (asset) && !GST_CLOCK_TIME_IS_VALID (duration)) {
     duration = GST_CLOCK_DIFF (inpoint,
         ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset)));
   }
 
   clip = ges_layer_add_asset (layer, asset, start, inpoint, duration,
-      GES_TRACK_TYPE_UNKNOWN);
+      track_types);
 
   if (clip) {
     res = TRUE;
@@ -429,17 +719,11 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
 
     if (GES_IS_TEST_CLIP (clip)) {
       if (pattern) {
-        GEnumClass *enum_class =
-            G_ENUM_CLASS (g_type_class_ref (GES_VIDEO_TEST_PATTERN_TYPE));
-        GEnumValue *value = g_enum_get_value_by_nick (enum_class, pattern);
-
-        if (!value) {
-          res = FALSE;
-          goto beach;
-        }
+        guint v;
 
-        ges_test_clip_set_vpattern (GES_TEST_CLIP (clip), value->value);
-        g_type_class_unref (enum_class);
+        REPORT_UNLESS (enum_from_str (GES_VIDEO_TEST_PATTERN_TYPE, pattern, &v),
+            beach, "Invalid pattern: %s", pattern);
+        ges_test_clip_set_vpattern (GES_TEST_CLIP (clip), v);
       }
     }
 
@@ -455,9 +739,12 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
           name, asset_id);
     }
   } else {
-    *error = g_error_new (GES_ERROR, 0,
+    *error =
+        g_error_new (GES_ERROR, 0,
         "Couldn't add clip with id %s to layer with priority %d", asset_id,
         layer_priority);
+    res = FALSE;
+    goto beach;
   }
 
   if (res) {
@@ -465,26 +752,91 @@ _ges_add_clip_from_struct (GESTimeline * timeline, GstStructure * structure,
     g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, NULL);
   }
 
-  gst_object_unref (layer);
+  res = _ges_save_timeline_if_needed (timeline, structure, error);
 
 beach:
+  gst_clear_object (&layer);
+  gst_clear_object (&asset);
   g_free (asset_id);
   g_free (check_asset_id);
   return res;
 }
 
 gboolean
+_ges_add_track_from_struct (GESTimeline * timeline,
+    GstStructure * structure, GError ** error)
+{
+  const gchar *ttype;
+  GESTrack *track;
+  GstCaps *caps;
+
+  const gchar *valid_fields[] = { "type", "restrictions", NULL };
+
+  FieldsError fields_error = { valid_fields, NULL };
+
+  if (!_check_fields (structure, fields_error, error))
+    return FALSE;
+
+  ttype = gst_structure_get_string (structure, "type");
+  if (!g_strcmp0 (ttype, "video")) {
+    track = GES_TRACK (ges_video_track_new ());
+  } else if (!g_strcmp0 (ttype, "audio")) {
+    track = GES_TRACK (ges_audio_track_new ());
+  } else {
+    g_set_error (error, GES_ERROR, 0, "Unhandled track type: `%s`", ttype);
+
+    return FALSE;
+  }
+
+  if (gst_structure_has_field (structure, "restrictions")) {
+    GstStructure *restriction_struct;
+    gchar *restriction_str;
+
+    if (gst_structure_get (structure, "restrictions", GST_TYPE_STRUCTURE,
+            &restriction_struct, NULL)) {
+      caps = gst_caps_new_full (restriction_struct, NULL);
+    } else if (gst_structure_get (structure, "restrictions", G_TYPE_STRING,
+            &restriction_str, NULL)) {
+      caps = gst_caps_from_string (restriction_str);
+
+      if (!caps) {
+        g_set_error (error, GES_ERROR, 0, "Invalid restrictions caps: %s",
+            restriction_str);
+
+        g_free (restriction_str);
+        return FALSE;
+      }
+      g_free (restriction_str);
+    } else if (!gst_structure_get (structure, "restrictions", GST_TYPE_CAPS,
+            &caps, NULL)) {
+      gchar *tmp = gst_structure_to_string (structure);
+
+      g_set_error (error, GES_ERROR, 0, "Can't use restrictions caps from %s",
+          tmp);
+
+      g_object_unref (track);
+      return FALSE;
+    }
+
+    ges_track_set_restriction_caps (track, caps);
+    gst_caps_unref (caps);
+  }
+
+  return ges_timeline_add_track (timeline, track);
+}
+
+gboolean
 _ges_container_add_child_from_struct (GESTimeline * timeline,
     GstStructure * structure, GError ** error)
 {
-  GESAsset *asset;
+  GESAsset *asset = NULL;
   GESContainer *container;
   GESTimelineElement *child = NULL;
   const gchar *container_name, *child_name, *child_type, *id;
 
   gboolean res = TRUE;
-  const gchar *valid_fields[] = { "container-name", "asset-id",
-    "child-type", "child-name", NULL
+  const gchar *valid_fields[] = { "container-name", "asset-id", "inpoint",
+    "child-type", "child-name", "project-uri", NULL
   };
 
   FieldsError fields_error = { valid_fields, NULL };
@@ -501,7 +853,14 @@ _ges_container_add_child_from_struct (GESTimeline * timeline,
         GES_CONTAINER (ges_timeline_get_element (timeline, container_name));
   }
 
-  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
+  if (!GES_IS_CONTAINER (container)) {
+    *error =
+        g_error_new (GES_ERROR, 0, "Could not find container: %s (%p)",
+        container_name, container);
+
+    res = FALSE;
+    goto beach;
+  }
 
   id = gst_structure_get_string (structure, "asset-id");
   child_type = gst_structure_get_string (structure, "child-type");
@@ -518,7 +877,7 @@ _ges_container_add_child_from_struct (GESTimeline * timeline,
 
     child = GES_TIMELINE_ELEMENT (ges_asset_extract (asset, NULL));
     if (!GES_IS_TIMELINE_ELEMENT (child)) {
-      g_error_new (GES_ERROR, 0, "Could not extract child element");
+      *error = g_error_new (GES_ERROR, 0, "Could not extract child element");
 
       goto beach;
     }
@@ -528,14 +887,15 @@ _ges_container_add_child_from_struct (GESTimeline * timeline,
   if (!child && child_name) {
     child = ges_timeline_get_element (timeline, child_name);
     if (!GES_IS_TIMELINE_ELEMENT (child)) {
-      g_error_new (GES_ERROR, 0, "Could not find child element");
+      *error = g_error_new (GES_ERROR, 0, "Could not find child element");
 
       goto beach;
     }
   }
 
   if (!child) {
-    g_error_new (GES_ERROR, 0, "Wong parametters, could not get a child");
+    *error =
+        g_error_new (GES_ERROR, 0, "Wrong parameters, could not get a child");
 
     return FALSE;
   }
@@ -545,14 +905,55 @@ _ges_container_add_child_from_struct (GESTimeline * timeline,
   else
     child_name = GES_TIMELINE_ELEMENT_NAME (child);
 
+  if (gst_structure_has_field (structure, "inpoint")) {
+    GstClockTime inpoint;
+    GESFrameNumber finpoint;
+
+    if (!GES_IS_TRACK_ELEMENT (child)) {
+      *error = g_error_new (GES_ERROR, 0, "Child %s is not a trackelement"
+          ", can't set inpoint.", child_name);
+
+      gst_object_unref (child);
+
+      goto beach;
+    }
+
+    if (!ges_util_structure_get_clocktime (structure, "inpoint", &inpoint,
+            &finpoint)) {
+      *error = g_error_new (GES_ERROR, 0, "Could not use inpoint.");
+      gst_object_unref (child);
+
+      goto beach;
+    }
+
+    if (!ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child),
+            TRUE)) {
+      *error =
+          g_error_new (GES_ERROR, 0,
+          "Could not set inpoint as %s can't have an internal source",
+          child_name);
+      gst_object_unref (child);
+
+      goto beach;
+    }
+
+    if (GES_FRAME_NUMBER_IS_VALID (finpoint))
+      inpoint = ges_timeline_get_frame_time (timeline, finpoint);
+
+    ges_timeline_element_set_inpoint (child, inpoint);
+
+  }
+
   res = ges_container_add (container, child);
   if (res == FALSE) {
     g_error_new (GES_ERROR, 0, "Could not add child to container");
   } else {
     g_object_set_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA, child);
   }
+  res = _ges_save_timeline_if_needed (timeline, structure, error);
 
 beach:
+  gst_clear_object (&asset);
   return res;
 }
 
@@ -561,74 +962,158 @@ _ges_set_child_property_from_struct (GESTimeline * timeline,
     GstStructure * structure, GError ** error)
 {
   const GValue *value;
+  GValue prop_value = G_VALUE_INIT;
+  gboolean prop_value_set = FALSE;
+  gchar *property_name;
   GESTimelineElement *element;
-  const gchar *property_name, *element_name;
+  gchar *serialized;
+  gboolean res;
 
-  const gchar *valid_fields[] = { "element-name", "property", "value", NULL };
+  const gchar *valid_fields[] =
+      { "element-name", "property", "value", "project-uri", NULL };
 
   FieldsError fields_error = { valid_fields, NULL };
 
   if (!_check_fields (structure, fields_error, error))
     return FALSE;
 
-  element_name = gst_structure_get_string (structure, "element-name");
-  if (element_name == NULL)
-    element = g_object_get_qdata (G_OBJECT (timeline), LAST_CHILD_QDATA);
-  else
-    element = ges_timeline_get_element (timeline, element_name);
+  GET_AND_CHECK ("property", G_TYPE_STRING, &property_name, err);
 
-  property_name = gst_structure_get_string (structure, "property");
-  if (property_name == NULL) {
-    const gchar *name = gst_structure_get_name (structure);
-
-    if (g_str_has_prefix (name, "set-"))
-      property_name = &name[4];
-    else {
-      gchar *struct_str = gst_structure_to_string (structure);
+  if (!(element =
+          find_element_for_property (timeline, structure, &property_name, FALSE,
+              error)))
+    goto err;
 
-      *error =
-          g_error_new (GES_ERROR, 0, "Could not find any property name in %s",
-          struct_str);
-      g_free (struct_str);
+  value = gst_structure_get_value (structure, "value");
 
-      return FALSE;
+  if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
+    GParamSpec *pspec;
+    if (ges_timeline_element_lookup_child (element, property_name, NULL,
+            &pspec)) {
+      GType p_type = pspec->value_type;
+      g_param_spec_unref (pspec);
+      if (p_type != G_TYPE_STRING) {
+        const gchar *val_string = g_value_get_string (value);
+        g_value_init (&prop_value, p_type);
+        if (!gst_value_deserialize (&prop_value, val_string)) {
+          *error = g_error_new (GES_ERROR, 0, "Could not set the property %s "
+              "because the value %s could not be deserialized to the %s type",
+              property_name, val_string, g_type_name (p_type));
+          return FALSE;
+        }
+        prop_value_set = TRUE;
+      }
     }
+    /* else, let the setter fail below */
   }
 
-  if (element) {
-    if (!ges_track_element_lookup_child (GES_TRACK_ELEMENT (element),
-            property_name, NULL, NULL))
-      element = NULL;
+  if (!prop_value_set) {
+    g_value_init (&prop_value, G_VALUE_TYPE (value));
+    g_value_copy (value, &prop_value);
   }
 
-  if (!element) {
-    element = g_object_get_qdata (G_OBJECT (timeline), LAST_CONTAINER_QDATA);
+  serialized = gst_value_serialize (&prop_value);
+  GST_INFO_OBJECT (element, "Setting property %s to %s\n", property_name,
+      serialized);
+  g_free (serialized);
 
-    if (element == NULL) {
-      *error =
-          g_error_new (GES_ERROR, 0,
-          "Could not find anywhere to set property: %s", property_name);
+  res = ges_timeline_element_set_child_property (element, property_name,
+      &prop_value);
+  g_value_unset (&prop_value);
+  if (!res) {
+    guint n_specs, i;
+    GParamSpec **specs =
+        ges_timeline_element_list_children_properties (element, &n_specs);
+    GString *errstr = g_string_new (NULL);
 
-      return FALSE;
-    }
-  }
+    g_string_append_printf (errstr,
+        "\n  Could not set property `%s` on `%s`, valid properties:\n",
+        property_name, GES_TIMELINE_ELEMENT_NAME (element));
 
-  if (!GES_IS_TIMELINE_ELEMENT (element)) {
-    *error =
-        g_error_new (GES_ERROR, 0, "Could not find child %s", element_name);
+    for (i = 0; i < n_specs; i++)
+      g_string_append_printf (errstr, "    - %s\n", specs[i]->name);
+    g_free (specs);
+
+    *error = g_error_new_literal (GES_ERROR, 0, errstr->str);
+    g_string_free (errstr, TRUE);
 
     return FALSE;
   }
+  g_free (property_name);
+  return _ges_save_timeline_if_needed (timeline, structure, error);
 
-  value = gst_structure_get_value (structure, "value");
+err:
+  g_free (property_name);
+  return FALSE;
+}
 
-  GST_DEBUG ("%s Setting %s property to %p",
-      element->name, property_name, value);
+gboolean
+_ges_set_control_source_from_struct (GESTimeline * timeline,
+    GstStructure * structure, GError ** error)
+{
+  guint mode;
+  gboolean res = FALSE;
+  GESTimelineElement *element = NULL;
 
-  ges_timeline_element_set_child_property (element, property_name,
-      (GValue *) value);
+  GstControlSource *source = NULL;
+  gchar *property_name, *binding_type = NULL,
+      *source_type = NULL, *interpolation_mode = NULL;
 
-  return TRUE;
+  GET_AND_CHECK ("property-name", G_TYPE_STRING, &property_name, beach);
+
+  if (!(element =
+          find_element_for_property (timeline, structure, &property_name, TRUE,
+              error)))
+    goto beach;
+
+  if (GES_IS_CLIP (element)) {
+    GList *tmp;
+    for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+      if (ges_timeline_element_lookup_child (tmp->data, property_name, NULL,
+              NULL)) {
+        gst_object_replace ((GstObject **) & element, tmp->data);
+
+        break;
+      }
+    }
+  }
+
+  REPORT_UNLESS (GES_IS_TRACK_ELEMENT (element), beach,
+      "Could not find TrackElement from %" GST_PTR_FORMAT, structure);
+
+  TRY_GET ("binding-type", G_TYPE_STRING, &binding_type, NULL);
+  TRY_GET ("source-type", G_TYPE_STRING, &source_type, NULL);
+  TRY_GET ("interpolation-mode", G_TYPE_STRING, &interpolation_mode, NULL);
+
+  if (!binding_type)
+    binding_type = g_strdup ("direct");
+
+  REPORT_UNLESS (source_type == NULL
+      || !g_strcmp0 (source_type, "interpolation"), beach,
+      "Interpolation type %s not supported", source_type);
+  source = gst_interpolation_control_source_new ();
+
+  if (interpolation_mode)
+    REPORT_UNLESS (enum_from_str (GST_TYPE_INTERPOLATION_MODE,
+            interpolation_mode, &mode), beach,
+        "Wrong interpolation mode: %s", interpolation_mode);
+  else
+    mode = GST_INTERPOLATION_MODE_LINEAR;
+
+  g_object_set (source, "mode", mode, NULL);
+
+  res = ges_track_element_set_control_source (GES_TRACK_ELEMENT (element),
+      source, property_name, binding_type);
+
+beach:
+  gst_clear_object (&element);
+  gst_clear_object (&source);
+  g_free (property_name);
+  g_free (binding_type);
+  g_free (source_type);
+  g_free (interpolation_mode);
+
+  return res;
 }
 
 #undef GET_AND_CHECK
index 6963bc2..9dd7a69 100644 (file)
@@ -17,9 +17,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-
-#ifndef __GES_STRUCTURED_INTERFACE__
-#define __GES_STRUCTURED_INTERFACE__
+#pragma once
 
 #include <ges/ges.h>
 
@@ -39,6 +37,11 @@ _ges_add_clip_from_struct                     (GESTimeline * timeline,
                                                GError ** error);
 
 G_GNUC_INTERNAL gboolean
+_ges_add_track_from_struct                    (GESTimeline * timeline,
+                                               GstStructure * structure,
+                                               GError ** error);
+
+G_GNUC_INTERNAL gboolean
 _ges_container_add_child_from_struct          (GESTimeline * timeline,
                                                GstStructure * structure,
                                                GError ** error);
@@ -56,7 +59,11 @@ _ges_get_asset_from_timeline                  (GESTimeline * timeline,
 G_GNUC_INTERNAL GESLayer *
 _ges_get_layer_by_priority                    (GESTimeline * timeline,
                                                gint priority);
+G_GNUC_INTERNAL gboolean
+_ges_save_timeline_if_needed (GESTimeline* timeline, GstStructure* structure, GError** error);
 
-G_END_DECLS
+G_GNUC_INTERNAL gboolean
+_ges_set_control_source_from_struct (GESTimeline * timeline,
+    GstStructure * structure, GError ** error);
 
-#endif /* __GES_STRUCTURED_INTERFACE__*/
+G_END_DECLS
index 77bd5f3..0eba359 100644 (file)
@@ -1,6 +1,8 @@
 /* GStreamer Editing Services
  * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
  *               2009 Nokia Corporation
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  *
  * Useful for testing purposes.
  *
- * You can use the ges_asset_request_simple API to create an Asset
- * capable of extracting GESTestClip-s
+ * ## Asset
+ *
+ * The default asset ID is GESTestClip, but the framerate and video
+ * size can be overridden using an ID of the form:
+ *
+ * ```
+ * framerate=60/1, width=1920, height=1080, max-duration=5.0
+ * ```
+ * Note: `max-duration` can be provided in seconds as float, or as GstClockTime
+ * as guint64 or gint.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #define DEFAULT_VOLUME 1.0
 #define DEFAULT_VPATTERN GES_VIDEO_TEST_PATTERN_SMPTE
 
+G_DECLARE_FINAL_TYPE (GESTestClipAsset, ges_test_clip_asset, GES,
+    TEST_CLIP_ASSET, GESSourceClipAsset);
+
+struct _GESTestClipAsset
+{
+  GESSourceClipAsset parent;
+
+  gint natural_framerate_n;
+  gint natural_framerate_d;
+  gint natural_width;
+  gint natural_height;
+  GstClockTime max_duration;
+};
+
+#define GES_TYPE_TEST_CLIP_ASSET (ges_test_clip_asset_get_type())
+G_DEFINE_TYPE (GESTestClipAsset, ges_test_clip_asset,
+    GES_TYPE_SOURCE_CLIP_ASSET);
+
+static gboolean
+_get_natural_framerate (GESClipAsset * asset, gint * framerate_n,
+    gint * framerate_d)
+{
+  GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset);
+
+  *framerate_n = self->natural_framerate_n;
+  *framerate_d = self->natural_framerate_d;
+  return TRUE;
+}
+
+static GstClockTime
+ges_test_clip_asset_get_max_duration (GESAsset * asset)
+{
+  GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset);
+
+  return GES_TEST_CLIP_ASSET (self)->max_duration;
+}
+
+gboolean
+ges_test_clip_asset_get_natural_size (GESAsset * asset, gint * width,
+    gint * height)
+{
+  GESTestClipAsset *self = GES_TEST_CLIP_ASSET (asset);
+
+  *width = self->natural_width;
+  *height = self->natural_height;
+
+  return TRUE;
+}
+
+static void
+ges_test_clip_asset_constructed (GObject * gobject)
+{
+  GESFrameNumber fmax_dur = GES_FRAME_NUMBER_NONE;
+  GESTestClipAsset *self = GES_TEST_CLIP_ASSET (gobject);
+  GstStructure *structure =
+      gst_structure_from_string (ges_asset_get_id (GES_ASSET (self)), NULL);
+
+  g_assert (structure);
+
+  gst_structure_get_int (structure, "width", &self->natural_width);
+  gst_structure_get_int (structure, "height", &self->natural_height);
+  gst_structure_get_fraction (structure, "framerate",
+      &self->natural_framerate_n, &self->natural_framerate_d);
+  ges_util_structure_get_clocktime (structure, "max-duration",
+      &self->max_duration, &fmax_dur);
+  if (GES_FRAME_NUMBER_IS_VALID (fmax_dur))
+    self->max_duration =
+        gst_util_uint64_scale (fmax_dur, self->natural_framerate_d * GST_SECOND,
+        self->natural_framerate_n);
+  gst_structure_free (structure);
+
+  G_OBJECT_CLASS (ges_test_clip_asset_parent_class)->constructed (gobject);
+}
+
+static void
+ges_test_clip_asset_class_init (GESTestClipAssetClass * klass)
+{
+  GESClipAssetClass *clip_asset_class = GES_CLIP_ASSET_CLASS (klass);
+
+  clip_asset_class->get_natural_framerate = _get_natural_framerate;
+  G_OBJECT_CLASS (klass)->constructed = ges_test_clip_asset_constructed;
+}
+
+static void
+ges_test_clip_asset_init (GESTestClipAsset * self)
+{
+  self->natural_width = DEFAULT_WIDTH;
+  self->natural_height = DEFAULT_HEIGHT;
+  self->natural_framerate_n = DEFAULT_FRAMERATE_N;
+  self->natural_framerate_d = DEFAULT_FRAMERATE_D;
+  self->max_duration = GST_CLOCK_TIME_NONE;
+}
+
 struct _GESTestClipPrivate
 {
   gboolean mute;
@@ -60,7 +163,88 @@ enum
   PROP_VOLUME,
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GESTestClip, ges_test_clip, GES_TYPE_SOURCE_CLIP);
+typedef struct
+{
+  const gchar *name;
+  GType type;
+} ValidField;
+
+gchar *
+ges_test_source_asset_check_id (GType type, const gchar * id, GError ** error)
+{
+  if (id && g_strcmp0 (id, g_type_name (type))) {
+    gchar *res = NULL;
+    GstStructure *structure = gst_structure_from_string (id, NULL);
+
+    if (!structure) {
+      gchar *struct_str = g_strdup_printf ("%s,%s", g_type_name (type), id);
+
+      structure = gst_structure_from_string (struct_str, NULL);
+      g_free (struct_str);
+    }
+
+    GST_INFO ("Test source ID: %" GST_PTR_FORMAT, structure);
+    if (!structure) {
+      g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
+          "GESTestClipAsset ID should be in the form: `framerate=30/1, "
+          "width=1920, height=1080, got %s", id);
+    } else {
+      static ValidField valid_fields[] = {
+        {"width", G_TYPE_INT},
+        {"height", G_TYPE_INT},
+        {"framerate", G_TYPE_NONE},     /* GST_TYPE_FRACTION is not constant */
+        {"max-duration", GST_TYPE_CLOCK_TIME},
+        {"disable-timecodestamper", G_TYPE_BOOLEAN},
+      };
+      gint i;
+
+      for (i = 0; i < G_N_ELEMENTS (valid_fields); i++) {
+        if (gst_structure_has_field (structure, valid_fields[i].name)) {
+          GstClockTime ts;
+          GESFrameNumber fn;
+          ValidField field = valid_fields[i];
+          GType type =
+              field.type == G_TYPE_NONE ? GST_TYPE_FRACTION : field.type;
+
+          if (!(gst_structure_has_field_typed (structure, field.name,
+                      type) ||
+                  (type == GST_TYPE_CLOCK_TIME &&
+                      ges_util_structure_get_clocktime (structure, field.name,
+                          &ts, &fn)))) {
+
+            g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
+                "Field %s has wrong type, %s, expected %s", field.name,
+                g_type_name (gst_structure_get_field_type (structure,
+                        field.name)), g_type_name (type));
+
+            gst_structure_free (structure);
+
+            return FALSE;
+          }
+        }
+      }
+      res = gst_structure_to_string (structure);
+      gst_structure_free (structure);
+    }
+
+    return res;
+  }
+
+  return g_strdup (g_type_name (type));
+}
+
+static void
+ges_extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->asset_type = GES_TYPE_TEST_CLIP_ASSET;
+  iface->check_id = ges_test_source_asset_check_id;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GESTestClip, ges_test_clip, GES_TYPE_SOURCE_CLIP,
+    G_ADD_PRIVATE (GESTestClip)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
+        ges_extractable_interface_init));
+
 
 static GESTrackElement
     * ges_test_clip_create_track_element (GESClip * clip, GESTrackType type);
@@ -169,6 +353,7 @@ ges_test_clip_class_init (GESTestClipClass * klass)
 static void
 ges_test_clip_init (GESTestClip * self)
 {
+  SUPRESS_UNUSED_WARNING (GES_IS_TEST_CLIP_ASSET);
   self->priv = ges_test_clip_get_instance_private (self);
 
   self->priv->freq = 0;
@@ -331,6 +516,7 @@ ges_test_clip_get_volume (GESTestClip * self)
 static GESTrackElement *
 ges_test_clip_create_track_element (GESClip * clip, GESTrackType type)
 {
+  GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (clip));
   GESTestClipPrivate *priv = GES_TEST_CLIP (clip)->priv;
   GESTrackElement *res = NULL;
 
@@ -338,7 +524,25 @@ ges_test_clip_create_track_element (GESClip * clip, GESTrackType type)
       ges_track_type_name (type));
 
   if (type == GES_TRACK_TYPE_VIDEO) {
-    res = (GESTrackElement *) ges_video_test_source_new ();
+    gchar *id = NULL;
+    GESAsset *videoasset;
+
+    if (asset) {
+      GstStructure *structure =
+          gst_structure_from_string (ges_asset_get_id (asset), NULL);
+
+      if (structure) {
+        id = g_strdup (gst_structure_get_name (structure));
+        gst_structure_free (structure);
+      }
+    }
+    /* Our asset ID has been verified and thus this should not fail ever */
+    videoasset = ges_asset_request (GES_TYPE_VIDEO_TEST_SOURCE, id, NULL);
+    g_assert (videoasset);
+    g_free (id);
+
+    res = (GESTrackElement *) ges_asset_extract (videoasset, NULL);
+    gst_object_unref (videoasset);
     ges_video_test_source_set_pattern (
         (GESVideoTestSource *) res, priv->vpattern);
   } else if (type == GES_TRACK_TYPE_AUDIO) {
@@ -351,6 +555,10 @@ ges_test_clip_create_track_element (GESClip * clip, GESTrackType type)
     ges_audio_test_source_set_volume ((GESAudioTestSource *) res, priv->volume);
   }
 
+  if (asset)
+    ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (res),
+        ges_test_clip_asset_get_max_duration (asset));
+
   return res;
 }
 
index 83ef166..6eaae99 100644 (file)
@@ -1,6 +1,8 @@
 /* GStreamer Editing Services
  * Copyright (C) 2009 Brandon Lewis <brandon.lewis@collabora.co.uk>
  *               2009 Nokia Corporation
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -18,8 +20,8 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TL_TESTSOURCE
-#define _GES_TL_TESTSOURCE
+#pragma once
+
 
 #include <glib-object.h>
 #include <ges/ges-enums.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TEST_CLIP ges_test_clip_get_type()
-
-#define GES_TEST_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TEST_CLIP, GESTestClip))
-
-#define GES_TEST_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TEST_CLIP, GESTestClipClass))
-
-#define GES_IS_TEST_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TEST_CLIP))
-
-#define GES_IS_TEST_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TEST_CLIP))
-
-#define GES_TEST_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TEST_CLIP, GESTestClipClass))
-
-typedef struct _GESTestClipPrivate GESTestClipPrivate;
+GES_DECLARE_TYPE(TestClip, test_clip, TEST_CLIP);
 
 /**
  * GESTestClip:
@@ -75,9 +61,6 @@ struct _GESTestClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_test_clip_get_type (void);
-
 GES_API void
 ges_test_clip_set_mute (GESTestClip * self, gboolean mute);
 
@@ -109,6 +92,3 @@ GES_API
 GESTestClip* ges_test_clip_new_for_nick(gchar * nick);
 
 G_END_DECLS
-
-#endif /* _GES_TL_TESTSOURCE */
-
index e46b59b..d12c165 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_OVERLAY_TEXT_CLIP
-#define _GES_OVERLAY_TEXT_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 
 G_BEGIN_DECLS
 #define GES_TYPE_OVERLAY_TEXT_CLIP ges_text_overlay_clip_get_type()
-#define GES_OVERLAY_TEXT_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_OVERLAY_TEXT_CLIP, GESTextOverlayClip))
-#define GES_OVERLAY_TEXT_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_OVERLAY_TEXT_CLIP, GESTextOverlayClipClass))
-#define GES_IS_OVERLAY_TEXT_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_OVERLAY_TEXT_CLIP))
-#define GES_IS_OVERLAY_TEXT_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_OVERLAY_TEXT_CLIP))
-#define GES_OVERLAY_TEXT_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_OVERLAY_TEXT_CLIP, GESTextOverlayClipClass))
-typedef struct _GESTextOverlayClipPrivate GESTextOverlayClipPrivate;
+GES_DECLARE_TYPE(TextOverlayClip, text_overlay_clip, OVERLAY_TEXT_CLIP);
 
 /**
  * GESTextOverlayClip:
@@ -69,9 +58,6 @@ struct _GESTextOverlayClipClass
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_text_overlay_clip_get_type (void);
-
 GES_API void
 ges_text_overlay_clip_set_text (GESTextOverlayClip * self,
     const gchar * text);
@@ -126,4 +112,3 @@ GES_API
 GESTextOverlayClip *ges_text_overlay_clip_new (void);
 
 G_END_DECLS
-#endif /* _GES_TL_OVERLAY */
index 98d90b8..2c95fa3 100644 (file)
@@ -69,14 +69,15 @@ static void
 ges_text_overlay_class_init (GESTextOverlayClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESTrackElementClass *bg_class = GES_TRACK_ELEMENT_CLASS (klass);
+  GESTrackElementClass *track_element_class = GES_TRACK_ELEMENT_CLASS (klass);
 
   object_class->get_property = ges_text_overlay_get_property;
   object_class->set_property = ges_text_overlay_set_property;
   object_class->dispose = ges_text_overlay_dispose;
   object_class->finalize = ges_text_overlay_finalize;
 
-  bg_class->create_element = ges_text_overlay_create_element;
+  track_element_class->create_element = ges_text_overlay_create_element;
+  track_element_class->ABI.abi.default_track_type = GES_TRACK_TYPE_VIDEO;
 }
 
 static void
@@ -429,10 +430,18 @@ ges_text_overlay_get_ypos (GESTextOverlay * self)
  *
  * Returns: (transfer floating) (nullable): The newly created #GESTextOverlay or
  * %NULL if something went wrong.
+ *
+ * Deprecated: 1.18: This should never be called by applications as this will
+ * be created by clips.
  */
 GESTextOverlay *
 ges_text_overlay_new (void)
 {
-  return g_object_new (GES_TYPE_TEXT_OVERLAY, "track-type",
-      GES_TRACK_TYPE_VIDEO, NULL);
+  GESTextOverlay *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_TEXT_OVERLAY, NULL, NULL);
+
+  res = GES_TEXT_OVERLAY (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index 572ee7c..559c53d 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TEXT_OVERLAY
-#define _GES_TEXT_OVERLAY
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 
 G_BEGIN_DECLS
 #define GES_TYPE_TEXT_OVERLAY ges_text_overlay_get_type()
-#define GES_TEXT_OVERLAY(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TEXT_OVERLAY, GESTextOverlay))
-#define GES_TEXT_OVERLAY_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TEXT_OVERLAY, GESTextOverlayClass))
-#define GES_IS_TEXT_OVERLAY(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TEXT_OVERLAY))
-#define GES_IS_TEXT_OVERLAY_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TEXT_OVERLAY))
-#define GES_TEXT_OVERLAY_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TEXT_OVERLAY, GESTextOverlayClass))
-typedef struct _GESTextOverlayPrivate GESTextOverlayPrivate;
+GES_DECLARE_TYPE(TextOverlay, text_overlay, TEXT_OVERLAY);
 
 /**
  * GESTextOverlay:
@@ -65,9 +54,6 @@ struct _GESTextOverlayClass
 };
 
 GES_API
-GType ges_text_overlay_get_type (void);
-
-GES_API
 void ges_text_overlay_set_text (GESTextOverlay * self,
     const gchar * text);
 GES_API
@@ -91,6 +77,9 @@ GES_API
 void ges_text_overlay_set_ypos (GESTextOverlay * self,
     gdouble position);
 
+GES_DEPRECATED
+GESTextOverlay *ges_text_overlay_new (void);
+
 GES_API
 const gchar *ges_text_overlay_get_text (GESTextOverlay * self);
 GES_API
@@ -108,8 +97,4 @@ const gdouble ges_text_overlay_get_xpos (GESTextOverlay * self);
 GES_API
 const gdouble ges_text_overlay_get_ypos (GESTextOverlay * self);
 
-GES_API
-GESTextOverlay *ges_text_overlay_new (void);
-
 G_END_DECLS
-#endif /* _GES_TEXT_OVERLAY */
diff --git a/ges/ges-time-overlay-clip.c b/ges/ges-time-overlay-clip.c
new file mode 100644 (file)
index 0000000..aad769c
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * SECTION:gestimeoverlayclip
+ * @title: GESTimeOverlayClip
+ * @short_description: Source with a time overlay on top
+ * @symbols:
+ *   - ges_source_clip_new_time_overlay
+ *
+ * A #GESSourceClip that overlays timing information on top.
+ *
+ * ## Asset
+ *
+ * The default asset ID is "time-overlay" (of type #GES_TYPE_SOURCE_CLIP),
+ * but the framerate and video size can be overridden using an ID of the form:
+ *
+ * ```
+ * time-overlay, framerate=60/1, width=1920, height=1080, max-duration=5.0
+ * ```
+ *
+ * ## Children properties
+ *
+ * {{ libs/GESTimeOverlayClip-children-props.md }}
+ *
+ * ## Symbols
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ges-asset.h"
+#include "ges-time-overlay-clip.h"
+
+
+/**
+ * ges_source_clip_new_time_overlay:
+ *
+ * Creates a new #GESSourceClip that renders a time overlay on top
+ *
+ * Returns: (transfer floating) (nullable): The newly created #GESSourceClip,
+ * or %NULL if there was an error.
+ * Since: 1.18
+ */
+GESSourceClip *
+ges_source_clip_new_time_overlay (void)
+{
+  GESSourceClip *new_clip;
+  GESAsset *asset = ges_asset_request (GES_TYPE_SOURCE_CLIP,
+      "time-overlay", NULL);
+
+  new_clip = GES_SOURCE_CLIP (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return new_clip;
+}
diff --git a/ges/ges-time-overlay-clip.h b/ges/ges-time-overlay-clip.h
new file mode 100644 (file)
index 0000000..7f0de14
--- /dev/null
@@ -0,0 +1,30 @@
+/* GStreamer Editing Services
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include "ges-source-clip.h"
+
+G_BEGIN_DECLS
+
+GES_API
+GESSourceClip* ges_source_clip_new_time_overlay (void);
+
+G_END_DECLS
\ No newline at end of file
index 5483722..9341d85 100644 (file)
 /**
  * SECTION:gestimelineelement
  * @title: GESTimelineElement
- * @short_description: Base Class for all elements that will be in a way or
- * another inside a GESTimeline.
- *
- * The GESTimelineElement base class implements the notion of timing as well
- * as priority. A GESTimelineElement can have a parent object which will be
- * responsible for controlling its timing properties.
+ * @short_description: Base Class for all elements with some temporal extent
+ * within a #GESTimeline.
+ *
+ * A #GESTimelineElement will have some temporal extent in its
+ * corresponding #GESTimelineElement:timeline, controlled by its
+ * #GESTimelineElement:start and #GESTimelineElement:duration. This
+ * determines when its content will be displayed, or its effect applied,
+ * in the timeline. Several objects may overlap within a given
+ * #GESTimeline, in which case their #GESTimelineElement:priority is used
+ * to determine their ordering in the timeline. Priority is mostly handled
+ * internally by #GESLayer-s and #GESClip-s.
+ *
+ * A timeline element can have a #GESTimelineElement:parent,
+ * such as a #GESClip, which is responsible for controlling its timing.
+ *
+ * ## Editing
+ *
+ * Elements can be moved around in their #GESTimelineElement:timeline by
+ * setting their #GESTimelineElement:start and
+ * #GESTimelineElement:duration using ges_timeline_element_set_start()
+ * and ges_timeline_element_set_duration(). Additionally, which parts of
+ * the underlying content are played in the timeline can be adjusted by
+ * setting the #GESTimelineElement:in-point using
+ * ges_timeline_element_set_inpoint(). The library also provides
+ * ges_timeline_element_edit(), with various #GESEditMode-s, which can
+ * adjust these properties in a convenient way, as well as introduce
+ * similar changes in neighbouring or later elements in the timeline.
+ *
+ * However, a timeline may refuse a change in these properties if they
+ * would place the timeline in an unsupported configuration. See
+ * #GESTimeline for its overlap rules.
+ *
+ * Additionally, an edit may be refused if it would place one of the
+ * timing properties out of bounds (such as a negative time value for
+ * #GESTimelineElement:start, or having insufficient internal
+ * content to last for the desired #GESTimelineElement:duration).
+ *
+ * ## Time Coordinates
+ *
+ * There are three main sets of time coordinates to consider when using
+ * timeline elements:
+ *
+ * + Timeline coordinates: these are the time coordinates used in the
+ *   output of the timeline in its #GESTrack-s. Each track share the same
+ *   coordinates, so there is only one set of coordinates for the
+ *   timeline. These extend indefinitely from 0. The times used for
+ *   editing (including setting #GESTimelineElement:start and
+ *   #GESTimelineElement:duration) use these coordinates, since these
+ *   define when an element is present and for how long the element lasts
+ *   for in the timeline.
+ * + Internal source coordinates: these are the time coordinates used
+ *   internally at the element's output. This is only really defined for
+ *   #GESTrackElement-s, where it refers to time coordinates used at the
+ *   final source pad of the wrapped #GstElement-s. However, these
+ *   coordinates may also be used in a #GESClip in reference to its
+ *   children. In particular, these are the coordinates used for
+ *   #GESTimelineElement:in-point and #GESTimelineElement:max-duration.
+ * + Internal sink coordinates: these are the time coordinates used
+ *   internally at the element's input. A #GESSource has no input, so
+ *   these would be undefined. Otherwise, for most #GESTrackElement-s
+ *   these will be the same set of coordinates as the internal source
+ *   coordinates because the element does not change the timing
+ *   internally. Only #GESBaseEffect can support elements where these
+ *   are different. See #GESBaseEffect for more information.
+ *
+ * You can determine the timeline time for a given internal source time
+ * in a #GESTrack in a #GESClip using
+ * ges_clip_get_timeline_time_from_internal_time(), and vice versa using
+ * ges_clip_get_internal_time_from_timeline_time(), for the purposes of
+ * editing and setting timings properties.
+ *
+ * ## Children Properties
+ *
+ * If a timeline element owns another #GstObject and wishes to expose
+ * some of its properties, it can do so by registering the property as one
+ * of the timeline element's children properties using
+ * ges_timeline_element_add_child_property(). The registered property of
+ * the child can then be read and set using the
+ * ges_timeline_element_get_child_property() and
+ * ges_timeline_element_set_child_property() methods, respectively. Some
+ * sub-classed objects will be created with pre-registered children
+ * properties; for example, to expose part of an underlying #GstElement
+ * that is used internally. The registered properties can be listed with
+ * ges_timeline_element_list_children_properties().
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -73,6 +151,8 @@ enum
 enum
 {
   DEEP_NOTIFY,
+  CHILD_PROPERTY_ADDED,
+  CHILD_PROPERTY_REMOVED,
   LAST_SIGNAL
 };
 
@@ -83,7 +163,9 @@ static GParamSpec *properties[PROP_LAST] = { NULL, };
 typedef struct
 {
   GObject *child;
+  GESTimelineElement *owner;
   gulong handler_id;
+  GESTimelineElement *self;
 } ChildPropHandler;
 
 struct _GESTimelineElementPrivate
@@ -112,11 +194,27 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTimelineElement, ges_timeline_element,
     G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init)
     G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
 
+/*********************************************
+ *      Virtual methods implementation       *
+ *********************************************/
 static void
 _set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child,
     GParamSpec * pspec, GValue * value)
 {
-  g_object_set_property (child, pspec->name, value);
+  if (G_VALUE_TYPE (value) != pspec->value_type
+      && G_VALUE_TYPE (value) == G_TYPE_STRING)
+    gst_util_set_object_arg (child, pspec->name, g_value_get_string (value));
+  else
+    g_object_set_property (child, pspec->name, value);
+}
+
+static gboolean
+_set_child_property_full (GESTimelineElement * self, GObject * child,
+    GParamSpec * pspec, const GValue * value, GError ** error)
+{
+  GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_child_property (self, child,
+      pspec, (GValue *) value);
+  return TRUE;
 }
 
 static gboolean
@@ -164,8 +262,8 @@ _lookup_child (GESTimelineElement * self, const gchar * prop_name,
   return res;
 }
 
-static GParamSpec **
-default_list_children_properties (GESTimelineElement * self,
+GParamSpec **
+ges_timeline_element_get_children_properties (GESTimelineElement * self,
     guint * n_properties)
 {
   GParamSpec **pspec, *spec;
@@ -297,10 +395,22 @@ _child_prop_handler_free (ChildPropHandler * handler)
   if (handler->handler_id)
     g_signal_handler_disconnect (handler->child, handler->handler_id);
   g_object_thaw_notify (handler->child);
-  gst_object_unref (handler->child);
+
+  if (handler->child != (GObject *) handler->self &&
+      handler->child != (GObject *) handler->owner)
+    gst_object_unref (handler->child);
   g_slice_free (ChildPropHandler, handler);
 }
 
+static gboolean
+_get_natural_framerate (GESTimelineElement * self, gint * framerate_n,
+    gint * framerate_d)
+{
+  GST_INFO_OBJECT (self, "No natural framerate");
+
+  return FALSE;
+}
+
 static void
 ges_timeline_element_init (GESTimelineElement * self)
 {
@@ -325,7 +435,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
   /**
    * GESTimelineElement:parent:
    *
-   * The parent container of the object
+   * The parent container of the element.
    */
   properties[PROP_PARENT] =
       g_param_spec_object ("parent", "Parent",
@@ -335,59 +445,92 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
   /**
    * GESTimelineElement:timeline:
    *
-   * The timeline in which @element is
+   * The timeline that the element lies within.
    */
   properties[PROP_TIMELINE] =
       g_param_spec_object ("timeline", "Timeline",
-      "The timeline the object is in", GES_TYPE_TIMELINE, G_PARAM_READWRITE);
+      "The timeline the object is in", GES_TYPE_TIMELINE,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GESTimelineElement:start:
    *
-   * The position of the object in its container (in nanoseconds).
+   * The starting position of the element in the timeline (in nanoseconds
+   * and in the time coordinates of the timeline). For example, for a
+   * source element, this would determine the time at which it should
+   * start outputting its internal content. For an operation element, this
+   * would determine the time at which it should start applying its effect
+   * to any source content.
    */
   properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
-      "The position in the container", 0, G_MAXUINT64, 0, G_PARAM_READWRITE);
+      "The position in the timeline", 0, G_MAXUINT64, 0,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GESTimelineElement:in-point:
    *
-   * The in-point at which this #GESTimelineElement will start outputting data
-   * from its contents (in nanoseconds).
+   * The initial offset to use internally when outputting content (in
+   * nanoseconds, but in the time coordinates of the internal content).
+   *
+   * For example, for a #GESVideoUriSource that references some media
+   * file, the "internal content" is the media file data, and the
+   * in-point would correspond to some timestamp in the media file.
+   * When playing the timeline, and when the element is first reached at
+   * timeline-time #GESTimelineElement:start, it will begin outputting the
+   * data from the timestamp in-point **onwards**, until it reaches the
+   * end of its #GESTimelineElement:duration in the timeline.
    *
-   * Ex : an in-point of 5 seconds means that the first outputted buffer will
-   * be the one located 5 seconds in the controlled resource.
+   * For elements that have no internal content, this should be kept
+   * as 0.
    */
   properties[PROP_INPOINT] =
       g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0,
-      G_MAXUINT64, 0, G_PARAM_READWRITE);
+      G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GESTimelineElement:duration:
    *
-   * The duration (in nanoseconds) which will be used in the container
+   * The duration that the element is in effect for in the timeline (a
+   * time difference in nanoseconds using the time coordinates of the
+   * timeline). For example, for a source element, this would determine
+   * for how long it should output its internal content for. For an
+   * operation element, this would determine for how long its effect
+   * should be applied to any source content.
    */
   properties[PROP_DURATION] =
-      g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
-      G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE);
+      g_param_spec_uint64 ("duration", "Duration", "The play duration", 0,
+      G_MAXUINT64, GST_CLOCK_TIME_NONE,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GESTimelineElement:max-duration:
    *
-   * The maximum duration (in nanoseconds) of the #GESTimelineElement.
+   * The full duration of internal content that is available (a time
+   * difference in nanoseconds using the time coordinates of the internal
+   * content).
+   *
+   * This will act as a cap on the #GESTimelineElement:in-point of the
+   * element (which is in the same time coordinates), and will sometimes
+   * be used to limit the #GESTimelineElement:duration of the element in
+   * the timeline.
+   *
+   * For example, for a #GESVideoUriSource that references some media
+   * file, this would be the length of the media file.
+   *
+   * For elements that have no internal content, or whose content is
+   * indefinite, this should be kept as #GST_CLOCK_TIME_NONE.
    */
   properties[PROP_MAX_DURATION] =
       g_param_spec_uint64 ("max-duration", "Maximum duration",
       "The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE,
-      G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GESTimelineElement:priority:
    *
-   * The priority of the object.
+   * The priority of the element.
    *
-   * Setting GESTimelineElement priorities is deprecated
-   * as all priority management is done by GES itself now.
+   * Deprecated: 1.10: Priority management is now done by GES itself.
    */
   properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
       "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE);
@@ -395,7 +538,7 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
   /**
    * GESTimelineElement:name:
    *
-   * The name of the object
+   * The name of the element. This should be unique within its timeline.
    */
   properties[PROP_NAME] =
       g_param_spec_string ("name", "Name", "The name of the timeline object",
@@ -414,19 +557,58 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
 
   /**
    * GESTimelineElement::deep-notify:
-   * @timeline_element: a #GESTtimelineElement
-   * @prop_object: the object that originated the signal
-   * @prop: the property that changed
+   * @timeline_element: A #GESTtimelineElement
+   * @prop_object: The child whose property has been set
+   * @prop: The specification for the property that been set
    *
-   * The deep notify signal is used to be notified of property changes of all
-   * the childs of @timeline_element
+   * Emitted when a child of the element has one of its registered
+   * properties set. See ges_timeline_element_add_child_property().
+   * Note that unlike #GObject::notify, a child property name can not be
+   * used as a signal detail.
    */
   ges_timeline_element_signals[DEEP_NOTIFY] =
       g_signal_new ("deep-notify", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED |
-      G_SIGNAL_NO_HOOKS, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM);
 
+  /**
+   * GESTimelineElement::child-property-added:
+   * @timeline_element: A #GESTtimelineElement
+   * @prop_object: The child whose property has been registered
+   * @prop: The specification for the property that has been registered
+   *
+   * Emitted when the element has a new child property registered. See
+   * ges_timeline_element_add_child_property().
+   *
+   * Note that some GES elements will be automatically created with
+   * pre-registered children properties. You can use
+   * ges_timeline_element_list_children_properties() to list these.
+   *
+   * Since: 1.18
+   */
+  ges_timeline_element_signals[CHILD_PROPERTY_ADDED] =
+      g_signal_new ("child-property-added", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
+      G_TYPE_OBJECT, G_TYPE_PARAM);
+
+  /**
+   * GESTimelineElement::child-property-removed:
+   * @timeline_element: A #GESTimelineElement
+   * @prop_object: The child whose property has been unregistered
+   * @prop: The specification for the property that has been unregistered
+   *
+   * Emitted when the element has a child property unregistered. See
+   * ges_timeline_element_remove_child_property().
+   *
+   * Since: 1.18
+   */
+  ges_timeline_element_signals[CHILD_PROPERTY_REMOVED] =
+      g_signal_new ("child-property-removed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
+      G_TYPE_OBJECT, G_TYPE_PARAM);
+
+
   object_class->dispose = ges_timeline_element_dispose;
   object_class->finalize = ges_timeline_element_finalize;
 
@@ -443,9 +625,12 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
   klass->roll_end = NULL;
   klass->trim = NULL;
 
-  klass->list_children_properties = default_list_children_properties;
+  klass->list_children_properties =
+      ges_timeline_element_get_children_properties;
   klass->lookup_child = _lookup_child;
   klass->set_child_property = _set_child_property;
+  klass->set_child_property_full = _set_child_property_full;
+  klass->get_natural_framerate = _get_natural_framerate;
 }
 
 static void
@@ -488,6 +673,13 @@ _set_name (GESTimelineElement * self, const gchar * wanted_name)
     /* If the wanted name uses the same 'namespace' as default, make
      * sure it does not badly interfere with our counting system */
 
+    /* FIXME: should we really be allowing a user to set the name
+     * "uriclip1" for, say, a GESTransition? The below code *does not*
+     * capture this case (because the prefix does not match "transition").
+     * If the user subsequently calls _set_name with name == NULL, on a
+     * GESClip *for the first time*, then the GES library will
+     * automatically choose the *same* name "uriclip1", but this is not
+     * unique! */
     if (g_str_has_prefix (wanted_name, lowcase_type)) {
       guint64 tmpcount =
           g_ascii_strtoull (&wanted_name[strlen (lowcase_type)], NULL, 10);
@@ -497,6 +689,11 @@ _set_name (GESTimelineElement * self, const gchar * wanted_name)
         GST_DEBUG_OBJECT (self, "Using same naming %s but updated count to %i",
             wanted_name, count);
       } else if (tmpcount < count) {
+        /* FIXME: this can unexpectedly change names given by the user
+         * E.g. if "transition2" already exists, and a user then wants to
+         * set a GESTransition to have the name "transition-custom" or
+         * "transition 1 too many" then tmpcount would in fact be 0 or 1,
+         * and the name would then be changed to "transition3"! */
         name = g_strdup_printf ("%s%d", lowcase_type, count);
         count++;
         GST_DEBUG_OBJECT (self, "Name %s already allocated, giving: %s instead"
@@ -519,19 +716,189 @@ _set_name (GESTimelineElement * self, const gchar * wanted_name)
 }
 
 /*********************************************
+ *       Internal and private helpers        *
+ *********************************************/
+
+GESTimelineElement *
+ges_timeline_element_peak_toplevel (GESTimelineElement * self)
+{
+  GESTimelineElement *toplevel = self;
+
+  while (toplevel->parent)
+    toplevel = toplevel->parent;
+
+  return toplevel;
+}
+
+GESTimelineElement *
+ges_timeline_element_get_copied_from (GESTimelineElement * self)
+{
+  GESTimelineElement *copied_from = self->priv->copied_from;
+  self->priv->copied_from = NULL;
+  return copied_from;
+}
+
+GESTimelineElementFlags
+ges_timeline_element_flags (GESTimelineElement * self)
+{
+  return self->priv->flags;
+}
+
+void
+ges_timeline_element_set_flags (GESTimelineElement * self,
+    GESTimelineElementFlags flags)
+{
+  self->priv->flags = flags;
+
+}
+
+static gboolean
+emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data)
+{
+  g_signal_emit (data->self, ges_timeline_element_signals[DEEP_NOTIFY], 0,
+      data->child, data->arg);
+
+  gst_object_unref (data->child);
+  g_param_spec_unref (data->arg);
+  gst_object_unref (data->self);
+  g_slice_free (EmitDeepNotifyInIdleData, data);
+
+  return FALSE;
+}
+
+static void
+child_prop_changed_cb (GObject * child, GParamSpec * arg,
+    GESTimelineElement * self)
+{
+  EmitDeepNotifyInIdleData *data;
+
+  /* Emit "deep-notify" right away if in main thread */
+  if (g_main_context_acquire (g_main_context_default ())) {
+    g_main_context_release (g_main_context_default ());
+    g_signal_emit (self, ges_timeline_element_signals[DEEP_NOTIFY], 0,
+        child, arg);
+    return;
+  }
+
+  data = g_slice_new (EmitDeepNotifyInIdleData);
+
+  data->child = gst_object_ref (child);
+  data->arg = g_param_spec_ref (arg);
+  data->self = gst_object_ref (self);
+
+  ges_idle_add ((GSourceFunc) emit_deep_notify_in_idle, data, NULL);
+}
+
+static gboolean
+set_child_property_by_pspec (GESTimelineElement * self,
+    GParamSpec * pspec, const GValue * value, GError ** error)
+{
+  GESTimelineElementClass *klass;
+  GESTimelineElement *setter = self;
+  ChildPropHandler *handler =
+      g_hash_table_lookup (self->priv->children_props, pspec);
+
+  if (!handler) {
+    GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name);
+    return FALSE;
+  }
+
+  if (handler->owner) {
+    klass = GES_TIMELINE_ELEMENT_GET_CLASS (handler->owner);
+    setter = handler->owner;
+  } else {
+    klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
+  }
+
+  if (klass->set_child_property_full)
+    return klass->set_child_property_full (setter, handler->child, pspec,
+        value, error);
+
+  g_assert (klass->set_child_property);
+  klass->set_child_property (setter, handler->child, pspec, (GValue *) value);
+
+  return TRUE;
+}
+
+gboolean
+ges_timeline_element_add_child_property_full (GESTimelineElement * self,
+    GESTimelineElement * owner, GParamSpec * pspec, GObject * child)
+{
+  gchar *signame;
+  ChildPropHandler *handler;
+
+  /* FIXME: allow the same pspec, provided the child is different. This
+   * is important for containers that may have duplicate children
+   * If this is changed, _remove_childs_child_property in ges-container.c
+   * should be changed to reflect this.
+   * We could hack around this by copying the pspec into a new instance
+   * of GParamSpec, but there is no such GLib method, and it would break
+   * the usage of get_..._from_pspec and set_..._from_pspec */
+  if (g_hash_table_contains (self->priv->children_props, pspec)) {
+    GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name);
+    return FALSE;
+  }
+
+  GST_DEBUG_OBJECT (self, "Adding child property: %" GST_PTR_FORMAT "::%s",
+      child, pspec->name);
+
+  signame = g_strconcat ("notify::", pspec->name, NULL);
+  handler = (ChildPropHandler *) g_slice_new0 (ChildPropHandler);
+  handler->self = self;
+  if (child == G_OBJECT (self) || child == G_OBJECT (owner))
+    handler->child = child;
+  else
+    handler->child = gst_object_ref (child);
+  handler->owner = owner;
+  handler->handler_id =
+      g_signal_connect (child, signame, G_CALLBACK (child_prop_changed_cb),
+      self);
+  g_hash_table_insert (self->priv->children_props, g_param_spec_ref (pspec),
+      handler);
+
+  g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_ADDED], 0,
+      child, pspec);
+
+  g_free (signame);
+  return TRUE;
+}
+
+GObject *
+ges_timeline_element_get_child_from_child_property (GESTimelineElement * self,
+    GParamSpec * pspec)
+{
+  ChildPropHandler *handler =
+      g_hash_table_lookup (self->priv->children_props, pspec);
+  if (handler)
+    return handler->child;
+  return NULL;
+}
+
+
+/*********************************************
  *            API implementation             *
  *********************************************/
 
 /**
  * ges_timeline_element_set_parent:
- * @self: a #GESTimelineElement
- * @parent: new parent of self
+ * @self: A #GESTimelineElement
+ * @parent (nullable): New parent of @self
+ *
+ * Sets the #GESTimelineElement:parent for the element.
+ *
+ * This is used internally and you should normally not call this. A
+ * #GESContainer will set the #GESTimelineElement:parent of its children
+ * in ges_container_add() and ges_container_remove().
+ *
+ * Note, if @parent is not %NULL, @self must not already have a parent
+ * set. Therefore, if you wish to switch parents, you will need to call
+ * this function twice: first to set the parent to %NULL, and then to the
+ * new parent.
  *
- * Sets the parent of @self to @parent. The parents needs to already
- * own a hard reference on @self.
+ * If @parent is not %NULL, you must ensure it already has a
+ * (non-floating) reference to @self before calling this.
  *
- * Returns: %TRUE if @parent could be set or %FALSE when @self
- * already had a parent or @self and @parent are the same.
+ * Returns: %TRUE if @parent could be set for @self.
  */
 gboolean
 ges_timeline_element_set_parent (GESTimelineElement * self,
@@ -544,6 +911,8 @@ ges_timeline_element_set_parent (GESTimelineElement * self,
   if (self == parent) {
     GST_INFO_OBJECT (self, "Trying to add %p in itself, not a good idea!",
         self);
+    /* FIXME: why are we sinking and then unreffing self when we do not
+     * own it? */
     gst_object_ref_sink (self);
     gst_object_unref (self);
     return FALSE;
@@ -568,6 +937,8 @@ ges_timeline_element_set_parent (GESTimelineElement * self,
 had_parent:
   {
     GST_WARNING_OBJECT (self, "set parent failed, object already had a parent");
+    /* FIXME: why are we sinking and then unreffing self when we do not
+     * own it? */
     gst_object_ref_sink (self);
     gst_object_unref (self);
     return FALSE;
@@ -576,13 +947,12 @@ had_parent:
 
 /**
  * ges_timeline_element_get_parent:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
  *
- * Returns the parent of @self. This function increases the refcount
- * of the parent object so you should gst_object_unref() it after usage.
+ * Gets the #GESTimelineElement:parent for the element.
  *
- * Returns: (transfer full) (nullable): parent of @self, this can be %NULL if
- * @self has no parent. unref after usage.
+ * Returns: (transfer full) (nullable): The parent of @self, or %NULL if
+ * @self has no parent.
  */
 GESTimelineElement *
 ges_timeline_element_get_parent (GESTimelineElement * self)
@@ -600,13 +970,25 @@ ges_timeline_element_get_parent (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_set_timeline:
- * @self: a #GESTimelineElement
- * @timeline: The #GESTimeline @self is in
+ * @self: A #GESTimelineElement
+ * @timeline (nullable): The #GESTimeline @self should be in
+ *
+ * Sets the #GESTimelineElement:timeline of the element.
+ *
+ * This is used internally and you should normally not call this. A
+ * #GESClip will have its #GESTimelineElement:timeline set through its
+ * #GESLayer. A #GESTrack will similarly take care of setting the
+ * #GESTimelineElement:timeline of its #GESTrackElement-s. A #GESGroup
+ * will adopt the same #GESTimelineElement:timeline as its children.
  *
- * Sets the timeline of @self to @timeline.
+ * If @timeline is %NULL, this will stop its current
+ * #GESTimelineElement:timeline from tracking it, otherwise @timeline will
+ * start tracking @self. Note, in the latter case, @self must not already
+ * have a timeline set. Therefore, if you wish to switch timelines, you
+ * will need to call this function twice: first to set the timeline to
+ * %NULL, and then to the new timeline.
  *
- * Returns: %TRUE if @timeline could be set or %FALSE when @timeline
- * already had a timeline.
+ * Returns: %TRUE if @timeline could be set for @self.
  */
 gboolean
 ges_timeline_element_set_timeline (GESTimelineElement * self,
@@ -617,6 +999,9 @@ ges_timeline_element_set_timeline (GESTimelineElement * self,
 
   GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline);
 
+  if (self->timeline == timeline)
+    return TRUE;
+
   if (timeline != NULL && G_UNLIKELY (self->timeline != NULL))
     goto had_timeline;
 
@@ -652,13 +1037,12 @@ had_timeline:
 
 /**
  * ges_timeline_element_get_timeline:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
  *
- * Returns the timeline of @self. This function increases the refcount
- * of the timeline so you should gst_object_unref() it after usage.
+ * Gets the #GESTimelineElement:timeline for the element.
  *
- * Returns: (transfer full) (nullable): timeline of @self, this can be %NULL if
- * @self has no timeline. unref after usage.
+ * Returns: (transfer full) (nullable): The timeline of @self, or %NULL
+ * if @self has no timeline.
  */
 GESTimeline *
 ges_timeline_element_get_timeline (GESTimelineElement * self)
@@ -676,15 +1060,20 @@ ges_timeline_element_get_timeline (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_set_start:
- * @self: a #GESTimelineElement
- * @start: the position in #GstClockTime
+ * @self: A #GESTimelineElement
+ * @start: The desired start position of the element in its timeline
  *
- * Set the position of the object in its containing layer.
+ * Sets #GESTimelineElement:start for the element. If the element has a
+ * parent, this will also move its siblings with the same shift.
  *
- * Note that if the snapping-distance property of the timeline containing
- * @self is set, @self will properly snap to the edges around @start.
+ * Whilst the element is part of a #GESTimeline, this is the same as
+ * editing the element with ges_timeline_element_edit() under
+ * #GES_EDIT_MODE_NORMAL with #GES_EDGE_NONE. In particular, the
+ * #GESTimelineElement:start of the element may be snapped to a different
+ * timeline time from the one given. In addition, setting may fail if it
+ * would place the timeline in an unsupported configuration.
  *
- * Returns: %TRUE if @start could be set.
+ * Returns: %TRUE if @start could be set for @self.
  */
 gboolean
 ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
@@ -693,21 +1082,26 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
   GESTimelineElement *toplevel_container, *parent;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);
 
   if (self->start == start)
     return TRUE;
 
-  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
-
   GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT
       " new start: %" GST_TIME_FORMAT,
       GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)), GST_TIME_ARGS (start));
 
-  toplevel_container = ges_timeline_element_get_toplevel_parent (self);
+  if (self->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (self))
+    return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_NORMAL,
+        GES_EDGE_NONE, start);
+
+  toplevel_container = ges_timeline_element_peak_toplevel (self);
   parent = self->parent;
 
   /* FIXME This should not belong to GESTimelineElement */
-  if (toplevel_container &&
+  /* only check if no timeline, otherwise the timeline-tree will handle this
+   * check */
+  if (!self->timeline && toplevel_container &&
       ((gint64) (_START (toplevel_container) + start - _START (self))) < 0 &&
       parent
       && GES_CONTAINER (parent)->children_control_mode == GES_CHILDREN_UPDATE) {
@@ -715,21 +1109,23 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
         "Can not move the object as it would imply its "
         "container to have a negative start value");
 
-    gst_object_unref (toplevel_container);
     return FALSE;
   }
 
-  gst_object_unref (toplevel_container);
+  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
   if (klass->set_start) {
-    gboolean res = klass->set_start (self, start);
-    if (res) {
+    gint res = klass->set_start (self, start);
+    if (res == FALSE)
+      return FALSE;
+    if (res == TRUE) {
       self->start = start;
       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]);
     }
 
     GST_DEBUG_OBJECT (self, "New start: %" GST_TIME_FORMAT,
         GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)));
-    return res;
+
+    return TRUE;
   }
 
   GST_WARNING_OBJECT (self, "No set_start virtual method implementation"
@@ -740,13 +1136,14 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
 
 /**
  * ges_timeline_element_set_inpoint:
- * @self: a #GESTimelineElement
- * @inpoint: the in-point in #GstClockTime
+ * @self: A #GESTimelineElement
+ * @inpoint: The in-point, in internal time coordinates
  *
- * Set the in-point, that is the moment at which the @self will start
- * outputting data from its contents.
+ * Sets #GESTimelineElement:in-point for the element. If the new in-point
+ * is above the current #GESTimelineElement:max-duration of the element,
+ * this method will fail.
  *
- * Returns: %TRUE if @inpoint could be set.
+ * Returns: %TRUE if @inpoint could be set for @self.
  */
 gboolean
 ges_timeline_element_set_inpoint (GESTimelineElement * self,
@@ -757,19 +1154,33 @@ ges_timeline_element_set_inpoint (GESTimelineElement * self,
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
 
   GST_DEBUG_OBJECT (self, "current inpoint: %" GST_TIME_FORMAT
-      " new inpoint: %" GST_TIME_FORMAT, GST_TIME_ARGS (inpoint),
-      GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self)));
+      " new inpoint: %" GST_TIME_FORMAT, GST_TIME_ARGS (self->inpoint),
+      GST_TIME_ARGS (inpoint));
+
+  if (G_UNLIKELY (inpoint == self->inpoint))
+    return TRUE;
+
+  if (GES_CLOCK_TIME_IS_LESS (self->maxduration, inpoint)) {
+    GST_WARNING_OBJECT (self, "Can not set an in-point of %" GST_TIME_FORMAT
+        " because it exceeds the element's max-duration: %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (inpoint), GST_TIME_ARGS (self->maxduration));
+    return FALSE;
+  }
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   if (klass->set_inpoint) {
-    gboolean res = klass->set_inpoint (self, inpoint);
-    if (res) {
-      self->inpoint = inpoint;
-      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPOINT]);
-    }
+    /* FIXME: Could we instead use g_object_freeze_notify() to prevent
+     * duplicate notify signals? Rather than relying on the return value
+     * being -1 for setting that succeeds but does not want a notify
+     * signal because it will call this method on itself a second time. */
+    if (!klass->set_inpoint (self, inpoint))
+      return FALSE;
 
-    return res;
+    self->inpoint = inpoint;
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPOINT]);
+
+    return TRUE;
   }
 
   GST_DEBUG_OBJECT (self, "No set_inpoint virtual method implementation"
@@ -781,12 +1192,14 @@ ges_timeline_element_set_inpoint (GESTimelineElement * self,
 
 /**
  * ges_timeline_element_set_max_duration:
- * @self: a #GESTimelineElement
- * @maxduration: the maximum duration in #GstClockTime
+ * @self: A #GESTimelineElement
+ * @maxduration: The maximum duration, in internal time coordinates
  *
- * Set the maximun duration of the object
+ * Sets #GESTimelineElement:max-duration for the element. If the new
+ * maximum duration is below the current #GESTimelineElement:in-point of
+ * the element, this method will fail.
  *
- * Returns: %TRUE if @maxduration could be set.
+ * Returns: %TRUE if @maxduration could be set for @self.
  */
 gboolean
 ges_timeline_element_set_max_duration (GESTimelineElement * self,
@@ -796,34 +1209,56 @@ ges_timeline_element_set_max_duration (GESTimelineElement * self,
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
 
-  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
+  GST_DEBUG_OBJECT (self, "current max-duration: %" GST_TIME_FORMAT
+      " new max-duration: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (self->maxduration), GST_TIME_ARGS (maxduration));
 
-  GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT
-      " new duration: %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (GES_TIMELINE_ELEMENT_MAX_DURATION (self)),
-      GST_TIME_ARGS (maxduration));
+  if (G_UNLIKELY (maxduration == self->maxduration))
+    return TRUE;
+
+  if (GES_CLOCK_TIME_IS_LESS (maxduration, self->inpoint)) {
+    GST_WARNING_OBJECT (self, "Can not set a max-duration of %"
+        GST_TIME_FORMAT " because it lies below the element's in-point: %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (maxduration),
+        GST_TIME_ARGS (self->inpoint));
+    return FALSE;
+  }
+
+  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   if (klass->set_max_duration) {
-    if (klass->set_max_duration (self, maxduration) == FALSE)
+    if (!klass->set_max_duration (self, maxduration))
       return FALSE;
+    self->maxduration = maxduration;
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_DURATION]);
+
+    return TRUE;
   }
 
-  self->maxduration = maxduration;
-  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_DURATION]);
-  return TRUE;
+  GST_DEBUG_OBJECT (self, "No set_max_duration virtual method implementation"
+      " on class %s. Can not set max-duration  %" GST_TIME_FORMAT,
+      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (maxduration));
+
+  return FALSE;
 }
 
 /**
  * ges_timeline_element_set_duration:
- * @self: a #GESTimelineElement
- * @duration: the duration in #GstClockTime
+ * @self: A #GESTimelineElement
+ * @duration: The desired duration in its timeline
  *
- * Set the duration of the object
+ * Sets #GESTimelineElement:duration for the element.
  *
- * Note that if the timeline snap-distance property of the timeline containing
- * @self is set, @self will properly snap to its neighboors.
+ * Whilst the element is part of a #GESTimeline, this is the same as
+ * editing the element with ges_timeline_element_edit() under
+ * #GES_EDIT_MODE_TRIM with #GES_EDGE_END. In particular, the
+ * #GESTimelineElement:duration of the element may be snapped to a
+ * different timeline time difference from the one given. In addition,
+ * setting may fail if it would place the timeline in an unsupported
+ * configuration, or the element does not have enough internal content to
+ * last the desired duration.
  *
- * Returns: %TRUE if @duration could be set.
+ * Returns: %TRUE if @duration could be set for @self.
  */
 gboolean
 ges_timeline_element_set_duration (GESTimelineElement * self,
@@ -833,21 +1268,29 @@ ges_timeline_element_set_duration (GESTimelineElement * self,
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
 
-  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
+  if (duration == self->duration)
+    return TRUE;
+
+  if (self->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (self))
+    return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM,
+        GES_EDGE_END, self->start + duration);
 
   GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT
       " new duration: %" GST_TIME_FORMAT,
       GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)),
       GST_TIME_ARGS (duration));
 
+  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
   if (klass->set_duration) {
-    gboolean res = klass->set_duration (self, duration);
-    if (res) {
+    gint res = klass->set_duration (self, duration);
+    if (res == FALSE)
+      return FALSE;
+    if (res == TRUE) {
       self->duration = duration;
       g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
     }
 
-    return res;
+    return TRUE;
   }
 
   GST_WARNING_OBJECT (self, "No set_duration virtual method implementation"
@@ -858,9 +1301,11 @@ ges_timeline_element_set_duration (GESTimelineElement * self,
 
 /**
  * ges_timeline_element_get_start:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
  *
- * Returns: The @start of @self
+ * Gets the #GESTimelineElement:start for the element.
+ *
+ * Returns: The start of @self (in nanoseconds).
  */
 GstClockTime
 ges_timeline_element_get_start (GESTimelineElement * self)
@@ -872,9 +1317,11 @@ ges_timeline_element_get_start (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_get_inpoint:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
  *
- * Returns: The @inpoint of @self
+ * Gets the #GESTimelineElement:in-point for the element.
+ *
+ * Returns: The in-point of @self (in nanoseconds).
  */
 GstClockTime
 ges_timeline_element_get_inpoint (GESTimelineElement * self)
@@ -886,9 +1333,11 @@ ges_timeline_element_get_inpoint (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_get_duration:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
  *
- * Returns: The @duration of @self
+ * Gets the #GESTimelineElement:duration for the element.
+ *
+ * Returns: The duration of @self (in nanoseconds).
  */
 GstClockTime
 ges_timeline_element_get_duration (GESTimelineElement * self)
@@ -900,9 +1349,11 @@ ges_timeline_element_get_duration (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_get_max_duration:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
+ *
+ * Gets the #GESTimelineElement:max-duration for the element.
  *
- * Returns: The @maxduration of @self
+ * Returns: The max-duration of @self (in nanoseconds).
  */
 GstClockTime
 ges_timeline_element_get_max_duration (GESTimelineElement * self)
@@ -914,9 +1365,11 @@ ges_timeline_element_get_max_duration (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_get_priority:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
+ *
+ * Gets the #GESTimelineElement:priority for the element.
  *
- * Returns: The @priority of @self
+ * Returns: The priority of @self.
  */
 guint32
 ges_timeline_element_get_priority (GESTimelineElement * self)
@@ -928,16 +1381,16 @@ ges_timeline_element_get_priority (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_set_priority:
- * @self: a #GESTimelineElement
- * @priority: the priority
+ * @self: A #GESTimelineElement
+ * @priority: The priority
  *
- * Sets the priority of the object within the containing layer
+ * Sets the priority of the element within the containing layer.
  *
- * Deprecated: All priority management is done by GES itself now.
+ * Deprecated:1.10: All priority management is done by GES itself now.
  * To set #GESEffect priorities #ges_clip_set_top_effect_index should
  * be used.
  *
- * Returns: %TRUE if @priority could be set.
+ * Returns: %TRUE if @priority could be set for @self.
  */
 gboolean
 ges_timeline_element_set_priority (GESTimelineElement * self, guint32 priority)
@@ -969,15 +1422,15 @@ ges_timeline_element_set_priority (GESTimelineElement * self, guint32 priority)
 
 /**
  * ges_timeline_element_ripple:
- * @self: The #GESTimelineElement to ripple.
- * @start: The new start of @self in ripple mode.
+ * @self: The #GESTimelineElement to ripple
+ * @start: The new start time of @self in ripple mode
  *
- * Edits @self in ripple mode. It allows you to modify the
- * start of @self and move the following neighbours accordingly.
- * This will change the overall timeline duration.
+ * Edits the start time of an element within its timeline in ripple mode.
+ * See ges_timeline_element_edit() with #GES_EDIT_MODE_RIPPLE and
+ * #GES_EDGE_NONE.
  *
- * Returns: %TRUE if the self as been rippled properly, %FALSE if an error
- * occured
+ * Returns: %TRUE if the ripple edit of @self completed, %FALSE on
+ * failure.
  */
 gboolean
 ges_timeline_element_ripple (GESTimelineElement * self, GstClockTime start)
@@ -985,31 +1438,30 @@ ges_timeline_element_ripple (GESTimelineElement * self, GstClockTime start)
   GESTimelineElementClass *klass;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   if (klass->ripple)
     return klass->ripple (self, start);
 
-  GST_WARNING_OBJECT (self, "No ripple virtual method implementation"
-      " on class %s. Can not ripple to %" GST_TIME_FORMAT,
-      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start));
+  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_RIPPLE,
+      GES_EDGE_NONE, start);
 
   return FALSE;
 }
 
 /**
  * ges_timeline_element_ripple_end:
- * @self: The #GESTimelineElement to ripple.
- * @end: The new end (start + duration) of @self in ripple mode. It will
- *       basically only change the duration of @self.
+ * @self: The #GESTimelineElement to ripple
+ * @end: The new end time of @self in ripple mode
  *
- * Edits @self in ripple mode. It allows you to modify the
- * duration of a @self and move the following neighbours accordingly.
- * This will change the overall timeline duration.
+ * Edits the end time of an element within its timeline in ripple mode.
+ * See ges_timeline_element_edit() with #GES_EDIT_MODE_RIPPLE and
+ * #GES_EDGE_END.
  *
- * Returns: %TRUE if the self as been rippled properly, %FALSE if an error
- * occured
+ * Returns: %TRUE if the ripple edit of @self completed, %FALSE on
+ * failure.
  */
 gboolean
 ges_timeline_element_ripple_end (GESTimelineElement * self, GstClockTime end)
@@ -1017,33 +1469,27 @@ ges_timeline_element_ripple_end (GESTimelineElement * self, GstClockTime end)
   GESTimelineElementClass *klass;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (end), FALSE);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
-  if (klass->ripple_end) {
+  if (klass->ripple_end)
     return klass->ripple_end (self, end);
-  }
-
-  GST_WARNING_OBJECT (self, "No ripple virtual method implementation"
-      " on class %s. Can not ripple end to %" GST_TIME_FORMAT,
-      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (end));
 
-  return FALSE;
+  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_RIPPLE,
+      GES_EDGE_END, end);
 }
 
 /**
  * ges_timeline_element_roll_start:
  * @self: The #GESTimelineElement to roll
- * @start: The new start of @self in roll mode, it will also adapat
- * the in-point of @self according
+ * @start: The new start time of @self in roll mode
  *
- * Edits @self in roll mode. It allows you to modify the
- * start and inpoint of a @self and "resize" (basicly change the duration
- * in this case) of the previous neighbours accordingly.
- * This will not change the overall timeline duration.
+ * Edits the start time of an element within its timeline in roll mode.
+ * See ges_timeline_element_edit() with #GES_EDIT_MODE_ROLL and
+ * #GES_EDGE_START.
  *
- * Returns: %TRUE if the self as been roll properly, %FALSE if an error
- * occured
+ * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure.
  */
 gboolean
 ges_timeline_element_roll_start (GESTimelineElement * self, GstClockTime start)
@@ -1051,32 +1497,27 @@ ges_timeline_element_roll_start (GESTimelineElement * self, GstClockTime start)
   GESTimelineElementClass *klass;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
-  if (klass->roll_start) {
+  if (klass->roll_start)
     return klass->roll_start (self, start);
-  }
-
-  GST_WARNING_OBJECT (self, "No ripple virtual method implementation"
-      " on class %s. Can not roll to %" GST_TIME_FORMAT,
-      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start));
 
-  return FALSE;
+  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_ROLL,
+      GES_EDGE_START, start);
 }
 
 /**
  * ges_timeline_element_roll_end:
- * @self: The #GESTimelineElement to roll.
- * @end: The new end (start + duration) of @self in roll mode
+ * @self: The #GESTimelineElement to roll
+ * @end: The new end time of @self in roll mode
  *
- * Edits @self in roll mode. It allows you to modify the
- * duration of a @self and trim (basicly change the start + inpoint
- * in this case) the following neighbours accordingly.
- * This will not change the overall timeline duration.
+ * Edits the end time of an element within its timeline in roll mode.
+ * See ges_timeline_element_edit() with #GES_EDIT_MODE_ROLL and
+ * #GES_EDGE_END.
  *
- * Returns: %TRUE if the self as been rolled properly, %FALSE if an error
- * occured
+ * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure.
  */
 gboolean
 ges_timeline_element_roll_end (GESTimelineElement * self, GstClockTime end)
@@ -1084,35 +1525,27 @@ ges_timeline_element_roll_end (GESTimelineElement * self, GstClockTime end)
   GESTimelineElementClass *klass;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (end), FALSE);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   if (klass->roll_end)
     return klass->roll_end (self, end);
 
-  GST_WARNING_OBJECT (self, "No ripple virtual method implementation"
-      " on class %s. Can not roll end to %" GST_TIME_FORMAT,
-      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (end));
-
-  return FALSE;
+  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_ROLL,
+      GES_EDGE_END, end);
 }
 
 /**
  * ges_timeline_element_trim:
- * @self: The #GESTimelineElement to trim.
- * @start: The new start of @self in trim mode, will adapt the inpoint
- * of @self accordingly
- *
- * Edits @self in trim mode. It allows you to modify the
- * inpoint and start of @self.
- * This will not change the overall timeline duration.
+ * @self: The #GESTimelineElement to trim
+ * @start: The new start time of @self in trim mode
  *
- * Note that to trim the end of an self you can just set its duration. The same way
- * as this method, it will take into account the snapping-distance property of the
- * timeline in which @self is.
+ * Edits the start time of an element within its timeline in trim mode.
+ * See ges_timeline_element_edit() with #GES_EDIT_MODE_TRIM and
+ * #GES_EDGE_START.
  *
- * Returns: %TRUE if the self as been trimmed properly, %FALSE if an error
- * occured
+ * Returns: %TRUE if the trim edit of @self completed, %FALSE on failure.
  */
 gboolean
 ges_timeline_element_trim (GESTimelineElement * self, GstClockTime start)
@@ -1120,94 +1553,76 @@ ges_timeline_element_trim (GESTimelineElement * self, GstClockTime start)
   GESTimelineElementClass *klass;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   if (klass->trim)
     return klass->trim (self, start);
 
-  GST_WARNING_OBJECT (self, "No ripple virtual method implementation"
-      " on class %s. Can not trim to %" GST_TIME_FORMAT,
-      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start));
-
-  return FALSE;
+  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM,
+      GES_EDGE_START, start);
 }
 
 /**
  * ges_timeline_element_copy:
  * @self: The #GESTimelineElement to copy
- * @deep: whether we want to create the elements @self contains or not
- *
- * Copies @self
- *
- * Returns: (transfer floating): The newly create #GESTimelineElement, copied from @self
+ * @deep: Whether the copy is needed for pasting
+ *
+ * Create a copy of @self. All the properties of @self are copied into
+ * a new element, with the exception of #GESTimelineElement:parent,
+ * #GESTimelineElement:timeline and #GESTimelineElement:name. Other data,
+ * such the list of a #GESContainer's children, is **not** copied.
+ *
+ * If @deep is %TRUE, then the new element is prepared so that it can be
+ * used in ges_timeline_element_paste() or ges_timeline_paste_element().
+ * In the case of copying a #GESContainer, this ensures that the children
+ * of @self will also be pasted. The new element should not be used for
+ * anything else and can only be used **once** in a pasting operation. In
+ * particular, the new element itself is not an actual 'deep' copy of
+ * @self, but should be thought of as an intermediate object used for a
+ * single paste operation.
+ *
+ * Returns: (transfer floating): The newly create element,
+ * copied from @self.
  */
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
 GESTimelineElement *
 ges_timeline_element_copy (GESTimelineElement * self, gboolean deep)
 {
   GESAsset *asset;
-  GParameter *params;
   GParamSpec **specs;
   GESTimelineElementClass *klass;
-  guint n, n_specs, n_params;
+  guint n, n_specs;
 
   GESTimelineElement *ret = NULL;
 
-  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);
 
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_specs);
-  params = g_new0 (GParameter, n_specs);
-  n_params = 0;
 
+  asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
+  g_assert (asset);
+  ret = GES_TIMELINE_ELEMENT (ges_asset_extract (asset, NULL));
   for (n = 0; n < n_specs; ++n) {
     /* We do not want the timeline or the name to be copied */
     if (g_strcmp0 (specs[n]->name, "parent") &&
         g_strcmp0 (specs[n]->name, "timeline") &&
         g_strcmp0 (specs[n]->name, "name") &&
-        (specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE) {
-      params[n_params].name = g_intern_string (specs[n]->name);
-      g_value_init (&params[n_params].value, specs[n]->value_type);
-      g_object_get_property (G_OBJECT (self), specs[n]->name,
-          &params[n_params].value);
-      ++n_params;
-    }
-  }
-
-#if GLIB_CHECK_VERSION(2, 53, 1)
-  {
-    gint i;
-    GValue *values;
-    const gchar **names;
-    values = g_malloc0 (sizeof (GValue) * n_specs);
-    names = g_malloc0 (sizeof (gchar *) * n_specs);
-
-    for (i = 0; i < n_params; i++) {
-      values[i] = params[i].value;
-      names[i] = params[i].name;
+        (specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
+        (specs[n]->flags & G_PARAM_CONSTRUCT_ONLY) == 0) {
+      GValue v = G_VALUE_INIT;
+      g_value_init (&v, specs[n]->value_type);
+      g_object_get_property (G_OBJECT (self), specs[n]->name, &v);
+
+      g_object_set_property (G_OBJECT (ret), specs[n]->name, &v);
+      g_value_reset (&v);
     }
-
-    ret =
-        GES_TIMELINE_ELEMENT (g_object_new_with_properties (G_OBJECT_TYPE
-            (self), n_params, names, values));
-    g_free (names);
-    g_free (values);
   }
-#else
-  ret = g_object_newv (G_OBJECT_TYPE (self), n_params, params);
-#endif
-
-  while (n_params--)
-    g_value_unset (&params[n_params].value);
 
   g_free (specs);
-  g_free (params);
-
-
-  asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
-  if (asset)
-    ges_extractable_set_asset (GES_EXTRACTABLE (ret), asset);
   if (deep) {
     if (klass->deep_copy)
       klass->deep_copy (self, ret);
@@ -1223,35 +1638,35 @@ ges_timeline_element_copy (GESTimelineElement * self, gboolean deep)
   return ret;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
+
 /**
  * ges_timeline_element_get_toplevel_parent:
  * @self: The #GESTimelineElement to get the toplevel parent from
  *
- * Gets the toplevel #GESTimelineElement controlling @self
+ * Gets the toplevel #GESTimelineElement:parent of the element.
  *
- * Returns: (transfer full): The toplevel controlling parent of @self
+ * Returns: (transfer full): The toplevel parent of @self.
  */
 GESTimelineElement *
 ges_timeline_element_get_toplevel_parent (GESTimelineElement * self)
 {
-  GESTimelineElement *toplevel = self;
+  GESTimelineElement *toplevel;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);
 
-  while (GES_TIMELINE_ELEMENT_PARENT (toplevel))
-    toplevel = GES_TIMELINE_ELEMENT_PARENT (toplevel);
+  toplevel = ges_timeline_element_peak_toplevel (self);
 
   return gst_object_ref (toplevel);
 }
 
 /**
  * ges_timeline_element_get_name:
- * @self: a #GESTimelineElement
+ * @self: A #GESTimelineElement
  *
- * Returns a copy of the name of @self.
- * Caller should g_free() the return value after usage.
+ * Gets the #GESTimelineElement:name for the element.
  *
- * Returns: (transfer full): The name of @self
+ * Returns: (transfer full): The name of @self.
  */
 gchar *
 ges_timeline_element_get_name (GESTimelineElement * self)
@@ -1263,12 +1678,28 @@ ges_timeline_element_get_name (GESTimelineElement * self)
 
 /**
  * ges_timeline_element_set_name:
- * @self: a #GESTimelineElement
- * @name: (allow-none): The name @self should take (if avalaible<)
- *
- * Sets the name of object, or gives @self a guaranteed unique name (if name is NULL).
- * This function makes a copy of the provided name, so the caller retains ownership
- * of the name it sent.
+ * @self: A #GESTimelineElement
+ * @name: (allow-none): The name @self should take
+ *
+ * Sets the #GESTimelineElement:name for the element. If %NULL is given
+ * for @name, then the library will instead generate a new name based on
+ * the type name of the element, such as the name "uriclip3" for a
+ * #GESUriClip, and will set that name instead.
+ *
+ * If @self already has a #GESTimelineElement:timeline, you should not
+ * call this function with @name set to %NULL.
+ *
+ * You should ensure that, within each #GESTimeline, every element has a
+ * unique name. If you call this function with @name as %NULL, then
+ * the library should ensure that the set generated name is unique from
+ * previously **generated** names. However, if you choose a @name that
+ * interferes with the naming conventions of the library, the library will
+ * attempt to ensure that the generated names will not conflict with the
+ * chosen name, which may lead to a different name being set instead, but
+ * the uniqueness between generated and user-chosen names is not
+ * guaranteed.
+ *
+ * Returns: %TRUE if @name or a generated name for @self could be set.
  */
 gboolean
 ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name)
@@ -1286,6 +1717,8 @@ ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name)
   if (self->timeline != NULL && name) {
     GESTimelineElement *tmp = ges_timeline_get_element (self->timeline, name);
 
+    /* FIXME: if tmp == self then this means that we setting the name of
+     * self to its existing name. There is no need to throw an error */
     if (tmp) {
       gst_object_unref (tmp);
       goto had_timeline;
@@ -1294,9 +1727,15 @@ ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name)
     timeline_remove_element (self->timeline, self);
     readd_to_timeline = TRUE;
   }
+  /* FIXME: if self already has a timeline and name is NULL, then it also
+   * needs to be re-added to the timeline (or, at least its entry in
+   * timeline->priv->all_elements needs its key to be updated) using the
+   * new generated name */
 
   _set_name (self, name);
 
+  /* FIXME: the set name may not always be unique in a given timeline, see
+   * _set_name(). This can cause timeline_add_element to fail! */
   if (readd_to_timeline)
     timeline_add_element (self->timeline, self);
 
@@ -1305,85 +1744,51 @@ ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name)
   /* error */
 had_timeline:
   {
+    /* FIXME: message is misleading. We are here if some other object in
+     * the timeline was added under @name (see above) */
     GST_WARNING ("Object %s already in a timeline can't be renamed to %s",
         self->name, name);
     return FALSE;
   }
 }
 
-static gboolean
-emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data)
-{
-  g_signal_emit (data->self, ges_timeline_element_signals[DEEP_NOTIFY], 0,
-      data->child, data->arg);
-
-  gst_object_unref (data->child);
-  g_param_spec_unref (data->arg);
-  gst_object_unref (data->self);
-  g_slice_free (EmitDeepNotifyInIdleData, data);
-
-  return FALSE;
-}
-
-static void
-child_prop_changed_cb (GObject * child, GParamSpec * arg
-    G_GNUC_UNUSED, GESTimelineElement * self)
-{
-  EmitDeepNotifyInIdleData *data;
-
-  /* Emit "deep-notify" right away if in main thread */
-  if (g_main_context_acquire (g_main_context_default ())) {
-    g_main_context_release (g_main_context_default ());
-    g_signal_emit (self, ges_timeline_element_signals[DEEP_NOTIFY], 0,
-        child, arg);
-    return;
-  }
-
-  data = g_slice_new (EmitDeepNotifyInIdleData);
-
-  data->child = gst_object_ref (child);
-  data->arg = g_param_spec_ref (arg);
-  data->self = gst_object_ref (self);
-
-  g_idle_add ((GSourceFunc) emit_deep_notify_in_idle, data);
-}
-
+/**
+ * ges_timeline_element_add_child_property:
+ * @self: A #GESTimelineElement
+ * @pspec: The specification for the property to add
+ * @child: The #GstObject who the property belongs to
+ *
+ * Register a property of a child of the element to allow it to be
+ * written with ges_timeline_element_set_child_property() and read with
+ * ges_timeline_element_get_child_property(). A change in the property
+ * will also appear in the #GESTimelineElement::deep-notify signal.
+ *
+ * @pspec should be unique from other children properties that have been
+ * registered on @self.
+ *
+ * Returns: %TRUE if the property was successfully registered.
+ */
 gboolean
 ges_timeline_element_add_child_property (GESTimelineElement * self,
     GParamSpec * pspec, GObject * child)
 {
-  gchar *signame;
-  ChildPropHandler *handler;
-
-  if (g_hash_table_contains (self->priv->children_props, pspec)) {
-    GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name);
-    return FALSE;
-  }
-
-  GST_DEBUG_OBJECT (self, "Adding child property: %" GST_PTR_FORMAT "::%s",
-      child, pspec->name);
-
-  signame = g_strconcat ("notify::", pspec->name, NULL);
-  handler = (ChildPropHandler *) g_slice_new0 (ChildPropHandler);
-  handler->child = gst_object_ref (child);
-  handler->handler_id =
-      g_signal_connect (child, signame, G_CALLBACK (child_prop_changed_cb),
-      self);
-  g_hash_table_insert (self->priv->children_props, g_param_spec_ref (pspec),
-      handler);
-
-  g_free (signame);
+  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
+  g_return_val_if_fail (G_IS_OBJECT (child), FALSE);
 
-  return TRUE;
+  return ges_timeline_element_add_child_property_full (self, NULL, pspec,
+      child);
 }
 
 /**
  * ges_timeline_element_get_child_property_by_pspec:
- * @self: a #GESTrackElement
- * @pspec: The #GParamSpec that specifies the property you want to get
- * @value: (out): return location for the value
+ * @self: A #GESTimelineElement
+ * @pspec: The specification of a registered child property to get
+ * @value: (out): The return location for the value
  *
- * Gets a property of a child of @self.
+ * Gets the property of a child of the element. Specifically, the property
+ * corresponding to the @pspec used in
+ * ges_timeline_element_add_child_property() is copied into @value.
  */
 void
 ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self,
@@ -1392,6 +1797,7 @@ ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self,
   ChildPropHandler *handler;
 
   g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
+  g_return_if_fail (G_IS_PARAM_SPEC (pspec));
 
   handler = g_hash_table_lookup (self->priv->children_props, pspec);
   if (!handler)
@@ -1410,74 +1816,63 @@ not_found:
 
 /**
  * ges_timeline_element_set_child_property_by_pspec:
- * @self: a #GESTimelineElement
- * @pspec: The #GParamSpec that specifies the property you want to set
- * @value: the value
+ * @self: A #GESTimelineElement
+ * @pspec: The specification of a registered child property to set
+ * @value: The value to set the property to
  *
- * Sets a property of a child of @self.
+ * Sets the property of a child of the element. Specifically, the property
+ * corresponding to the @pspec used in
+ * ges_timeline_element_add_child_property() is set to @value.
  */
 void
 ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
     GParamSpec * pspec, const GValue * value)
 {
-  ChildPropHandler *handler;
-  GESTimelineElementClass *klass;
-
-  g_return_if_fail (GES_IS_TRACK_ELEMENT (self));
-
-  handler = g_hash_table_lookup (self->priv->children_props, pspec);
-
-  if (!handler)
-    goto not_found;
-
-  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
-  g_assert (klass->set_child_property);
-  klass->set_child_property (self, handler->child, pspec, (GValue *) value);
-
-  return;
+  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
+  g_return_if_fail (G_IS_PARAM_SPEC (pspec));
 
-not_found:
-  {
-    GST_ERROR ("The %s property doesn't exist", pspec->name);
-    return;
-  }
+  set_child_property_by_pspec (self, pspec, value, NULL);
 }
 
 /**
- * ges_timeline_element_set_child_property:
- * @self: The origin #GESTimelineElement
- * @property_name: The name of the property
- * @value: the value
- *
- * Sets a property of a child of @self
- *
- * Note that #ges_timeline_element_set_child_property is really
- * intended for language bindings, #ges_timeline_element_set_child_properties
- * is much more convenient for C programming.
- *
- * Returns: %TRUE if the property was set, %FALSE otherwize
+ * ges_timeline_element_set_child_property_full:
+ * @self: A #GESTimelineElement
+ * @property_name: The name of the child property to set
+ * @value: The value to set the property to
+ * @error: (nullable): Return location for an error
+ *
+ * Sets the property of a child of the element.
+ *
+ * @property_name can either be in the format "prop-name" or
+ * "TypeName::prop-name", where "prop-name" is the name of the property
+ * to set (as used in g_object_set()), and "TypeName" is the type name of
+ * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is
+ * useful when two children of different types share the same property
+ * name.
+ *
+ * The first child found with the given "prop-name" property that was
+ * registered with ges_timeline_element_add_child_property() (and of the
+ * type "TypeName", if it was given) will have the corresponding
+ * property set to @value. Other children that may have also matched the
+ * property name (and type name) are left unchanged!
+ *
+ * Returns: %TRUE if the property was found and set.
+ * Since: 1.18
  */
 gboolean
-ges_timeline_element_set_child_property (GESTimelineElement * self,
-    const gchar * property_name, const GValue * value)
+ges_timeline_element_set_child_property_full (GESTimelineElement * self,
+    const gchar * property_name, const GValue * value, GError ** error)
 {
   GParamSpec *pspec;
-  GESTimelineElementClass *klass;
   GObject *child;
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
 
   if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec))
     goto not_found;
 
-  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
-  g_assert (klass->set_child_property);
-  klass->set_child_property (self, child, pspec, (GValue *) value);
-
-  gst_object_unref (child);
-  g_param_spec_unref (pspec);
-
-  return TRUE;
+  return set_child_property_by_pspec (self, pspec, value, error);
 
 not_found:
   {
@@ -1488,24 +1883,52 @@ not_found:
 }
 
 /**
-* ges_timeline_element_get_child_property:
-* @self: The origin #GESTimelineElement
-* @property_name: The name of the property
-* @value: (out): return location for the property value, it will
-* be initialized if it is initialized with 0
-*
-* In general, a copy is made of the property contents and
-* the caller is responsible for freeing the memory by calling
-* g_value_unset().
-*
-* Gets a property of a GstElement contained in @object.
-*
-* Note that #ges_timeline_element_get_child_property is really
-* intended for language bindings, #ges_timeline_element_get_child_properties
-* is much more convenient for C programming.
-*
-* Returns: %TRUE if the property was found, %FALSE otherwize
-*/
+ * ges_timeline_element_set_child_property:
+ * @self: A #GESTimelineElement
+ * @property_name: The name of the child property to set
+ * @value: The value to set the property to
+ *
+ * See ges_timeline_element_set_child_property_full(), which also gives an
+ * error.
+ *
+ * Note that ges_timeline_element_set_child_properties() may be more
+ * convenient for C programming.
+ *
+ * Returns: %TRUE if the property was found and set.
+ */
+gboolean
+ges_timeline_element_set_child_property (GESTimelineElement * self,
+    const gchar * property_name, const GValue * value)
+{
+  return ges_timeline_element_set_child_property_full (self, property_name,
+      value, NULL);
+}
+
+/**
+ * ges_timeline_element_get_child_property:
+ * @self: A #GESTimelineElement
+ * @property_name: The name of the child property to get
+ * @value: (out): The return location for the value
+ *
+ * Gets the property of a child of the element.
+ *
+ * @property_name can either be in the format "prop-name" or
+ * "TypeName::prop-name", where "prop-name" is the name of the property
+ * to get (as used in g_object_get()), and "TypeName" is the type name of
+ * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is
+ * useful when two children of different types share the same property
+ * name.
+ *
+ * The first child found with the given "prop-name" property that was
+ * registered with ges_timeline_element_add_child_property() (and of the
+ * type "TypeName", if it was given) will have the corresponding
+ * property copied into @value.
+ *
+ * Note that ges_timeline_element_get_child_properties() may be more
+ * convenient for C programming.
+ *
+ * Returns: %TRUE if the property was found and copied to @value.
+ */
 gboolean
 ges_timeline_element_get_child_property (GESTimelineElement * self,
     const gchar * property_name, GValue * value)
@@ -1518,6 +1941,8 @@ ges_timeline_element_get_child_property (GESTimelineElement * self,
   if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec))
     goto not_found;
 
+  /* FIXME: since GLib 2.60, g_object_get_property() will automatically
+   * initialize the type */
   if (G_VALUE_TYPE (value) == G_TYPE_INVALID)
     g_value_init (value, pspec->value_type);
 
@@ -1538,24 +1963,29 @@ not_found:
 
 /**
  * ges_timeline_element_lookup_child:
- * @self: object to lookup the property in
- * @prop_name: name of the property to look up. You can specify the name of the
- *     class as such: "ClassName::property-name", to guarantee that you get the
- *     proper GParamSpec in case various GstElement-s contain the same property
- *     name. If you don't do so, you will get the first element found, having
- *     this property and the and the corresponding GParamSpec.
- * @child: (out) (allow-none) (transfer full): pointer to a #GstElement that
- *     takes the real object to set property on
- * @pspec: (out) (allow-none) (transfer full): pointer to take the #GParamSpec
- *     describing the property
- *
- * Looks up which @element and @pspec would be effected by the given @name. If various
- * contained elements have this property name you will get the first one, unless you
- * specify the class name in @name.
- *
- * Returns: TRUE if @element and @pspec could be found. FALSE otherwise. In that
- * case the values for @pspec and @element are not modified. Unref @element after
- * usage.
+ * @self: A #GESTimelineElement
+ * @prop_name: The name of a child property
+ * @child: (out) (optional) (transfer full): The return location for the
+ * found child
+ * @pspec: (out) (optional) (transfer full): The return location for the
+ * specification of the child property
+ *
+ * Looks up a child property of the element.
+ *
+ * @prop_name can either be in the format "prop-name" or
+ * "TypeName::prop-name", where "prop-name" is the name of the property
+ * to look up (as used in g_object_get()), and "TypeName" is the type name
+ * of the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is
+ * useful when two children of different types share the same property
+ * name.
+ *
+ * The first child found with the given "prop-name" property that was
+ * registered with ges_timeline_element_add_child_property() (and of the
+ * type "TypeName", if it was given) will be passed to @child, and the
+ * registered specification of this property will be passed to @pspec.
+ *
+ * Returns: %TRUE if a child corresponding to the property was found, in
+ * which case @child and @pspec are set.
  */
 gboolean
 ges_timeline_element_lookup_child (GESTimelineElement * self,
@@ -1572,15 +2002,13 @@ ges_timeline_element_lookup_child (GESTimelineElement * self,
 
 /**
  * ges_timeline_element_set_child_property_valist:
- * @self: The #GESTimelineElement parent object
- * @first_property_name: The name of the first property to set
- * @var_args: value for the first property, followed optionally by more
- * name/return location pairs, followed by NULL
- *
- * Sets a property of a child of @self. If there are various child elements
- * that have the same property name, you can distinguish them using the following
- * syntax: 'ClasseName::property_name' as property name. If you don't, the
- * corresponding property of the first element found will be set.
+ * @self: A #GESTimelineElement
+ * @first_property_name: The name of the first child property to set
+ * @var_args: The value for the first property, followed optionally by more
+ * name/value pairs, followed by %NULL
+ *
+ * Sets several of the children properties of the element. See
+ * ges_timeline_element_set_child_property().
  */
 void
 ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
@@ -1588,7 +2016,6 @@ ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
 {
   const gchar *name;
   GParamSpec *pspec;
-  GObject *child;
 
   gchar *error = NULL;
   GValue value = { 0, };
@@ -1602,7 +2029,7 @@ ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
 
   /* iterate over pairs */
   while (name) {
-    if (!ges_timeline_element_lookup_child (self, name, &child, &pspec))
+    if (!ges_timeline_element_lookup_child (self, name, NULL, &pspec))
       goto not_found;
 
     G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args,
@@ -1611,9 +2038,8 @@ ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
     if (error)
       goto cant_copy;
 
-    g_object_set_property (child, pspec->name, &value);
+    set_child_property_by_pspec (self, pspec, &value, NULL);
 
-    gst_object_unref (child);
     g_param_spec_unref (pspec);
     g_value_unset (&value);
 
@@ -1631,7 +2057,6 @@ cant_copy:
     GST_WARNING_OBJECT (self, "error copying value %s in %p: %s", pspec->name,
         self, error);
 
-    gst_object_unref (child);
     g_param_spec_unref (pspec);
     g_value_unset (&value);
     return;
@@ -1640,15 +2065,13 @@ cant_copy:
 
 /**
  * ges_timeline_element_set_child_properties:
- * @self: The #GESTimelineElement parent object
- * @first_property_name: The name of the first property to set
- * @...: value for the first property, followed optionally by more
- * name/return location pairs, followed by NULL
- *
- * Sets a property of a child of @self. If there are various child elements
- * that have the same property name, you can distinguish them using the following
- * syntax: 'ClasseName::property_name' as property name. If you don't, the
- * corresponding property of the first element found will be set.
+ * @self: A #GESTimelineElement
+ * @first_property_name: The name of the first child property to set
+ * @...: The value for the first property, followed optionally by more
+ * name/value pairs, followed by %NULL
+ *
+ * Sets several of the children properties of the element. See
+ * ges_timeline_element_set_child_property().
  */
 void
 ges_timeline_element_set_child_properties (GESTimelineElement * self,
@@ -1666,15 +2089,13 @@ ges_timeline_element_set_child_properties (GESTimelineElement * self,
 
 /**
  * ges_timeline_element_get_child_property_valist:
- * @self: The #GESTimelineElement parent object
- * @first_property_name: The name of the first property to get
- * @var_args: value for the first property, followed optionally by more
- * name/return location pairs, followed by NULL
- *
- * Gets a property of a child of @self. If there are various child elements
- * that have the same property name, you can distinguish them using the following
- * syntax: 'ClasseName::property_name' as property name. If you don't, the
- * corresponding property of the first element found will be set.
+ * @self: A #GESTimelineElement
+ * @first_property_name: The name of the first child property to get
+ * @var_args: The return location for the first property, followed
+ * optionally by more name/return location pairs, followed by %NULL
+ *
+ * Gets several of the children properties of the element. See
+ * ges_timeline_element_get_child_property().
  */
 void
 ges_timeline_element_get_child_property_valist (GESTimelineElement * self,
@@ -1732,14 +2153,17 @@ compare_gparamspec (GParamSpec ** a, GParamSpec ** b, gpointer udata)
 
 /**
  * ges_timeline_element_list_children_properties:
- * @self: The #GESTimelineElement to get the list of children properties from
- * @n_properties: (out): return location for the length of the returned array
+ * @self: A #GESTimelineElement
+ * @n_properties: (out): The return location for the length of the
+ * returned array
  *
- * Gets an array of #GParamSpec* for all configurable properties of the
- * children of @self.
+ * Get a list of children properties of the element, which is a list of
+ * all the specifications passed to
+ * ges_timeline_element_add_child_property().
  *
- * Returns: (transfer full) (array length=n_properties): an array of #GParamSpec* which should be freed after use or
- * %NULL if something went wrong
+ * Returns: (transfer full) (array length=n_properties): An array of
+ * #GParamSpec corresponding to the child properties of @self, or %NULL if
+ * something went wrong.
  */
 GParamSpec **
 ges_timeline_element_list_children_properties (GESTimelineElement * self,
@@ -1769,12 +2193,13 @@ ges_timeline_element_list_children_properties (GESTimelineElement * self,
 
 /**
  * ges_timeline_element_get_child_properties:
- * @self: The origin #GESTimelineElement
- * @first_property_name: The name of the first property to get
- * @...: return location for the first property, followed optionally by more
- * name/return location pairs, followed by NULL
+ * @self: A #GESTimelineElement
+ * @first_property_name: The name of the first child property to get
+ * @...: The return location for the first property, followed
+ * optionally by more name/return location pairs, followed by %NULL
  *
- * Gets properties of a child of @self.
+ * Gets several of the children properties of the element. See
+ * ges_timeline_element_get_child_property().
  */
 void
 ges_timeline_element_get_child_properties (GESTimelineElement * self,
@@ -1790,18 +2215,56 @@ ges_timeline_element_get_child_properties (GESTimelineElement * self,
   va_end (var_args);
 }
 
+/**
+ * ges_timeline_element_remove_child_property:
+ * @self: A #GESTimelineElement
+ * @pspec: The specification for the property to remove
+ *
+ * Remove a child property from the element. @pspec should be a
+ * specification that was passed to
+ * ges_timeline_element_add_child_property(). The corresponding property
+ * will no longer be registered as a child property for the element.
+ *
+ * Returns: %TRUE if the property was successfully un-registered for @self.
+ */
 gboolean
 ges_timeline_element_remove_child_property (GESTimelineElement * self,
     GParamSpec * pspec)
 {
-  return g_hash_table_remove (self->priv->children_props, pspec);
+  gpointer key, value;
+  GParamSpec *found_pspec;
+  ChildPropHandler *handler;
+
+  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
+
+  if (!g_hash_table_lookup_extended (self->priv->children_props, pspec,
+          &key, &value)) {
+    GST_WARNING_OBJECT (self, "No child property with pspec %p (%s) found",
+        pspec, pspec->name);
+    return FALSE;
+  }
+  g_hash_table_steal (self->priv->children_props, pspec);
+  found_pspec = G_PARAM_SPEC (key);
+  handler = (ChildPropHandler *) value;
+
+  g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_REMOVED], 0,
+      handler->child, found_pspec);
+
+  g_param_spec_unref (found_pspec);
+  _child_prop_handler_free (handler);
+
+  return TRUE;
 }
 
 /**
  * ges_timeline_element_get_track_types:
  * @self: A #GESTimelineElement
  *
- * Gets all the TrackTypes @self will interact with
+ * Gets the track types that the element can interact with, i.e. the type
+ * of #GESTrack it can exist in, or will create #GESTrackElement-s for.
+ *
+ * Returns: The track types that @self supports.
  *
  * Since: 1.6.0
  */
@@ -1818,15 +2281,29 @@ ges_timeline_element_get_track_types (GESTimelineElement * self)
 /**
  * ges_timeline_element_paste:
  * @self: The #GESTimelineElement to paste
- * @paste_position: The position in the timeline the element should
- * be copied to, meaning it will become the start of @self
+ * @paste_position: The position in the timeline @element should be pasted
+ * to, i.e. the #GESTimelineElement:start value for the pasted element.
+ *
+ * Paste an element inside the same timeline and layer as @self. @self
+ * **must** be the return of ges_timeline_element_copy() with `deep=TRUE`,
+ * and it should not be changed before pasting.
+ * @self is not placed in the timeline, instead a new element is created,
+ * alike to the originally copied element. Note that the originally
+ * copied element must stay within the same timeline and layer, at both
+ * the point of copying and pasting.
+ *
+ * Pasting may fail if it would place the timeline in an unsupported
+ * configuration.
  *
- * Paste @self inside the timeline. @self must have been created
- * using ges_timeline_element_copy with recurse=TRUE set,
- * otherwise it will fail.
+ * After calling this function @element should not be used. In particular,
+ * @element can **not** be pasted again. Instead, you can copy the
+ * returned element and paste that copy (although, this is only possible
+ * if the paste was successful).
  *
- * Returns: (transfer full): New element resulting of pasting @self
- * or %NULL
+ * See also ges_timeline_paste_element().
+ *
+ * Returns: (transfer full) (nullable): The newly created element, or
+ * %NULL if pasting fails.
  *
  * Since: 1.6.0
  */
@@ -1836,6 +2313,7 @@ ges_timeline_element_paste (GESTimelineElement * self,
 {
   GESTimelineElement *res;
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (paste_position), FALSE);
 
   if (!self->priv->copied_from) {
     GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!");
@@ -1854,16 +2332,20 @@ ges_timeline_element_paste (GESTimelineElement * self,
 
   g_clear_object (&self->priv->copied_from);
 
-  return res ? g_object_ref (res) : res;
+  return res ? g_object_ref_sink (res) : res;
 }
 
 /**
  * ges_timeline_element_get_layer_priority:
  * @self: A #GESTimelineElement
  *
- * Returns: The priority of the first layer the element is in (note that only
- * groups can span over several layers). %GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY
- * means that the element is not in a layer.
+ * Gets the priority of the layer the element is in. A #GESGroup may span
+ * several layers, so this would return the highest priority (numerically,
+ * the smallest) amongst them.
+ *
+ * Returns: The priority of the layer @self is in, or
+ * #GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY if @self does not exist in a
+ * layer.
  *
  * Since: 1.16
  */
@@ -1879,69 +2361,140 @@ ges_timeline_element_get_layer_priority (GESTimelineElement * self)
   return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority (self);
 }
 
-/* Internal */
-gdouble
-ges_timeline_element_get_media_duration_factor (GESTimelineElement * self)
+/**
+ * ges_timeline_element_edit_full:
+ * @self: The #GESTimelineElement to edit
+ * @new_layer_priority: The priority/index of the layer @self should be
+ * moved to. -1 means no move
+ * @mode: The edit mode
+ * @edge: The edge of @self where the edit should occur
+ * @position: The edit position: a new location for the edge of @self
+ * (in nanoseconds) in the timeline coordinates
+ * @error: (nullable): Return location for an error
+ *
+ * Edits the element within its timeline by adjusting its
+ * #GESTimelineElement:start, #GESTimelineElement:duration or
+ * #GESTimelineElement:in-point, and potentially doing the same for
+ * other elements in the timeline. See #GESEditMode for details about each
+ * edit mode. An edit may fail if it would place one of these properties
+ * out of bounds, or if it would place the timeline in an unsupported
+ * configuration.
+ *
+ * Note that if you act on a #GESTrackElement, this will edit its parent
+ * #GESClip instead. Moreover, for any #GESTimelineElement, if you select
+ * #GES_EDGE_NONE for #GES_EDIT_MODE_NORMAL or #GES_EDIT_MODE_RIPPLE, this
+ * will edit the toplevel instead, but still in such a way as to make the
+ * #GESTimelineElement:start of @self reach the edit @position.
+ *
+ * Note that if the element's timeline has a
+ * #GESTimeline:snapping-distance set, then the edit position may be
+ * snapped to the edge of some element under the edited element.
+ *
+ * @new_layer_priority can be used to switch @self, and other elements
+ * moved by the edit, to a new layer. New layers may be be created if the
+ * the corresponding layer priority/index does not yet exist for the
+ * timeline.
+ *
+ * Returns: %TRUE if the edit of @self completed, %FALSE on failure.
+ * Since: 1.18
+ */
+
+gboolean
+ges_timeline_element_edit_full (GESTimelineElement * self,
+    gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position,
+    GError ** error)
 {
-  gdouble media_duration_factor;
-  GESEffectClass *class;
-  GList *props;
+  GESTimeline *timeline;
+  guint32 layer_prio;
 
-  media_duration_factor = 1.0;
+  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
 
-  class = GES_EFFECT_CLASS (g_type_class_ref (GES_TYPE_EFFECT));
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
+  g_return_val_if_fail (timeline, FALSE);
 
-  for (props = class->rate_properties; props != NULL; props = props->next) {
-    GObject *child;
-    GParamSpec *pspec;
-    if (ges_timeline_element_lookup_child (self, props->data, &child, &pspec)) {
-      if (G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_FLOAT) {
-        gfloat rate_change;
-        g_object_get (child, pspec->name, &rate_change, NULL);
-        media_duration_factor *= rate_change;
-      } else if (G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_DOUBLE) {
-        gdouble rate_change;
-        g_object_get (child, pspec->name, &rate_change, NULL);
-        media_duration_factor *= rate_change;
-      } else {
-        GST_WARNING_OBJECT (self,
-            "Rate property %s in child %" GST_PTR_FORMAT
-            " is of unsupported type %s", pspec->name, child,
-            G_VALUE_TYPE_NAME (pspec->value_type));
-      }
+  layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self);
 
-      gst_object_unref (child);
-      g_param_spec_unref (pspec);
+  if (new_layer_priority < 0)
+    new_layer_priority = layer_prio;
 
-      GST_DEBUG_OBJECT (self,
-          "Added rate changing property %s, set to value %lf",
-          (const char *) props->data, media_duration_factor);
-    }
-  }
+  GST_DEBUG_OBJECT (self, "Editing %s at edge %s to position %"
+      GST_TIME_FORMAT " under %s mode, and to layer %" G_GINT64_FORMAT,
+      self->name, ges_edge_name (edge), GST_TIME_ARGS (position),
+      ges_edit_mode_name (mode), new_layer_priority);
 
-  g_type_class_unref (class);
-  return media_duration_factor;
+  return ges_timeline_edit (timeline, self, new_layer_priority, mode,
+      edge, position, error);
 }
 
-/* Internal */
-GESTimelineElement *
-ges_timeline_element_get_copied_from (GESTimelineElement * self)
-{
-  GESTimelineElement *copied_from = self->priv->copied_from;
-  self->priv->copied_from = NULL;
-  return copied_from;
-}
+/**
+ * ges_timeline_element_edit:
+ * @self: The #GESTimelineElement to edit
+ * @layers: (element-type GESLayer) (nullable): A whitelist of layers
+ * where the edit can be performed, %NULL allows all layers in the
+ * timeline.
+ * @new_layer_priority: The priority/index of the layer @self should be
+ * moved to. -1 means no move
+ * @mode: The edit mode
+ * @edge: The edge of @self where the edit should occur
+ * @position: The edit position: a new location for the edge of @self
+ * (in nanoseconds) in the timeline coordinates
+ *
+ * See ges_timeline_element_edit_full(), which also gives an error.
+ *
+ * Note that the @layers argument is currently ignored, so you should
+ * just pass %NULL.
+ *
+ * Returns: %TRUE if the edit of @self completed, %FALSE on failure.
+ *
+ * Since: 1.18
+ */
 
-GESTimelineElementFlags
-ges_timeline_element_flags (GESTimelineElement * self)
+/* FIXME: handle the layers argument. Currently we always treat it as if
+ * it is NULL in the ges-timeline code */
+gboolean
+ges_timeline_element_edit (GESTimelineElement * self, GList * layers,
+    gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
 {
-  return self->priv->flags;
+  return ges_timeline_element_edit_full (self, new_layer_priority, mode, edge,
+      position, NULL);
 }
 
-void
-ges_timeline_element_set_flags (GESTimelineElement * self,
-    GESTimelineElementFlags flags)
+/**
+ * ges_timeline_element_get_natural_framerate:
+ * @self: The #GESTimelineElement to get "natural" framerate from
+ * @framerate_n: (out): The framerate numerator
+ * @framerate_d: (out): The framerate denominator
+ *
+ * Get the "natural" framerate of @self. This is to say, for example
+ * for a #GESVideoUriSource the framerate of the source.
+ *
+ * Note that a #GESAudioSource may also have a natural framerate if it derives
+ * from the same #GESSourceClip asset as a #GESVideoSource, and its value will
+ * be that of the video source. For example, if the uri of a #GESUriClip points
+ * to a file that contains both a video and audio stream, then the corresponding
+ * #GESAudioUriSource will share the natural framerate of the corresponding
+ * #GESVideoUriSource.
+ *
+ * Returns: Whether @self has a natural framerate or not, @framerate_n
+ * and @framerate_d will be set to, respectively, 0 and -1 if it is
+ * not the case.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_timeline_element_get_natural_framerate (GESTimelineElement * self,
+    gint * framerate_n, gint * framerate_d)
 {
-  self->priv->flags = flags;
+  GESTimelineElementClass *klass;
+
+  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
+  g_return_val_if_fail (framerate_n && framerate_d, FALSE);
+
+  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
+  *framerate_n = 0;
+  *framerate_d = -1;
+  return klass->get_natural_framerate (self, framerate_n, framerate_d);
 }
index 860b72b..80fbd6f 100644 (file)
@@ -17,8 +17,7 @@
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef _GES_TIMELINE_ELEMENT_H_
-#define _GES_TIMELINE_ELEMENT_H_
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TIMELINE_ELEMENT             (ges_timeline_element_get_type ())
-#define GES_TIMELINE_ELEMENT(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TIMELINE_ELEMENT, GESTimelineElement))
-#define GES_TIMELINE_ELEMENT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TIMELINE_ELEMENT, GESTimelineElementClass))
-#define GES_IS_TIMELINE_ELEMENT(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TIMELINE_ELEMENT))
-#define GES_IS_TIMELINE_ELEMENT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TIMELINE_ELEMENT))
-#define GES_TIMELINE_ELEMENT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TIMELINE_ELEMENT, GESTimelineElementClass))
-
-typedef struct _GESTimelineElementPrivate GESTimelineElementPrivate;
+GES_DECLARE_TYPE(TimelineElement, timeline_element, TIMELINE_ELEMENT);
 
 /**
  * GES_TIMELINE_ELEMENT_START:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The start position of the object (in nanoseconds).
+ * The #GESTimelineElement:start of @obj.
  */
 #define GES_TIMELINE_ELEMENT_START(obj) (((GESTimelineElement*)obj)->start)
 
 /**
  * GES_TIMELINE_ELEMENT_END:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The end position of the object (in nanoseconds).
+ * The end position of @obj: #GESTimelineElement:start +
+ * #GESTimelineElement:duration.
  */
 #define GES_TIMELINE_ELEMENT_END(obj) ((((GESTimelineElement*)obj)->start) + (((GESTimelineElement*)obj)->duration))
 
 /**
  * GES_TIMELINE_ELEMENT_INPOINT:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The in-point of the object (in nanoseconds).
+ * The #GESTimelineElement:in-point of @obj.
  */
 #define GES_TIMELINE_ELEMENT_INPOINT(obj) (((GESTimelineElement*)obj)->inpoint)
 
 /**
  * GES_TIMELINE_ELEMENT_DURATION:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The duration of the object (in nanoseconds).
+ * The #GESTimelineElement:duration of @obj.
  */
 #define GES_TIMELINE_ELEMENT_DURATION(obj) (((GESTimelineElement*)obj)->duration)
 
 /**
  * GES_TIMELINE_ELEMENT_MAX_DURATION:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The maximun duration of the object (in nanoseconds).
+ * The #GESTimelineElement:max-duration of @obj.
  */
 #define GES_TIMELINE_ELEMENT_MAX_DURATION(obj) (((GESTimelineElement*)obj)->maxduration)
 
 /**
  * GES_TIMELINE_ELEMENT_PRIORITY:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The priority of the object.
+ * The #GESTimelineElement:priority of @obj.
  */
 #define GES_TIMELINE_ELEMENT_PRIORITY(obj) (((GESTimelineElement*)obj)->priority)
 
 /**
  * GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY:
  *
- * Layer priority when the element is not in a layer
+ * Layer priority when a timeline element is not in any layer.
  */
 #define GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY ((guint32) -1)
 
@@ -95,47 +89,49 @@ typedef struct _GESTimelineElementPrivate GESTimelineElementPrivate;
  * GES_TIMELINE_ELEMENT_LAYER_PRIORITY:
  * @obj: The object to retrieve the layer priority from
  *
- * See #ges_timeline_element_get_layer_priority
+ * See #ges_timeline_element_get_layer_priority.
  */
 #define GES_TIMELINE_ELEMENT_LAYER_PRIORITY(obj) (ges_timeline_element_get_layer_priority(((GESTimelineElement*)obj)))
 
 /**
  * GES_TIMELINE_ELEMENT_PARENT:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The parent of the object.
+ * The #GESTimelineElement:parent of @obj.
  */
 #define GES_TIMELINE_ELEMENT_PARENT(obj) (((GESTimelineElement*)obj)->parent)
 
 /**
  * GES_TIMELINE_ELEMENT_TIMELINE:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The timeline in which the object is.
+ * The #GESTimelineElement:timeline of @obj.
  */
 #define GES_TIMELINE_ELEMENT_TIMELINE(obj) (((GESTimelineElement*)obj)->timeline)
 
 /**
  * GES_TIMELINE_ELEMENT_NAME:
- * @obj: a #GESTimelineElement
+ * @obj: A #GESTimelineElement
  *
- * The name of the object.
+ * The #GESTimelineElement:name of @obj.
  */
 #define GES_TIMELINE_ELEMENT_NAME(obj) (((GESTimelineElement*)obj)->name)
 
 /**
  * GESTimelineElement:
- * @parent: The #GESTimelineElement that controls the object
+ * @parent: The #GESTimelineElement:parent of the element
  * @asset: The #GESAsset from which the object has been extracted
- * @start: position (in time) of the object
- * @inpoint: Position in the media from which the object should be used
- * @duration: duration of the object to be used
- * @maxduration: The maximum duration the object can have
- * @priority: priority of the object in the layer (0:top priority)
+ * @start: The #GESTimelineElement:start of the element
+ * @inpoint: The #GESTimelineElement:in-point of the element
+ * @duration: The #GESTimelineElement:duration of the element
+ * @maxduration: The #GESTimelineElement:max-duration of the element
+ * @priority: The #GESTimelineElement:priority of the element
+ * @name: The #GESTimelineElement:name of the element
+ * @timeline: The #GESTimelineElement:timeline of the element
  *
- * Those filed can be accessed from outside but in no case should
- * be changed from there. Subclasses can write them but should make
- * sure to properly call g_object_notify.
+ * All members can be read freely, but should usually not be written to.
+ * Subclasses may write to them, but should make sure to properly call
+ * g_object_notify().
  */
 struct _GESTimelineElement
 {
@@ -162,26 +158,80 @@ struct _GESTimelineElement
 
 /**
  * GESTimelineElementClass:
- * @set_parent: method to set the parent of a #GESTimelineElement.
- * @set_start: method to set the start of a #GESTimelineElement
- * @set_duration: method to set the duration of a #GESTimelineElement
- * @set_inpoint: method to set the inpoint of a #GESTimelineElement
- * @set_max_duration: method to set the maximun duration of a #GESTimelineElement
- * @set_priority: method to set the priority of a #GESTimelineElement
- * @ripple: method to ripple an object
- * @ripple_end: method to ripple an object on its #GES_EDGE_END edge
- * @roll_start: method to roll an object on its #GES_EDGE_START edge
- * @roll_end: method to roll an object on its #GES_EDGE_END edge
- * @trim: method to trim an object
- * @deep_copy: Copy the children properties of @self into @copy
+ * @set_parent: Method called just before the #GESTimelineElement:parent
+ * is set.
+ * @set_start: Method called just before the #GESTimelineElement:start is
+ * set. This method should check whether the #GESTimelineElement:start can
+ * be changed to the new value and to otherwise prepare the element in
+ * response to what the new value will be. A return of %FALSE means that
+ * the property should not be set. A return of %TRUE means that the
+ * property should be set to the value given to the setter and a notify
+ * emitted. A return of -1 means that the property should not be set but
+ * the setter should still return %TRUE (normally because the method
+ * already handled setting the value, potentially to a snapped value, and
+ * emitted the notify signal).
+ * #GESTimelineElement:duration is set. This method should check
+ * whether the #GESTimelineElement:duration can be changed to the new
+ * value and to otherwise prepare the element in response to what the new
+ * value will be. A return of %FALSE means that the property should not be
+ * set. A return of %TRUE means that the property should be set to the
+ * value given to the setter and a notify emitted. A return of -1 means
+ * that the property should not be set but the setter should still return
+ * %TRUE (normally because the method already handled setting the value,
+ * potentially to a snapped value, and emitted the notify signal).
+ * @set_inpoint: Method called just before the
+ * #GESTimelineElement:in-point is set to a new value. This method should
+ * not set the #GESTimelineElement:in-point itself, but should check
+ * whether it can be changed to the new value and to otherwise prepare the
+ * element in response to what the new value will be. A return of %FALSE
+ * means that the property should not be set.
+ * @set_max_duration: Method called just before the
+ * #GESTimelineElement:max-duration is set. This method should
+ * not set the #GESTimelineElement:max-duration itself, but should check
+ * whether it can be changed to the new value and to otherwise prepare the
+ * element in response to what the new value will be. A return of %FALSE
+ * means that the property should not be set.
+ * @set_priority:  Method called just before the
+ * #GESTimelineElement:priority is set.
+ * @ripple: Set this method to overwrite a redirect to
+ * ges_timeline_element_edit() in ges_timeline_element_ripple().
+ * @ripple_end: Set this method to overwrite a redirect to
+ * ges_timeline_element_edit() in ges_timeline_element_ripple_end().
+ * @roll: Set this method to overwrite a redirect to
+ * ges_timeline_element_edit() in ges_timeline_element_roll().
+ * @roll_end: Set this method to overwrite a redirect to
+ * ges_timeline_element_edit() in ges_timeline_element_roll_end().
+ * @trim: Set this method to overwrite a redirect to
+ * ges_timeline_element_edit() in ges_timeline_element_trim().
+ * @deep_copy: Prepare @copy for pasting as a copy of @self. At least by
+ * copying the children properties of @self into @copy.
+ * @paste: Paste @self, which is the @copy prepared by @deep_copy, into
+ * the timeline at the given @paste_position, with @ref_element as a
+ * reference, which is the @self that was passed to @deep_copy.
+ * @lookup-child: Method to find a child with the child property.
+ * @prop_name. The default vmethod will return the first child that
+ * matches. Overwrite this with a call to the parent vmethod if you wish
+ * to redirect property names. Or overwrite to change which child is
+ * returned if multiple children share the same child property name.
+ * @get_track_types: Return a the track types for the element.
+ * @list_children_properties: List the children properties that have been
+ * registered for the element. The default implementation is able to fetch
+ * all of these, so should be sufficient. If you overwrite this, you
+ * should still call the default implementation to get the full list, and
+ * then edit its content.
+ * @lookup_child: Find @child, and its registered child property @pspec,
+ * corresponding to the child property specified by @prop_name. The
+ * default implementation will search for the first child that matches. If
+ * you overwrite this, you will likely still want to call the default
+ * vmethod, which has access to the registered parameter specifications.
  *
- * The GESTimelineElement base class. Subclasses should override at least
+ * The #GESTimelineElement base class. Subclasses should override at least
  * @set_start @set_inpoint @set_duration @ripple @ripple_end @roll_start
  * @roll_end and @trim.
  *
  * Vmethods in subclasses should apply all the operation they need to but
  * the real method implementation is in charge of setting the proper field,
- * and emit the notify signal.
+ * and emitting the notify signal.
  */
 struct _GESTimelineElementClass
 {
@@ -210,135 +260,197 @@ struct _GESTimelineElementClass
   gboolean (*lookup_child)                 (GESTimelineElement *self, const gchar *prop_name,
                                             GObject **child, GParamSpec **pspec);
   GESTrackType (*get_track_types)          (GESTimelineElement * self);
-  void         (*set_child_property)       (GESTimelineElement * self, GObject *child,
-                                            GParamSpec *pspec, GValue *value);
 
+  /**
+   * GESTimelineElementClass::set_child_property:
+   *
+   * Method for setting the child property given by
+   * @pspec on @child to @value. Default implementation will use
+   * g_object_set_property().
+   *
+   * Since: 1.16
+   */
+  void         (*set_child_property)       (GESTimelineElement * self,
+                                            GObject *child,
+                                            GParamSpec *pspec,
+                                            GValue *value);
+
+  /**
+   * GESTimelineElementClass::get_layer_priority:
+   *
+   * Get the #GESLayer:priority of the layer that this
+   * element is part of.
+   *
+   * Since: 1.16
+   */
   guint32      (*get_layer_priority)       (GESTimelineElement *self);
 
-  /*< private > */
+  /**
+   * GESTimelineElementClass::get_natural_framerate:
+   * @self: A #GESTimelineElement
+   * @framerate_n: The framerate numerator to retrieve
+   * @framerate_d: The framerate denominator to retrieve
+   *
+   * Returns: %TRUE if @self has a natural framerate @FALSE otherwise.
+   *
+   * Since: 1.18
+   */
+  gboolean (*get_natural_framerate) (GESTimelineElement * self, gint *framerate_n, gint *framerate_d);
+
+  /**
+   * GESTimelineElementClass::set_child_property_full:
+   *
+   * Similar to @set_child_property, except setting can fail, with the @error
+   * being optionally set. Default implementation will call @set_child_property
+   * and return %TRUE.
+   *
+   * Since: 1.18
+   */
+  gboolean     (*set_child_property_full)  (GESTimelineElement * self,
+                                            GObject *child,
+                                            GParamSpec *pspec,
+                                            const GValue *value,
+                                            GError ** error);
   /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING_LARGE - 4];
+  gpointer _ges_reserved[GES_PADDING_LARGE - 6];
 };
 
 GES_API
-GType ges_timeline_element_get_type (void) G_GNUC_CONST;
-
-GES_API GESTimelineElement *
-ges_timeline_element_get_toplevel_parent             (GESTimelineElement *self);
+GESTimelineElement * ges_timeline_element_get_toplevel_parent         (GESTimelineElement *self);
 GES_API
-GESTimelineElement * ges_timeline_element_get_parent (GESTimelineElement * self);
+GESTimelineElement * ges_timeline_element_get_parent                  (GESTimelineElement * self);
 GES_API
-gboolean ges_timeline_element_set_parent             (GESTimelineElement *self, GESTimelineElement *parent);
+gboolean             ges_timeline_element_set_parent                  (GESTimelineElement *self,
+                                                                       GESTimelineElement *parent);
 GES_API
-gboolean ges_timeline_element_set_timeline           (GESTimelineElement *self, GESTimeline *timeline);
+gboolean             ges_timeline_element_set_timeline                (GESTimelineElement *self,
+                                                                       GESTimeline *timeline);
 GES_API
-gboolean ges_timeline_element_set_start              (GESTimelineElement *self, GstClockTime start);
+gboolean             ges_timeline_element_set_start                   (GESTimelineElement *self,
+                                                                       GstClockTime start);
 GES_API
-gboolean ges_timeline_element_set_inpoint            (GESTimelineElement *self, GstClockTime inpoint);
+gboolean             ges_timeline_element_set_inpoint                 (GESTimelineElement *self,
+                                                                       GstClockTime inpoint);
 GES_API
-gboolean ges_timeline_element_set_duration           (GESTimelineElement *self, GstClockTime duration);
+gboolean             ges_timeline_element_set_duration                (GESTimelineElement *self,
+                                                                       GstClockTime duration);
 GES_API
-gboolean ges_timeline_element_set_max_duration       (GESTimelineElement *self, GstClockTime maxduration);
+gboolean             ges_timeline_element_set_max_duration            (GESTimelineElement *self,
+                                                                       GstClockTime maxduration);
+GES_DEPRECATED
+gboolean             ges_timeline_element_set_priority                (GESTimelineElement *self,
+                                                                       guint32 priority);
 GES_API
-gboolean ges_timeline_element_set_priority           (GESTimelineElement *self, guint32 priority);
-
+GstClockTime         ges_timeline_element_get_start                   (GESTimelineElement *self);
 GES_API
-GstClockTime ges_timeline_element_get_start          (GESTimelineElement *self);
+GstClockTime         ges_timeline_element_get_inpoint                 (GESTimelineElement *self);
 GES_API
-GstClockTime ges_timeline_element_get_inpoint        (GESTimelineElement *self);
+GstClockTime         ges_timeline_element_get_duration                (GESTimelineElement *self);
 GES_API
-GstClockTime ges_timeline_element_get_duration       (GESTimelineElement *self);
+GstClockTime         ges_timeline_element_get_max_duration            (GESTimelineElement *self);
 GES_API
-GstClockTime ges_timeline_element_get_max_duration   (GESTimelineElement *self);
+GESTimeline *        ges_timeline_element_get_timeline                (GESTimelineElement *self);
 GES_API
-GESTimeline * ges_timeline_element_get_timeline      (GESTimelineElement *self);
+guint32              ges_timeline_element_get_priority                (GESTimelineElement *self);
 GES_API
-guint32 ges_timeline_element_get_priority            (GESTimelineElement *self);
-
+gboolean             ges_timeline_element_ripple                      (GESTimelineElement *self,
+                                                                       GstClockTime  start);
 GES_API
-gboolean ges_timeline_element_ripple                 (GESTimelineElement *self, GstClockTime  start);
+gboolean             ges_timeline_element_ripple_end                  (GESTimelineElement *self,
+                                                                       GstClockTime  end);
 GES_API
-gboolean ges_timeline_element_ripple_end             (GESTimelineElement *self, GstClockTime  end);
+gboolean             ges_timeline_element_roll_start                  (GESTimelineElement *self,
+                                                                       GstClockTime  start);
 GES_API
-gboolean ges_timeline_element_roll_start             (GESTimelineElement *self, GstClockTime  start);
+gboolean             ges_timeline_element_roll_end                    (GESTimelineElement *self,
+                                                                       GstClockTime  end);
 GES_API
-gboolean ges_timeline_element_roll_end               (GESTimelineElement *self, GstClockTime  end);
+gboolean             ges_timeline_element_trim                        (GESTimelineElement *self,
+                                                                       GstClockTime  start);
 GES_API
-gboolean ges_timeline_element_trim                   (GESTimelineElement *self, GstClockTime  start);
+GESTimelineElement * ges_timeline_element_copy                        (GESTimelineElement *self,
+                                                                       gboolean deep);
 GES_API
-GESTimelineElement * ges_timeline_element_copy       (GESTimelineElement *self, gboolean deep);
+gchar  *             ges_timeline_element_get_name                    (GESTimelineElement *self);
 GES_API
-gchar  * ges_timeline_element_get_name               (GESTimelineElement *self);
+gboolean             ges_timeline_element_set_name                    (GESTimelineElement *self,
+                                                                       const gchar *name);
 GES_API
-gboolean  ges_timeline_element_set_name              (GESTimelineElement *self, const gchar *name);
-GES_API GParamSpec **
-ges_timeline_element_list_children_properties        (GESTimelineElement *self,
-                                                      guint *n_properties);
-
+GParamSpec **        ges_timeline_element_list_children_properties    (GESTimelineElement *self,
+                                                                       guint *n_properties);
 GES_API
-gboolean ges_timeline_element_lookup_child           (GESTimelineElement *self,
-                                                      const gchar *prop_name,
-                                                      GObject  **child,
-                                                      GParamSpec **pspec);
-
-GES_API void
-ges_timeline_element_get_child_property_by_pspec     (GESTimelineElement * self,
-                                                      GParamSpec * pspec,
-                                                      GValue * value);
-
-GES_API void
-ges_timeline_element_get_child_property_valist       (GESTimelineElement * self,
-                                                      const gchar * first_property_name,
-                                                      va_list var_args);
-
-GES_API void
-ges_timeline_element_get_child_properties           (GESTimelineElement *self,
-                                                      const gchar * first_property_name,
-                                                      ...) G_GNUC_NULL_TERMINATED;
-
-GES_API void
-ges_timeline_element_set_child_property_valist      (GESTimelineElement * self,
-                                                     const gchar * first_property_name,
-                                                     va_list var_args);
-
-GES_API void
-ges_timeline_element_set_child_property_by_pspec    (GESTimelineElement * self,
-                                                     GParamSpec * pspec,
-                                                     const GValue * value);
+gboolean             ges_timeline_element_lookup_child                (GESTimelineElement *self,
+                                                                       const gchar *prop_name,
+                                                                       GObject  **child,
+                                                                       GParamSpec **pspec);
 GES_API
-void ges_timeline_element_set_child_properties     (GESTimelineElement * self,
-                                                     const gchar * first_property_name,
-                                                     ...) G_GNUC_NULL_TERMINATED;
-
+void                 ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self,
+                                                                       GParamSpec * pspec, GValue * value);
 GES_API
-gboolean ges_timeline_element_set_child_property   (GESTimelineElement *self,
-                                                    const gchar *property_name,
-                                                    const GValue * value);
-
+void                 ges_timeline_element_get_child_property_valist   (GESTimelineElement * self,
+                                                                       const gchar * first_property_name,
+                                                                       va_list var_args);
 GES_API
-gboolean ges_timeline_element_get_child_property   (GESTimelineElement *self,
-                                                    const gchar *property_name,
-                                                    GValue * value);
-
+void                 ges_timeline_element_get_child_properties        (GESTimelineElement *self,
+                                                                       const gchar * first_property_name, ...) G_GNUC_NULL_TERMINATED;
 GES_API
-gboolean ges_timeline_element_add_child_property   (GESTimelineElement * self,
-                                                    GParamSpec *pspec,
-                                                    GObject *child);
-
+void                 ges_timeline_element_set_child_property_valist   (GESTimelineElement * self,
+                                                                       const gchar * first_property_name,
+                                                                       va_list var_args);
 GES_API
-gboolean ges_timeline_element_remove_child_property(GESTimelineElement * self,
-                                                    GParamSpec *pspec);
-
+void                 ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
+                                                                       GParamSpec * pspec,
+                                                                       const GValue * value);
+GES_API
+void                 ges_timeline_element_set_child_properties        (GESTimelineElement * self,
+                                                                       const gchar * first_property_name,
+                                                                       ...) G_GNUC_NULL_TERMINATED;
+GES_API
+gboolean             ges_timeline_element_set_child_property          (GESTimelineElement *self,
+                                                                       const gchar *property_name,
+                                                                       const GValue * value);
+GES_API
+gboolean             ges_timeline_element_set_child_property_full     (GESTimelineElement *self,
+                                                                       const gchar *property_name,
+                                                                       const GValue * value,
+                                                                       GError ** error);
+GES_API
+gboolean             ges_timeline_element_get_child_property          (GESTimelineElement *self,
+                                                                       const gchar *property_name,
+                                                                       GValue * value);
 GES_API
-GESTimelineElement * ges_timeline_element_paste    (GESTimelineElement * self,
-                                                    GstClockTime paste_position);
+gboolean             ges_timeline_element_add_child_property          (GESTimelineElement * self,
+                                                                       GParamSpec *pspec,
+                                                                       GObject *child);
+GES_API
+gboolean             ges_timeline_element_remove_child_property       (GESTimelineElement * self,
+                                                                       GParamSpec *pspec);
+GES_API
+GESTimelineElement * ges_timeline_element_paste                       (GESTimelineElement * self,
+                                                                       GstClockTime paste_position);
+GES_API
+GESTrackType         ges_timeline_element_get_track_types             (GESTimelineElement * self);
+GES_API
+gboolean             ges_timeline_element_get_natural_framerate       (GESTimelineElement *self,
+                                                                       gint *framerate_n,
+                                                                       gint *framerate_d);
 
 GES_API
-GESTrackType ges_timeline_element_get_track_types  (GESTimelineElement * self);
+guint32 ges_timeline_element_get_layer_priority                       (GESTimelineElement * self);
 
 GES_API
-guint32 ges_timeline_element_get_layer_priority    (GESTimelineElement * self);
+gboolean ges_timeline_element_edit                                    (GESTimelineElement * self,
+                                                                       GList * layers,
+                                                                       gint64 new_layer_priority,
+                                                                       GESEditMode mode,
+                                                                       GESEdge edge,
+                                                                       guint64 position);
 
+GES_API
+gboolean ges_timeline_element_edit_full                                (GESTimelineElement * self,
+                                                                       gint64 new_layer_priority,
+                                                                       GESEditMode mode,
+                                                                       GESEdge edge,
+                                                                       guint64 position,
+                                                                       GError ** error);
 G_END_DECLS
-
-#endif /* _GES_TIMELINE_ELEMENT_H_ */
index 6f875dc..ed44884 100644 (file)
 
 #include "ges-timeline-tree.h"
 #include "ges-internal.h"
+#include "ges-marker-list.h"
 
 GST_DEBUG_CATEGORY_STATIC (tree_debug);
 #undef GST_CAT_DEFAULT
 #define GST_CAT_DEFAULT tree_debug
 
-#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? ((GstClockTimeDiff) _END (e)) : ((GstClockTimeDiff) _START (e)))
-typedef struct
+#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? _END (e) : _START (e))
+
+typedef struct _SnappedPosition
 {
+  /* the element that was being snapped */
+  GESTrackElement *element;
+  /* the position of element, and whether it is a negative position */
+  gboolean negative;
+  GstClockTime position;
+  /* the element that was snapped to */
+  GESTrackElement *snapped_to;
+  /* the snapped positioned */
+  GstClockTime snapped;
+  /* the distance below which two elements can snap */
   GstClockTime distance;
-  gboolean on_end_only;
-  gboolean on_start_only;
+} SnappedPosition;
 
-  GESEdge edge;
-  GESTimelineElement *element;
+typedef enum
+{
+  EDIT_MOVE,
+  EDIT_TRIM_START,
+  EDIT_TRIM_END,
+  EDIT_TRIM_INPOINT_ONLY,
+} ElementEditMode;
 
-  GESTimelineElement *moving_element;
-  GESEdge moving_edge;
-  GstClockTimeDiff diff;
-} SnappingData;
+typedef struct _EditData
+{
+  /* offsets to use */
+  GstClockTime offset;
+  gint64 layer_offset;
+  /* actual values */
+  GstClockTime duration;
+  GstClockTime start;
+  GstClockTime inpoint;
+  guint32 layer_priority;
+  /* mode */
+  ElementEditMode mode;
+} EditData;
+
+typedef struct _PositionData
+{
+  guint32 layer_priority;
+  GstClockTime start;
+  GstClockTime end;
+} PositionData;
 
 /*  *INDENT-OFF* */
 struct _TreeIterationData
 {
   GNode *root;
   gboolean res;
-
-  GstClockTimeDiff start_diff;
-  GstClockTimeDiff inpoint_diff;
-  GstClockTimeDiff duration_diff;
-  gint64 priority_diff;
+  /* an error to set */
+  GError **error;
 
   /* The element we are visiting */
   GESTimelineElement *element;
+  /* the position data of the visited element */
+  PositionData *pos_data;
 
-  /* All the TrackElement currently moving */
-  GList *movings;
+  /* All the TrackElement currently moving: owned by data */
+  GHashTable *moving;
 
   /* Elements overlaping on the start/end of @element */
   GESTimelineElement *overlaping_on_start;
   GESTimelineElement *overlaping_on_end;
+  GstClockTime overlap_start_final_time;
+  GstClockTime overlap_end_first_time;
 
-  /* Timestamp after which elements will be rippled */
-  GstClockTime ripple_time;
-
-  SnappingData *snapping;
+  SnappedPosition *snap;
+  GList *sources;
+  GstClockTime position;
+  GstClockTime negative;
 
-  /* The edge being trimmed or rippled */
   GESEdge edge;
-  GHashTable *moved_clips;
-
   GList *neighbours;
 } tree_iteration_data_init = {
    .root = NULL,
    .res = TRUE,
-   .start_diff = 0,
-   .inpoint_diff = 0,
-   .duration_diff = 0,
-   .priority_diff = 0,
    .element = NULL,
-   .movings = NULL,
+   .pos_data = NULL,
+   .moving = NULL,
    .overlaping_on_start = NULL,
    .overlaping_on_end = NULL,
-   .ripple_time = GST_CLOCK_TIME_NONE,
-   .snapping = NULL,
+   .overlap_start_final_time = GST_CLOCK_TIME_NONE,
+   .overlap_end_first_time = GST_CLOCK_TIME_NONE,
+   .snap = NULL,
+   .sources = NULL,
+   .position = GST_CLOCK_TIME_NONE,
+   .negative = FALSE,
    .edge = GES_EDGE_NONE,
-   .moved_clips = NULL,
    .neighbours = NULL,
 };
 /*  *INDENT-ON* */
 
 typedef struct _TreeIterationData TreeIterationData;
 
-static void
-clean_iteration_data (TreeIterationData * data)
+static EditData *
+new_edit_data (ElementEditMode mode, GstClockTimeDiff offset,
+    gint64 layer_offset)
+{
+  EditData *data = g_new (EditData, 1);
+
+  data->start = GST_CLOCK_TIME_NONE;
+  data->duration = GST_CLOCK_TIME_NONE;
+  data->inpoint = GST_CLOCK_TIME_NONE;
+  data->layer_priority = GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY;
+
+  data->mode = mode;
+  data->offset = offset;
+  data->layer_offset = layer_offset;
+
+  return data;
+}
+
+static SnappedPosition *
+new_snapped_position (GstClockTime distance)
+{
+  SnappedPosition *snap;
+
+  if (distance == 0)
+    return NULL;
+
+  snap = g_new0 (SnappedPosition, 1);
+  snap->position = GST_CLOCK_TIME_NONE;
+  snap->snapped = GST_CLOCK_TIME_NONE;
+  snap->distance = distance;
+
+  return snap;
+}
+
+static GHashTable *
+new_edit_table ()
+{
+  return g_hash_table_new_full (NULL, NULL, NULL, g_free);
+}
+
+static GHashTable *
+new_position_table ()
 {
-  g_list_free (data->neighbours);
-  g_list_free (data->movings);
-  if (data->moved_clips)
-    g_hash_table_unref (data->moved_clips);
+  return g_hash_table_new_full (NULL, NULL, NULL, g_free);
 }
 
 void
@@ -117,11 +184,11 @@ static gboolean
 print_node (GNode * node, gpointer unused_data)
 {
   if (G_NODE_IS_ROOT (node)) {
-    g_print ("Timeline: %p\n", node->data);
+    gst_print ("Timeline: %p\n", node->data);
     return FALSE;
   }
 
-  g_print ("%*c- %" GES_FORMAT " - layer %" G_GINT32_FORMAT "\n",
+  gst_print ("%*c- %" GES_FORMAT " - layer %" G_GINT32_FORMAT "\n",
       2 * g_node_depth (node), ' ', GES_ARGS (node->data),
       ges_timeline_element_get_layer_priority (node->data));
 
@@ -135,28 +202,6 @@ timeline_tree_debug (GNode * root)
       (GNodeTraverseFunc) print_node, NULL);
 }
 
-static inline GESTimelineElement *
-get_toplevel_container (gpointer element)
-{
-  GESTimelineElement *ret =
-      ges_timeline_element_get_toplevel_parent ((GESTimelineElement
-          *) (element));
-
-  /*  We own a ref to the elements ourself */
-  gst_object_unref (ret);
-  return ret;
-}
-
-static gboolean
-timeline_tree_can_move_element_internal (GNode * root,
-    GESTimelineElement * element,
-    gint64 priority,
-    GstClockTimeDiff start,
-    GstClockTimeDiff inpoint,
-    GstClockTimeDiff duration,
-    GList * moving_track_elements,
-    GstClockTime ripple_time, SnappingData * snapping, GESEdge edge);
-
 static GNode *
 find_node (GNode * root, gpointer element)
 {
@@ -193,7 +238,7 @@ timeline_tree_track_element (GNode * root, GESTimelineElement * element)
   g_signal_connect (element, "notify::parent",
       G_CALLBACK (timeline_element_parent_cb), root);
 
-  toplevel = get_toplevel_container (element);
+  toplevel = ges_timeline_element_peak_toplevel (element);
   if (toplevel == element) {
     GST_DEBUG ("Tracking toplevel element %" GES_FORMAT, GES_ARGS (element));
 
@@ -247,676 +292,1782 @@ timeline_tree_stop_tracking_element (GNode * root, GESTimelineElement * element)
   timeline_update_duration (root->data);
 }
 
-static inline gboolean
-check_can_move_to_layer (GESTimelineElement * element,
-    gint layer_priority_offset)
+/****************************************************
+ *     GstClockTime with over/underflow checking    *
+ ****************************************************/
+
+static GstClockTime
+_clock_time_plus (GstClockTime time, GstClockTime add)
 {
-  return (((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
-          layer_priority_offset) >= 0);
-}
+  if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (add))
+    return GST_CLOCK_TIME_NONE;
 
-/*  *INDENT-OFF* */
-#define CHECK_AND_SNAP(diff_val,moving_edge_,edge_) \
-if (snapping->distance >= ABS(diff_val) && ABS(diff_val) <= ABS(snapping->diff)) { \
-  snapping->element = element; \
-  snapping->edge = edge_; \
-  snapping->moving_element = moving_elem; \
-  snapping->moving_edge = moving_edge_; \
-  snapping->diff = (diff_val); \
-  GST_LOG("Snapping %" GES_FORMAT "with %" GES_FORMAT " - diff: %" G_GINT64_FORMAT "", GES_ARGS (moving_elem), GES_ARGS(element), (diff_val)); \
+  if (time >= (G_MAXUINT64 - add)) {
+    GST_ERROR ("The time %" G_GUINT64_FORMAT " would overflow when "
+        "adding %" G_GUINT64_FORMAT, time, add);
+    return GST_CLOCK_TIME_NONE;
+  }
+  return time + add;
 }
 
-static void
-check_snapping (GESTimelineElement * element, GESTimelineElement * moving_elem,
-    SnappingData * snapping, GstClockTime start, GstClockTime end,
-    GstClockTime moving_start, GstClockTime moving_end)
+static GstClockTime
+_clock_time_minus (GstClockTime time, GstClockTime minus, gboolean * negative)
 {
-  GstClockTimeDiff snap_end_end_diff;
-  GstClockTimeDiff snap_end_start_diff;
-
-  if (element == moving_elem)
-    return;
+  if (negative)
+    *negative = FALSE;
 
-  if (!snapping || (
-      GES_IS_CLIP (element->parent) && element->parent == moving_elem->parent))
-    return;
-
-  snap_end_end_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) end;
-  snap_end_start_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) start;
-
-  GST_DEBUG("Moving [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "] element [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "]", moving_start, moving_end, start, end);
-  /* Prefer snapping end */
-  if (!snapping->on_start_only) {
-    CHECK_AND_SNAP(snap_end_end_diff, GES_EDGE_END, GES_EDGE_END)
-    else CHECK_AND_SNAP(snap_end_start_diff, GES_EDGE_END, GES_EDGE_START)
-  }
+  if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (minus))
+    return GST_CLOCK_TIME_NONE;
 
-  if (!snapping->on_end_only) {
-    GstClockTimeDiff snap_start_end_diff = GST_CLOCK_DIFF(end, moving_start);
-    GstClockTimeDiff snap_start_start_diff = GST_CLOCK_DIFF(start, moving_start);
-
-    CHECK_AND_SNAP(snap_start_end_diff, GES_EDGE_START, GES_EDGE_END)
-    else CHECK_AND_SNAP(snap_start_start_diff, GES_EDGE_START, GES_EDGE_START)
+  if (time < minus) {
+    if (negative) {
+      *negative = TRUE;
+      return minus - time;
+    }
+    /* otherwise don't allow negative */
+    GST_INFO ("The time %" G_GUINT64_FORMAT " would underflow when "
+        "subtracting %" G_GUINT64_FORMAT, time, minus);
+    return GST_CLOCK_TIME_NONE;
   }
+  return time - minus;
 }
-#undef CHECK_AND_SNAP
-/*  *INDENT-ON* */
 
-static gboolean
-check_track_elements_overlaps_and_values (GNode * node,
-    TreeIterationData * data)
+static GstClockTime
+_clock_time_minus_diff (GstClockTime time, GstClockTimeDiff diff,
+    gboolean * negative)
 {
-  GESTimelineElement *e = (GESTimelineElement *) node->data;
-  GstClockTimeDiff moving_start, moving_end, start, inpoint, end, duration;
-  gint64 priority = ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e));
-  gint64 moving_priority =
-      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (data->element) -
-      data->priority_diff;
+  if (negative)
+    *negative = FALSE;
 
-  gboolean can_overlap = e != data->element, in_movings, rippling, moving;
+  if (!GST_CLOCK_TIME_IS_VALID (time))
+    return GST_CLOCK_TIME_NONE;
 
-  if (!GES_IS_SOURCE (e))
-    return FALSE;
-
-  in_movings = ! !g_list_find (data->movings, e);
-  rippling = e != data->element && !in_movings
-      && (GST_CLOCK_TIME_IS_VALID (data->ripple_time)
-      && e->start >= data->ripple_time);
-  moving = in_movings || rippling || e == data->element;
-
-  start = (GstClockTimeDiff) e->start;
-  inpoint = (GstClockTimeDiff) e->inpoint;
-  end = (GstClockTimeDiff) e->start + e->duration;
-  duration = e->duration;
-  moving_start = GST_CLOCK_DIFF (data->start_diff, data->element->start);
-  moving_end =
-      GST_CLOCK_DIFF (data->duration_diff,
-      moving_start + data->element->duration);
-
-  if (moving) {
-    if (rippling) {
-      if (data->edge == GES_EDGE_END) {
-        /* Moving as rippled from the end of a previous element */
-        start -= data->duration_diff;
-      } else
-        start -= data->start_diff;
-    } else {
-      start -= data->start_diff;
-      if (GES_TIMELINE_ELEMENT_GET_CLASS (e)->set_inpoint)
-        inpoint -= data->inpoint_diff;
-      duration -= data->duration_diff;
-    }
-    end = start + duration;
-    priority -= data->priority_diff;
-
-    GST_DEBUG ("%s %" GES_FORMAT "to [%" G_GINT64_FORMAT "(%"
-        G_GINT64_FORMAT ") - %" G_GINT64_FORMAT "] - layer: %" G_GINT64_FORMAT,
-        rippling ? "Rippling" : "Moving", GES_ARGS (e), start, inpoint,
-        duration, priority);
-  }
+  if (diff < 0)
+    return _clock_time_plus (time, -diff);
+  else
+    return _clock_time_minus (time, diff, negative);
+}
 
-  /* Not in the same track */
-  if (ges_track_element_get_track ((GESTrackElement *) node->data) !=
-      ges_track_element_get_track ((GESTrackElement *) data->element)) {
-    GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT
-        " are not in the same track", GES_ARGS (node->data),
-        GES_ARGS (data->element));
-    can_overlap = FALSE;
-  }
+static GstClockTime
+_abs_clock_time_distance (GstClockTime time1, GstClockTime time2)
+{
+  if (!GST_CLOCK_TIME_IS_VALID (time1) || !GST_CLOCK_TIME_IS_VALID (time2))
+    return GST_CLOCK_TIME_NONE;
+  if (time1 > time2)
+    return time1 - time2;
+  else
+    return time2 - time1;
+}
 
-  /* Not in the same layer */
-  if (priority != moving_priority) {
-    GST_LOG ("%" GST_PTR_FORMAT " and %" GST_PTR_FORMAT
-        " are not on the same layer (%d != %" G_GINT64_FORMAT ")", node->data,
-        data->element, GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e),
-        moving_priority);
-    can_overlap = FALSE;
+static void
+get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
+    GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start,
+    GstClockTime * end, gboolean * negative_end)
+{
+  GstClockTime current_end =
+      _clock_time_plus (element->start, element->duration);
+  GstClockTime new_start = GST_CLOCK_TIME_NONE, new_end = GST_CLOCK_TIME_NONE;
+
+  switch (mode) {
+    case EDIT_MOVE:
+      new_start =
+          _clock_time_minus_diff (element->start, offset, negative_start);
+      new_end = _clock_time_minus_diff (current_end, offset, negative_end);
+      break;
+    case EDIT_TRIM_START:
+      new_start =
+          _clock_time_minus_diff (element->start, offset, negative_start);
+      new_end = current_end;
+      if (negative_end)
+        *negative_end = FALSE;
+      break;
+    case EDIT_TRIM_END:
+      new_start = element->start;
+      if (negative_start)
+        *negative_start = FALSE;
+      new_end = _clock_time_minus_diff (current_end, offset, negative_end);
+      break;
+    case EDIT_TRIM_INPOINT_ONLY:
+      GST_ERROR_OBJECT (element, "Trim in-point only not handled");
+      break;
   }
+  if (start)
+    *start = new_start;
+  if (end)
+    *end = new_end;
+}
 
-  if (start < 0) {
-    GST_INFO ("%" GES_FORMAT "start would be %" G_GINT64_FORMAT " < 0",
-        GES_ARGS (e), start);
-    goto error;
-  }
+/****************************************************
+ *                   Snapping                       *
+ ****************************************************/
 
-  if (duration < 0) {
-    GST_INFO ("%" GES_FORMAT "duration would be %" G_GINT64_FORMAT " < 0",
-        GES_ARGS (e), duration);
-    goto error;
-  }
+static void
+snap_to_marker (GESTrackElement * element, GstClockTime position,
+    gboolean negative, GstClockTime marker_timestamp,
+    GESTrackElement * marker_parent, SnappedPosition * snap)
+{
+  GstClockTime distance;
 
-  if (priority < 0) {
-    GST_INFO ("%" GES_FORMAT "priority would be %" G_GINT64_FORMAT " < 0",
-        GES_ARGS (e), priority);
-    goto error;
+  if (negative)
+    distance = _clock_time_plus (position, marker_timestamp);
+  else
+    distance = _abs_clock_time_distance (position, marker_timestamp);
+
+  if (GST_CLOCK_TIME_IS_VALID (distance) && distance <= snap->distance) {
+    snap->negative = negative;
+    snap->position = position;
+    snap->distance = distance;
+    snap->snapped = marker_timestamp;
+    snap->element = element;
+    snap->snapped_to = marker_parent;
   }
+}
 
-  if (inpoint < 0) {
-    GST_INFO ("%" GES_FORMAT " can't set inpoint %" G_GINT64_FORMAT,
-        GES_ARGS (e), inpoint);
-    goto error;
-  }
+static void
+snap_to_edge (GESTrackElement * element, GstClockTime position,
+    gboolean negative, GESTrackElement * snap_to, GESEdge edge,
+    SnappedPosition * snap)
+{
+  GstClockTime edge_pos = ELEMENT_EDGE_VALUE (snap_to, edge);
+  GstClockTime distance;
 
-  if (inpoint + duration > e->maxduration) {
-    GST_INFO ("%" GES_FORMAT " inpoint + duration %" G_GINT64_FORMAT
-        " > max_duration %" G_GINT64_FORMAT,
-        GES_ARGS (e), inpoint + duration, e->maxduration);
-    goto error;
+  if (negative)
+    distance = _clock_time_plus (position, edge_pos);
+  else
+    distance = _abs_clock_time_distance (position, edge_pos);
+
+  if (GST_CLOCK_TIME_IS_VALID (distance) && distance <= snap->distance) {
+    GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (element);
+    GESTimelineElement *snap_parent = GES_TIMELINE_ELEMENT_PARENT (snap_to);
+    GST_LOG_OBJECT (element, "%s (under %s) snapped with %" GES_FORMAT
+        "(under %s) from position %s%" GST_TIME_FORMAT " to %"
+        GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME (element),
+        parent ? parent->name : NULL, GES_ARGS (snap_to),
+        snap_parent ? snap_parent->name : NULL, negative ? "-" : "",
+        GST_TIME_ARGS (position), GST_TIME_ARGS (edge_pos));
+    snap->negative = negative;
+    snap->position = position;
+    snap->distance = distance;
+    snap->snapped = edge_pos;
+    snap->element = element;
+    snap->snapped_to = snap_to;
   }
+}
 
-  if (!moving)
-    check_snapping (e, data->element, data->snapping, start, end, moving_start,
-        moving_end);
-
-  if (!can_overlap)
-    return FALSE;
+static void
+find_marker_snap (const GESMetaContainer * container, const gchar * key,
+    const GValue * value, TreeIterationData * data)
+{
+  GESTrackElement *marker_parent, *moving;
+  GESClip *parent_clip;
+  GstClockTime timestamp;
+  GESMarkerList *marker_list;
+  GESMarker *marker;
+  GESMarkerFlags flags;
+  GObject *obj;
+
+  if (!G_VALUE_HOLDS_OBJECT (value))
+    return;
 
-  if (start > moving_end || moving_start > end) {
-    /* They do not overlap at all */
-    GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT
-        " do not overlap at all.",
-        GES_ARGS (node->data), GES_ARGS (data->element));
-    return FALSE;
-  }
+  obj = g_value_get_object (value);
+  if (!GES_IS_MARKER_LIST (obj))
+    return;
 
-  if ((moving_start <= start && moving_end >= end) ||
-      (moving_start >= start && moving_end <= end)) {
-    GST_INFO ("Fully overlaped: %s<%p> [%" G_GINT64_FORMAT " - %"
-        G_GINT64_FORMAT "] and %s<%p> [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT
-        " (%" G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
-        data->element, moving_start, moving_end, data->duration_diff);
+  marker_list = GES_MARKER_LIST (obj);
 
-    goto error;
-  }
+  g_object_get (marker_list, "flags", &flags, NULL);
+  if (!(flags & GES_MARKER_FLAG_SNAPPABLE))
+    return;
 
-  if (moving_start < end && moving_start > start) {
-    GST_LOG ("Overlap start: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT
-        "] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%"
-        G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
-        data->element, moving_start, moving_end, data->duration_diff);
-    if (data->overlaping_on_start) {
-      GST_INFO ("Clip is overlapped by %s and %s at its start",
-          data->overlaping_on_start->name, e->name);
-      goto error;
-    }
+  marker_parent = GES_TRACK_ELEMENT ((gpointer) container);
+  moving = GES_TRACK_ELEMENT (data->element);
+  parent_clip = (GESClip *) GES_TIMELINE_ELEMENT_PARENT (marker_parent);
 
-    data->overlaping_on_start = node->data;
-  } else if (moving_end > end && end > moving_start) {
-    GST_LOG ("Overlap end: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT
-        "] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%"
-        G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
-        data->element, moving_start, moving_end, data->duration_diff);
+  /* Translate current position into the target clip's time domain */
+  timestamp =
+      ges_clip_get_internal_time_from_timeline_time (parent_clip, marker_parent,
+      data->position, NULL);
+  marker = ges_marker_list_get_closest (marker_list, timestamp);
 
-    if (data->overlaping_on_end) {
-      GST_INFO ("Clip is overlapped by %s and %s at its end",
-          data->overlaping_on_end->name, e->name);
-      goto error;
-    }
-    data->overlaping_on_end = node->data;
-  }
+  if (marker == NULL)
+    return;
 
-  return FALSE;
+  /* Make timestamp timeline-relative again */
+  g_object_get (marker, "position", &timestamp, NULL);
+  timestamp =
+      ges_clip_get_timeline_time_from_internal_time (parent_clip, marker_parent,
+      timestamp, NULL);
+  snap_to_marker (moving, data->position, data->negative, timestamp,
+      marker_parent, data->snap);
 
-error:
-  data->res = FALSE;
-  return TRUE;
+  g_object_unref (marker);
 }
 
 static gboolean
-check_can_move_children (GNode * node, TreeIterationData * data)
+find_snap (GNode * node, TreeIterationData * data)
 {
   GESTimelineElement *element = node->data;
-  GstClockTimeDiff start = GST_CLOCK_DIFF (data->start_diff, element->start);
-  GstClockTime inpoint = GST_CLOCK_DIFF (data->inpoint_diff, element->inpoint);
-  GstClockTime duration =
-      GST_CLOCK_DIFF (data->duration_diff, element->duration);
-  gint64 priority =
-      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
-      data->priority_diff;
-  if (element == data->element)
+  GESTrackElement *track_el, *moving;
+
+  /* Only snap to sources */
+  /* Maybe we should allow snapping to anything that isn't an
+   * auto-transition? */
+  if (!GES_IS_SOURCE (element))
+    return FALSE;
+
+  /* don't snap to anything we are moving */
+  if (g_hash_table_contains (data->moving, element))
     return FALSE;
 
-  data->res =
-      timeline_tree_can_move_element_internal (data->root, node->data, priority,
-      start, inpoint, duration, data->movings, data->ripple_time,
-      data->snapping, data->edge);
+  track_el = GES_TRACK_ELEMENT (element);
+  moving = GES_TRACK_ELEMENT (data->element);
+  snap_to_edge (moving, data->position, data->negative, track_el,
+      GES_EDGE_END, data->snap);
+  snap_to_edge (moving, data->position, data->negative, track_el,
+      GES_EDGE_START, data->snap);
 
-  return !data->res;
+  ges_meta_container_foreach (GES_META_CONTAINER (element),
+      (GESMetaForeachFunc) find_marker_snap, data);
+
+  return FALSE;
 }
 
-static gboolean
-timeline_tree_can_move_element_from_data (GNode * root,
-    TreeIterationData * data)
+static void
+find_snap_for_element (GESTrackElement * element, GstClockTime position,
+    gboolean negative, TreeIterationData * data)
 {
-  GNode *node = find_node (root, data->element);
+  data->element = GES_TIMELINE_ELEMENT (element);
+  data->position = position;
+  data->negative = negative;
+  g_node_traverse (data->root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+      (GNodeTraverseFunc) find_snap, data);
+}
 
-  g_assert (node);
-  if (G_NODE_IS_LEAF (node)) {
-    if (GES_IS_SOURCE (node->data)) {
-      g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
-          (GNodeTraverseFunc) check_track_elements_overlaps_and_values, data);
+/* find up to one source at the edge */
+static gboolean
+find_source_at_edge (GNode * node, TreeIterationData * data)
+{
+  GESEdge edge = data->edge;
+  GESTimelineElement *element = node->data;
+  GESTimelineElement *ancestor = data->element;
 
-      return data->res;
-    }
+  if (!GES_IS_SOURCE (element))
+    return FALSE;
 
+  if (ELEMENT_EDGE_VALUE (element, edge) == ELEMENT_EDGE_VALUE (ancestor, edge)) {
+    data->sources = g_list_append (data->sources, element);
     return TRUE;
   }
-
-  g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAFS, -1,
-      (GNodeTraverseFunc) check_can_move_children, data);
-
-  return data->res;
+  return FALSE;
 }
 
 static gboolean
-add_element_to_list (GNode * node, GList ** elements)
+find_sources (GNode * node, TreeIterationData * data)
 {
-  *elements = g_list_prepend (*elements, node->data);
-
+  GESTimelineElement *element = node->data;
+  if (GES_IS_SOURCE (element))
+    data->sources = g_list_append (data->sources, element);
   return FALSE;
 }
 
+/* Tries to find a new snap to the start or end edge of one of the
+ * descendant sources of @element, depending on @mode, and updates @offset
+ * by the size of the jump.
+ * Any elements in @moving are not snapped to.
+ */
 static gboolean
-timeline_tree_can_move_element_internal (GNode * root,
-    GESTimelineElement * element, gint64 priority, GstClockTimeDiff start,
-    GstClockTimeDiff inpoint, GstClockTimeDiff duration,
-    GList * moving_track_elements, GstClockTime ripple_time,
-    SnappingData * snapping, GESEdge edge)
+timeline_tree_snap (GNode * root, GESTimelineElement * element,
+    ElementEditMode mode, GstClockTimeDiff * offset, GHashTable * moving,
+    SnappedPosition * snap)
 {
-  gboolean res;
+  gboolean ret = FALSE;
   TreeIterationData data = tree_iteration_data_init;
+  GList *tmp;
+  GNode *node;
+
+  if (!snap)
+    return TRUE;
 
+  /* get the sources we can snap to */
   data.root = root;
-  data.start_diff = GST_CLOCK_DIFF (start, element->start);
-  data.inpoint_diff = GST_CLOCK_DIFF (inpoint, element->inpoint);
-  data.duration_diff = GST_CLOCK_DIFF (duration, element->duration);
-  data.priority_diff =
-      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority;
+  data.moving = moving;
+  data.sources = NULL;
+  data.snap = snap;
   data.element = element;
-  data.movings = g_list_copy (moving_track_elements);
-  data.ripple_time = ripple_time;
-  data.snapping = snapping;
-  data.edge = edge;
-
-  if (GES_IS_SOURCE (element))
-    data.priority_diff =
-        GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority;
 
-  res = timeline_tree_can_move_element_from_data (root, &data);
-  clean_iteration_data (&data);
+  node = find_node (root, element);
 
-  return res;
-}
+  if (!node) {
+    GST_ERROR_OBJECT (element, "Not being tracked");
+    goto done;
+  }
 
-gboolean
-timeline_tree_can_move_element (GNode * root,
-    GESTimelineElement * element, guint32 priority, GstClockTime start,
-    GstClockTime duration, GList * moving_track_elements)
-{
-  GESTimelineElement *toplevel;
-  GstClockTimeDiff start_offset, duration_offset;
-  gint64 priority_diff;
-  gboolean res;
-  GList *local_moving_track_elements = g_list_copy (moving_track_elements);
+  switch (mode) {
+    case EDIT_MOVE:
+      /* can snap with any source below the element, if any */
+      g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+          (GNodeTraverseFunc) find_sources, &data);
+      break;
+    case EDIT_TRIM_START:
+      /* can only snap with sources at the start of the element.
+       * only need one such source since all will share the same start.
+       * if there is no source at the start edge, then snapping is not
+       * possible */
+      data.edge = GES_EDGE_START;
+      g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+          (GNodeTraverseFunc) find_source_at_edge, &data);
+      break;
+    case EDIT_TRIM_END:
+      /* can only snap with sources at the end of the element.
+       * only need one such source since all will share the same end.
+       * if there is no source at the end edge, then snapping is not
+       * possible */
+      data.edge = GES_EDGE_END;
+      g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+          (GNodeTraverseFunc) find_source_at_edge, &data);
+      break;
+    case EDIT_TRIM_INPOINT_ONLY:
+      GST_ERROR_OBJECT (element, "Trim in-point only not handled");
+      goto done;
+  }
 
-  toplevel = get_toplevel_container (element);
-  if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
-      ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE))
-    return TRUE;
+  for (tmp = data.sources; tmp; tmp = tmp->next) {
+    GESTrackElement *source = tmp->data;
+    GstClockTime end, start;
+    gboolean negative_end, negative_start;
+
+    /* Allow negative start/end positions in case a snap makes them valid!
+     * But we can still only snap to an existing edge in the timeline,
+     * which should be a valid time */
+    get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode, *offset,
+        &start, &negative_start, &end, &negative_end);
+
+    if (!GST_CLOCK_TIME_IS_VALID (start)) {
+      GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT
+          " with offset %" G_GINT64_FORMAT " because it would result in "
+          "an invalid start", GES_ARGS (element), *offset);
+      goto done;
+    }
 
-  start_offset = GST_CLOCK_DIFF (start, element->start);
-  duration_offset = GST_CLOCK_DIFF (duration, element->duration);
-  priority_diff =
-      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) -
-      (gint64) priority;
+    if (!GST_CLOCK_TIME_IS_VALID (end)) {
+      GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT
+          " with offset %" G_GINT64_FORMAT " because it would result in "
+          "an invalid end", GES_ARGS (element), *offset);
+      goto done;
+    }
 
-  g_node_traverse (find_node (root, toplevel), G_IN_ORDER,
-      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
-      &local_moving_track_elements);
+    switch (mode) {
+      case EDIT_MOVE:
+        /* try snap start and end */
+        find_snap_for_element (source, end, negative_end, &data);
+        find_snap_for_element (source, start, negative_start, &data);
+        break;
+      case EDIT_TRIM_START:
+        /* only snap the start of the source */
+        find_snap_for_element (source, start, negative_start, &data);
+        break;
+      case EDIT_TRIM_END:
+        /* only snap the start of the source */
+        find_snap_for_element (source, end, negative_end, &data);
+        break;
+      case EDIT_TRIM_INPOINT_ONLY:
+        GST_ERROR_OBJECT (element, "Trim in-point only not handled");
+        goto done;
+    }
+  }
 
-  res = timeline_tree_can_move_element_internal (root, toplevel,
-      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) - priority_diff,
-      GST_CLOCK_DIFF (start_offset, toplevel->start),
-      toplevel->inpoint,
-      GST_CLOCK_DIFF (duration_offset, toplevel->duration),
-      local_moving_track_elements, GST_CLOCK_TIME_NONE, NULL, GES_EDGE_NONE);
+  if (GST_CLOCK_TIME_IS_VALID (snap->snapped)) {
+    if (snap->negative)
+      *offset -= (snap->position + snap->snapped);
+    else
+      *offset += (snap->position - snap->snapped);
+    GST_INFO_OBJECT (element, "Element %s under %s snapped with %" GES_FORMAT
+        " from %s%" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+        GES_TIMELINE_ELEMENT_NAME (snap->element), element->name,
+        GES_ARGS (snap->snapped_to), snap->negative ? "-" : "",
+        GST_TIME_ARGS (snap->position), GST_TIME_ARGS (snap->snapped));
+  } else {
+    GST_INFO_OBJECT (element, "Nothing within snapping distance of %s",
+        element->name);
+  }
 
-  g_list_free (local_moving_track_elements);
-  return res;
-}
+  ret = TRUE;
 
-static void
-move_to_new_layer (GESTimelineElement * elem, gint layer_priority_offset)
-{
-  guint32 nprio =
-      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) - layer_priority_offset;
-  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (elem);
+done:
+  g_list_free (data.sources);
 
-  if (!layer_priority_offset)
-    return;
+  return ret;
+}
 
-  GST_DEBUG ("%s moving from %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " (%"
-      G_GUINT32_FORMAT ")", elem->name, elem->priority, nprio,
-      layer_priority_offset);
-  if (GES_IS_CLIP (elem)) {
-    GESLayer *layer = ges_timeline_get_layer (timeline, nprio);
+/****************************************************
+ *                 Check Overlaps                   *
+ ****************************************************/
 
-    if (layer == NULL) {
-      do {
-        layer = ges_timeline_append_layer (timeline);
-      } while (ges_layer_get_priority (layer) < nprio);
-    } else {
-      gst_object_unref (layer);
-    }
+#define _SOURCE_FORMAT "\"%s\"%s%s%s"
+#define _SOURCE_ARGS(element) \
+  element->name, element->parent ? " (parent: \"" : "", \
+  element->parent ? element->parent->name : "", \
+  element->parent ? "\")" : ""
 
-    ges_clip_move_to_layer (GES_CLIP (elem), layer);
-  } else if (GES_IS_GROUP (elem)) {
-    ges_timeline_element_set_priority (elem, nprio);
-  } else {
-    g_assert_not_reached ();
+static void
+set_full_overlap_error (GError ** error, GESTimelineElement * super,
+    GESTimelineElement * sub, GESTrack * track)
+{
+  if (error) {
+    gchar *track_name = gst_object_get_name (GST_OBJECT (track));
+    g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK,
+        "The source " _SOURCE_FORMAT " would totally overlap the "
+        "source " _SOURCE_FORMAT " in the track \"%s\"", _SOURCE_ARGS (super),
+        _SOURCE_ARGS (sub), track_name);
+    g_free (track_name);
   }
 }
 
-gboolean
-timeline_tree_ripple (GNode * root, gint64 layer_priority_offset,
-    GstClockTimeDiff offset, GESTimelineElement * rippled_element,
-    GESEdge edge, GstClockTime snapping_distance)
+static void
+set_triple_overlap_error (GError ** error, GESTimelineElement * first,
+    GESTimelineElement * second, GESTimelineElement * third, GESTrack * track)
 {
-  GNode *node;
-  GHashTableIter iter;
-  GESTimelineElement *elem;
-  GstClockTimeDiff start, duration;
-  gboolean res = TRUE;
-  GHashTable *to_move = g_hash_table_new (g_direct_hash, g_direct_equal);
-  GList *moving_track_elements = NULL;
-  SnappingData snapping = {
-    .distance = snapping_distance,
-    .on_end_only = edge == GES_EDGE_END,
-    .on_start_only = FALSE,
-    .element = NULL,
-    .edge = GES_EDGE_NONE,
-    .diff = (GstClockTimeDiff) snapping_distance,
-  };
-  gint64 new_layer_priority =
-      ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (rippled_element)) -
-      layer_priority_offset;
-  GESTimelineElement *ripple_toplevel =
-      get_toplevel_container (rippled_element);
-  GstClockTimeDiff ripple_time = ELEMENT_EDGE_VALUE (rippled_element, edge);
-
-  if (edge == GES_EDGE_END) {
-    if (ripple_toplevel != rippled_element) {
-      GST_FIXME ("Trying to ripple end %" GES_FORMAT " but in %" GES_FORMAT
-          " we do not know how to do that yet!",
-          GES_ARGS (rippled_element), GES_ARGS (ripple_toplevel));
-      goto error;
-    }
-  } else {
-    g_node_traverse (find_node (root, ripple_toplevel), G_IN_ORDER,
-        G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
-        &moving_track_elements);
+  if (error) {
+    gchar *track_name = gst_object_get_name (GST_OBJECT (track));
+    g_set_error (error, GES_ERROR, GES_ERROR_INVALID_OVERLAP_IN_TRACK,
+        "The sources " _SOURCE_FORMAT ", " _SOURCE_FORMAT " and "
+        _SOURCE_FORMAT " would all overlap at the same point in the "
+        "track \"%s\"", _SOURCE_ARGS (first), _SOURCE_ARGS (second),
+        _SOURCE_ARGS (third), track_name);
+    g_free (track_name);
   }
+}
+
+#define _ELEMENT_FORMAT \
+  "%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \
+  "(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")"
+#define _E_ARGS e->name, e->parent ? e->parent->name : NULL, \
+  GST_TIME_ARGS (start), GST_TIME_ARGS (end), layer_prio, track
+#define _CMP_ARGS cmp->name, cmp->parent ? cmp->parent->name : NULL, \
+  GST_TIME_ARGS (cmp_start), GST_TIME_ARGS (cmp_end), cmp_layer_prio, \
+  cmp_track
+
+static gboolean
+check_overlap_with_element (GNode * node, TreeIterationData * data)
+{
+  GESTimelineElement *e = node->data, *cmp = data->element;
+  GstClockTime start, end, cmp_start, cmp_end;
+  guint32 layer_prio, cmp_layer_prio;
+  GESTrack *track, *cmp_track;
+  PositionData *pos_data;
+
+  if (e == cmp)
+    return FALSE;
 
-  GST_INFO ("Moving %" GES_FORMAT " with offset %" G_GINT64_FORMAT "",
-      GES_ARGS (ripple_toplevel), offset);
+  if (!GES_IS_SOURCE (e) || !GES_IS_SOURCE (cmp))
+    return FALSE;
 
-  if (edge == GES_EDGE_END) {
-    start = _START (rippled_element);
-    duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element));
+  /* get position of compared element */
+  pos_data = data->pos_data;
+  if (pos_data) {
+    cmp_start = pos_data->start;
+    cmp_end = pos_data->end;
+    cmp_layer_prio = pos_data->layer_priority;
   } else {
-    start = GST_CLOCK_DIFF (offset, _START (rippled_element));
-    duration = _DURATION (rippled_element);
+    cmp_start = cmp->start;
+    cmp_end = cmp_start + cmp->duration;
+    cmp_layer_prio = ges_timeline_element_get_layer_priority (cmp);
   }
 
-  if (!timeline_tree_can_move_element_internal (root, rippled_element,
-          new_layer_priority, start, rippled_element->inpoint, duration, NULL,
-          ripple_time, snapping_distance ? &snapping : NULL, edge)) {
-    goto error;
+  /* get position of the node */
+  if (data->moving)
+    pos_data = g_hash_table_lookup (data->moving, e);
+  else
+    pos_data = NULL;
+
+  if (pos_data) {
+    start = pos_data->start;
+    end = pos_data->end;
+    layer_prio = pos_data->layer_priority;
+  } else {
+    start = e->start;
+    end = start + e->duration;
+    layer_prio = ges_timeline_element_get_layer_priority (e);
   }
 
-  if (snapping_distance) {
-    if (snapping.element) {
-      offset =
-          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
-          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+  track = ges_track_element_get_track (GES_TRACK_ELEMENT (e));
+  cmp_track = ges_track_element_get_track (GES_TRACK_ELEMENT (cmp));
+  GST_LOG ("Checking overlap between " _ELEMENT_FORMAT " and "
+      _ELEMENT_FORMAT, _CMP_ARGS, _E_ARGS);
 
-      if (edge == GES_EDGE_END) {
-        start = _START (rippled_element);
-        duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element));
-      } else {
-        start = GST_CLOCK_DIFF (offset, _START (rippled_element));
-        duration = _DURATION (rippled_element);
-      }
+  if (track != cmp_track || track == NULL || cmp_track == NULL) {
+    GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " are not in the "
+        "same track", _CMP_ARGS, _E_ARGS);
+    return FALSE;
+  }
 
-      GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT "",
-          GES_ARGS (snapping.element),
-          snapping.edge == GES_EDGE_END ? "end" : "start",
-          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge));
-      if (!timeline_tree_can_move_element_internal (root, rippled_element,
-              new_layer_priority, start, rippled_element->inpoint, duration,
-              NULL, ripple_time, NULL, edge)) {
-        goto error;
-      }
-    }
+  if (layer_prio != cmp_layer_prio) {
+    GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " are not in the "
+        "same layer", _CMP_ARGS, _E_ARGS);
+    return FALSE;
+  }
 
-    ges_timeline_emit_snapping (root->data, rippled_element, snapping.element,
-        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
-            snapping.edge) : GST_CLOCK_TIME_NONE);
+  if (start >= cmp_end || cmp_start >= end) {
+    /* They do not overlap at all */
+    GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " do not overlap",
+        _CMP_ARGS, _E_ARGS);
+    return FALSE;
   }
 
-  /* Make sure we can ripple all toplevels after the rippled element */
-  for (node = root->children; node; node = node->next) {
-    GESTimelineElement *toplevel = get_toplevel_container (node->data);
+  if (cmp_start <= start && cmp_end >= end) {
+    /* cmp fully overlaps e */
+    GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap",
+        _CMP_ARGS, _E_ARGS);
+    set_full_overlap_error (data->error, cmp, e, track);
+    goto error;
+  }
 
-    if (GES_TIMELINE_ELEMENT_START (toplevel) < ripple_time
-        && (edge == GES_EDGE_END || toplevel != ripple_toplevel))
-      continue;
+  if (cmp_start >= start && cmp_end <= end) {
+    /* e fully overlaps cmp */
+    GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap",
+        _CMP_ARGS, _E_ARGS);
+    set_full_overlap_error (data->error, e, cmp, track);
+    goto error;
+  }
 
-    if (!timeline_tree_can_move_element_internal (root, node->data,
-            ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data)) -
-            layer_priority_offset,
-            GST_CLOCK_DIFF (offset, _START (node->data)),
-            _INPOINT (node->data),
-            _DURATION (node->data), moving_track_elements, ripple_time, NULL,
-            GES_EDGE_NONE)) {
+  if (cmp_start < end && cmp_start > start) {
+    /* cmp_start is between the start and end of the node */
+    GST_LOG (_ELEMENT_FORMAT " is overlapped at its start by "
+        _ELEMENT_FORMAT ". Overlap ends at %" GST_TIME_FORMAT,
+        _CMP_ARGS, _E_ARGS, GST_TIME_ARGS (end));
+    if (data->overlaping_on_start) {
+      GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start",
+          _CMP_ARGS, data->overlaping_on_start->name, e->name);
+      set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start,
+          track);
       goto error;
     }
-
-    if (!check_can_move_to_layer (toplevel, layer_priority_offset)) {
-      GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
-          GES_ARGS (toplevel));
+    if (GST_CLOCK_TIME_IS_VALID (data->overlap_end_first_time) &&
+        end > data->overlap_end_first_time) {
+      GST_INFO (_ELEMENT_FORMAT " overlaps %s on its start and %s on its "
+          "end, but they already overlap each other", _CMP_ARGS, e->name,
+          data->overlaping_on_end->name);
+      set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end,
+          track);
       goto error;
     }
-
-    g_hash_table_add (to_move, toplevel);
+    /* record the time at which the overlapped ends */
+    data->overlap_start_final_time = end;
+    data->overlaping_on_start = e;
   }
 
-  if (edge == GES_EDGE_END) {
-    if (!check_can_move_to_layer (rippled_element, layer_priority_offset)) {
-      GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
-          GES_ARGS (rippled_element));
+  if (cmp_end < end && cmp_end > start) {
+    /* cmp_end is between the start and end of the node */
+    GST_LOG (_ELEMENT_FORMAT " is overlapped at its end by "
+        _ELEMENT_FORMAT ". Overlap starts at %" GST_TIME_FORMAT,
+        _CMP_ARGS, _E_ARGS, GST_TIME_ARGS (start));
 
+    if (data->overlaping_on_end) {
+      GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end",
+          _CMP_ARGS, data->overlaping_on_end->name, e->name);
+      set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_end,
+          track);
       goto error;
     }
-
-    if (duration < 0) {
-      GST_INFO ("Would set duration to  %" G_GINT64_FORMAT " <= 0", duration);
+    if (GST_CLOCK_TIME_IS_VALID (data->overlap_start_final_time) &&
+        start < data->overlap_start_final_time) {
+      GST_INFO (_ELEMENT_FORMAT " overlaps %s on its end and %s on its "
+          "start, but they already overlap each other", _CMP_ARGS, e->name,
+          data->overlaping_on_start->name);
+      set_triple_overlap_error (data->error, cmp, e, data->overlaping_on_start,
+          track);
       goto error;
     }
-
-    ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-    ges_timeline_element_set_duration (rippled_element, duration);
-    ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  }
-
-  g_hash_table_iter_init (&iter, to_move);
-  while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL)) {
-    GST_LOG ("Moving %" GES_FORMAT " to %" G_GINT64_FORMAT " - layer %"
-        G_GINT64_FORMAT "", GES_ARGS (elem),
-        GES_TIMELINE_ELEMENT_START (elem) - offset,
-        (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) -
-        layer_priority_offset);
-
-    ELEMENT_SET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-    ges_timeline_element_set_start (elem,
-        GST_CLOCK_DIFF (offset, GES_TIMELINE_ELEMENT_START (elem)));
-    move_to_new_layer (elem, layer_priority_offset);
-    ELEMENT_UNSET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+    /* record the time at which the overlapped starts */
+    data->overlap_end_first_time = start;
+    data->overlaping_on_end = e;
   }
 
-  ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  if (edge == GES_EDGE_END)
-    move_to_new_layer (rippled_element, layer_priority_offset);
-  ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-
-  timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
-  timeline_update_transition (root->data);
-  timeline_update_duration (root->data);
-
-done:
-  g_hash_table_unref (to_move);
-  g_list_free (moving_track_elements);
-  return res;
+  return FALSE;
 
 error:
-  res = FALSE;
-  goto done;
+  data->res = FALSE;
+  return TRUE;
 }
 
+/* check and find the overlaps with the element at node */
 static gboolean
-check_trim_child (GNode * node, TreeIterationData * data)
-{
-  GESTimelineElement *e = node->data;
-  GstClockTimeDiff n_start = GST_CLOCK_DIFF (data->start_diff, e->start);
-  GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (data->inpoint_diff, e->inpoint);
-  GstClockTimeDiff n_duration = data->edge == GES_EDGE_END ?
-      GST_CLOCK_DIFF (data->duration_diff, e->duration) :
-      GST_CLOCK_DIFF (n_start, (GstClockTimeDiff) e->start + e->duration);
-
-  if (!timeline_tree_can_move_element_internal (data->root, e,
-          (gint64) ges_timeline_element_get_layer_priority (e) -
-          data->priority_diff, n_start, n_inpoint, n_duration, NULL,
-          GST_CLOCK_TIME_NONE, data->snapping, GES_EDGE_NONE))
-    goto error;
-
-  if (GES_IS_CLIP (e->parent))
-    g_hash_table_add (data->moved_clips, e->parent);
-  else if (GES_IS_CLIP (e))
-    g_hash_table_add (data->moved_clips, e);
-
+check_all_overlaps_with_element (GNode * node, TreeIterationData * data)
+{
+  GESTimelineElement *element = node->data;
+  if (GES_IS_SOURCE (element)) {
+    data->element = element;
+    data->overlaping_on_start = NULL;
+    data->overlaping_on_end = NULL;
+    data->overlap_start_final_time = GST_CLOCK_TIME_NONE;
+    data->overlap_end_first_time = GST_CLOCK_TIME_NONE;
+    if (data->moving)
+      data->pos_data = g_hash_table_lookup (data->moving, element);
+    else
+      data->pos_data = NULL;
+
+    g_node_traverse (data->root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+        (GNodeTraverseFunc) check_overlap_with_element, data);
+
+    return !data->res;
+  }
   return FALSE;
-
-error:
-  data->res = FALSE;
-
-  return TRUE;
 }
 
 static gboolean
-timeline_tree_can_trim_element_internal (GNode * root, TreeIterationData * data)
+check_moving_overlaps (GNode * node, TreeIterationData * data)
 {
-  g_node_traverse (find_node (root, data->element), G_IN_ORDER,
-      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) check_trim_child, data);
+  if (g_hash_table_contains (data->moving, node->data))
+    return check_all_overlaps_with_element (node, data);
+  return FALSE;
+}
 
-  return data->res;
+/* whether the elements in moving can be moved to their corresponding
+ * PositionData */
+static gboolean
+timeline_tree_can_move_elements (GNode * root, GHashTable * moving,
+    GError ** error)
+{
+  TreeIterationData data = tree_iteration_data_init;
+  data.moving = moving;
+  data.root = root;
+  data.res = TRUE;
+  data.error = error;
+  /* sufficient to check the leaves, which is all the track elements or
+   * empty clips
+   * should also be sufficient to only check the moving elements */
+  g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+      (GNodeTraverseFunc) check_moving_overlaps, &data);
+
+  return data.res;
 }
 
+/****************************************************
+ *               Setting Edit Data                  *
+ ****************************************************/
+
 static void
-trim_simple (GESTimelineElement * element, GstClockTimeDiff offset,
-    GESEdge edge)
+set_negative_start_error (GError ** error, GESTimelineElement * element,
+    GstClockTime neg_start)
 {
-  ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  if (edge == GES_EDGE_END) {
-    ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
-            element->duration));
-  } else {
-    ges_timeline_element_set_start (element, GST_CLOCK_DIFF (offset,
-            element->start));
-    ges_timeline_element_set_inpoint (element, GST_CLOCK_DIFF (offset,
-            element->inpoint));
-    ges_timeline_element_set_duration (element, element->duration + offset);
-  }
-  GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element));
-  ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+      "The element \"%s\" would have a negative start of -%"
+      GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_start));
 }
 
-#define SET_TRIMMING_DATA(data, _edge, offset) G_STMT_START { \
-  data.edge = (_edge);                                           \
-  data.start_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
-  data.inpoint_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
-  data.duration_diff = (_edge) == GES_EDGE_END ? (offset) : -(offset); \
-} G_STMT_END
+static void
+set_negative_duration_error (GError ** error, GESTimelineElement * element,
+    GstClockTime neg_duration)
+{
+  g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+      "The element \"%s\" would have a negative duration of -%"
+      GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_duration));
+}
 
+static void
+set_negative_inpoint_error (GError ** error, GESTimelineElement * element,
+    GstClockTime neg_inpoint)
+{
+  g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_TIME,
+      "The element \"%s\" would have a negative in-point of -%"
+      GST_TIME_FORMAT, element->name, GST_TIME_ARGS (neg_inpoint));
+}
 
-gboolean
-timeline_tree_trim (GNode * root, GESTimelineElement * element,
-    gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
-    GstClockTime snapping_distance)
+static void
+set_negative_layer_error (GError ** error, GESTimelineElement * element,
+    gint64 neg_layer)
 {
-  GHashTableIter iter;
-  gboolean res = TRUE;
-  GESTimelineElement *elem;
-  gint64 new_layer_priority =
-      ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element)) -
-      layer_priority_offset;
-  SnappingData snapping = {
-    .distance = snapping_distance,
-    .on_end_only = edge == GES_EDGE_END,
-    .on_start_only = edge != GES_EDGE_END,
-    .element = NULL,
-    .edge = GES_EDGE_NONE,
-    .diff = (GstClockTimeDiff) snapping_distance,
-  };
-  TreeIterationData data = tree_iteration_data_init;
+  g_set_error (error, GES_ERROR, GES_ERROR_NEGATIVE_LAYER,
+      "The element \"%s\" would have a negative layer priority of -%"
+      G_GINT64_FORMAT, element->name, neg_layer);
+}
 
-  data.root = root;
-  data.element = element;
-  data.priority_diff =
-      (gint64) ges_timeline_element_get_layer_priority (element) -
-      new_layer_priority;
-  data.snapping = snapping_distance ? &snapping : NULL;
-  data.moved_clips = g_hash_table_new (g_direct_hash, g_direct_equal);
-
-  SET_TRIMMING_DATA (data, edge, offset);
-  GST_INFO ("%" GES_FORMAT " trimming %s with offset %" G_GINT64_FORMAT "",
-      GES_ARGS (element), edge == GES_EDGE_END ? "end" : "start", offset);
-  g_node_traverse (find_node (root, element), G_IN_ORDER,
-      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
-      &data.movings);
-
-  if (!timeline_tree_can_trim_element_internal (root, &data)) {
-    GST_INFO ("Can not trim object.");
+static void
+set_breaks_duration_limit_error (GError ** error, GESClip * clip,
+    GstClockTime duration, GstClockTime duration_limit)
+{
+  g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
+      "The clip \"%s\" would have a duration of %" GST_TIME_FORMAT
+      " that would break its duration-limit of %" GST_TIME_FORMAT,
+      GES_TIMELINE_ELEMENT_NAME (clip), GST_TIME_ARGS (duration),
+      GST_TIME_ARGS (duration_limit));
+}
+
+static void
+set_inpoint_breaks_max_duration_error (GError ** error,
+    GESTimelineElement * element, GstClockTime inpoint,
+    GstClockTime max_duration)
+{
+  g_set_error (error, GES_ERROR, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT,
+      "The element \"%s\" would have an in-point of %" GST_TIME_FORMAT
+      " that would break its max-duration of %" GST_TIME_FORMAT,
+      GES_TIMELINE_ELEMENT_NAME (element), GST_TIME_ARGS (inpoint),
+      GST_TIME_ARGS (max_duration));
+}
+
+static gboolean
+set_layer_priority (GESTimelineElement * element, EditData * data,
+    GError ** error)
+{
+  gint64 layer_offset = data->layer_offset;
+  guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
+
+  if (!layer_offset)
+    return TRUE;
+
+  if (layer_prio == GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+    GST_INFO_OBJECT (element, "Cannot shift %s to a new layer because it "
+        "has no layer priority", element->name);
+    return FALSE;
+  }
+
+  if (layer_offset > (gint64) layer_prio) {
+    GST_INFO_OBJECT (element, "%s would have a negative layer priority (%"
+        G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name,
+        layer_prio, layer_offset);
+    set_negative_layer_error (error, element,
+        layer_offset - (gint64) layer_prio);
+    return FALSE;
+  }
+  if ((layer_prio - (gint64) layer_offset) >= G_MAXUINT32) {
+    GST_ERROR_OBJECT (element, "%s would have an overflowing layer priority",
+        element->name);
+    return FALSE;
+  }
+
+  data->layer_priority = (guint32) (layer_prio - (gint64) layer_offset);
+
+  if (ges_timeline_layer_priority_in_gap (element->timeline,
+          data->layer_priority)) {
+    GST_ERROR_OBJECT (element, "Edit layer %" G_GUINT32_FORMAT " would "
+        "be within a gap in the timeline layers", data->layer_priority);
+    return FALSE;
+  }
+
+  GST_INFO_OBJECT (element, "%s will move to layer %" G_GUINT32_FORMAT,
+      element->name, data->layer_priority);
+
+  return TRUE;
+}
+
+#define _CHECK_END(element, start, duration) \
+  if (!GST_CLOCK_TIME_IS_VALID (_clock_time_plus (start, duration))) { \
+    GST_INFO_OBJECT (element, "Cannot edit %s because it would result in " \
+        "an invalid end", element->name); \
+    return FALSE; \
+  }
+
+static gboolean
+set_edit_move_values (GESTimelineElement * element, EditData * data,
+    GError ** error)
+{
+  gboolean negative = FALSE;
+  GstClockTime new_start =
+      _clock_time_minus_diff (element->start, data->offset, &negative);
+  if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) {
+    GST_INFO_OBJECT (element, "Cannot move %" GES_FORMAT " with offset %"
+        G_GINT64_FORMAT " because it would result in an invalid start",
+        GES_ARGS (element), data->offset);
+    if (negative)
+      set_negative_start_error (error, element, new_start);
+    return FALSE;
+  }
+  _CHECK_END (element, new_start, element->duration);
+  data->start = new_start;
+
+  if (GES_IS_GROUP (element))
+    return TRUE;
+
+  GST_INFO_OBJECT (element, "%s will move by setting start to %"
+      GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start));
+
+  return set_layer_priority (element, data, error);
+}
+
+static gboolean
+set_edit_trim_start_clip_inpoints (GESClip * clip, EditData * clip_data,
+    GHashTable * edit_table, GError ** error)
+{
+  gboolean ret = FALSE;
+  GList *tmp;
+  GstClockTime duration_limit;
+  GstClockTime clip_inpoint;
+  GstClockTime new_start = clip_data->start;
+  gboolean no_core = FALSE;
+  GHashTable *child_inpoints;
+
+  child_inpoints = g_hash_table_new_full (NULL, NULL, gst_object_unref, g_free);
+
+  clip_inpoint = ges_clip_get_core_internal_time_from_timeline_time (clip,
+      new_start, &no_core, error);
+
+  if (no_core) {
+    GST_INFO_OBJECT (clip, "Clip %" GES_FORMAT " has no active core "
+        "children with an internal source. Not setting in-point during "
+        "trim to start", GES_ARGS (clip));
+    clip_inpoint = GES_TIMELINE_ELEMENT_INPOINT (clip);
+  } else if (!GST_CLOCK_TIME_IS_VALID (clip_inpoint)) {
+    GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT
+        " with offset %" G_GINT64_FORMAT " because it would result in an "
+        "invalid in-point for its core children", GES_ARGS (clip),
+        clip_data->offset);
+    goto done;
+  } else {
+    GST_INFO_OBJECT (clip, "Clip %" GES_FORMAT " will have its in-point "
+        " set to %" GST_TIME_FORMAT " because its start is being trimmed "
+        "to %" GST_TIME_FORMAT, GES_ARGS (clip),
+        GST_TIME_ARGS (clip_inpoint), GST_TIME_ARGS (new_start));
+    clip_data->inpoint = clip_inpoint;
+  }
+
+  /* need to set in-point of active non-core children to keep their
+   * internal content at the same timeline position */
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTimelineElement *child = tmp->data;
+    GESTrackElement *el = tmp->data;
+    GstClockTime new_inpoint = child->inpoint;
+    GstClockTime *inpoint_p;
+
+    if (ges_track_element_has_internal_source (el)) {
+      if (ges_track_element_is_core (el)) {
+        new_inpoint = clip_inpoint;
+      } else if (ges_track_element_is_active (el)) {
+        EditData *data;
+
+        if (g_hash_table_contains (edit_table, child)) {
+          GST_ERROR_OBJECT (child, "Already set to be edited");
+          goto done;
+        }
+
+        new_inpoint = ges_clip_get_internal_time_from_timeline_time (clip, el,
+            new_start, error);
+
+        if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) {
+          GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT
+              " to %" GST_TIME_FORMAT " because it would result in an "
+              "invalid in-point for the non-core child %" GES_FORMAT,
+              GES_ARGS (clip), GST_TIME_ARGS (new_start), GES_ARGS (child));
+          goto done;
+        }
+
+        GST_INFO_OBJECT (child, "Setting track element %s to trim "
+            "in-point to %" GST_TIME_FORMAT " since the parent clip %"
+            GES_FORMAT " is being trimmed to start %" GST_TIME_FORMAT,
+            child->name, GST_TIME_ARGS (new_inpoint), GES_ARGS (clip),
+            GST_TIME_ARGS (new_start));
+
+        data = new_edit_data (EDIT_TRIM_INPOINT_ONLY, 0, 0);
+        data->inpoint = new_inpoint;
+        g_hash_table_insert (edit_table, child, data);
+      }
+    }
+
+    if (GES_CLOCK_TIME_IS_LESS (child->maxduration, new_inpoint)) {
+      GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT
+          " to %" GST_TIME_FORMAT " because it would result in an "
+          "in-point of %" GST_TIME_FORMAT " for the child %" GES_FORMAT
+          ", which breaks its max-duration", GES_ARGS (clip),
+          GST_TIME_ARGS (new_start), GST_TIME_ARGS (new_inpoint),
+          GES_ARGS (child));
+
+      set_inpoint_breaks_max_duration_error (error, child, new_inpoint,
+          child->maxduration);
+      goto done;
+    }
+
+    inpoint_p = g_new (GstClockTime, 1);
+    *inpoint_p = new_inpoint;
+    g_hash_table_insert (child_inpoints, gst_object_ref (child), inpoint_p);
+  }
+
+  duration_limit =
+      ges_clip_duration_limit_with_new_children_inpoints (clip, child_inpoints);
+
+  if (GES_CLOCK_TIME_IS_LESS (duration_limit, clip_data->duration)) {
+    GST_INFO_OBJECT (clip, "Cannot trim start of %" GES_FORMAT
+        " to %" GST_TIME_FORMAT " because it would result in a "
+        "duration of %" GST_TIME_FORMAT " that breaks its new "
+        "duration-limit of %" GST_TIME_FORMAT, GES_ARGS (clip),
+        GST_TIME_ARGS (new_start), GST_TIME_ARGS (clip_data->duration),
+        GST_TIME_ARGS (duration_limit));
+
+    set_breaks_duration_limit_error (error, clip, clip_data->duration,
+        duration_limit);
+    goto done;
+  }
+
+  ret = TRUE;
+
+done:
+  g_hash_table_unref (child_inpoints);
+
+  return ret;
+}
+
+/* trim the start of a clip or a track element */
+static gboolean
+set_edit_trim_start_values (GESTimelineElement * element, EditData * data,
+    GHashTable * edit_table, GError ** error)
+{
+  gboolean negative = FALSE;
+  GstClockTime new_duration;
+  GstClockTime new_start =
+      _clock_time_minus_diff (element->start, data->offset, &negative);
+
+  if (negative || !GST_CLOCK_TIME_IS_VALID (new_start)) {
+    GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
+        " with offset %" G_GINT64_FORMAT " because it would result in an "
+        "invalid start", GES_ARGS (element), data->offset);
+    if (negative)
+      set_negative_start_error (error, element, new_start);
+    return FALSE;
+  }
+
+  new_duration =
+      _clock_time_minus_diff (element->duration, -data->offset, &negative);
+
+  if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) {
+    GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
+        " with offset %" G_GINT64_FORMAT " because it would result in an "
+        "invalid duration", GES_ARGS (element), data->offset);
+    if (negative)
+      set_negative_duration_error (error, element, new_duration);
+    return FALSE;
+  }
+  _CHECK_END (element, new_start, new_duration);
+
+  data->start = new_start;
+  data->duration = new_duration;
+
+  if (GES_IS_GROUP (element))
+    return TRUE;
+
+  if (GES_IS_CLIP (element)) {
+    if (!set_edit_trim_start_clip_inpoints (GES_CLIP (element), data,
+            edit_table, error))
+      return FALSE;
+  } else if (GES_IS_TRACK_ELEMENT (element)
+      && ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) {
+    GstClockTime new_inpoint =
+        _clock_time_minus_diff (element->inpoint, data->offset, &negative);
+
+    if (negative || !GST_CLOCK_TIME_IS_VALID (new_inpoint)) {
+      GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
+          " with offset %" G_GINT64_FORMAT " because it would result in "
+          "an invalid in-point", GES_ARGS (element), data->offset);
+      if (negative)
+        set_negative_inpoint_error (error, element, new_inpoint);
+      return FALSE;
+    }
+  }
+
+  GST_INFO_OBJECT (element, "%s will trim start by setting start to %"
+      GST_TIME_FORMAT ", in-point to %" GST_TIME_FORMAT " and duration "
+      "to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start),
+      GST_TIME_ARGS (data->inpoint), GST_TIME_ARGS (data->duration));
+
+  return set_layer_priority (element, data, error);
+}
+
+/* trim the end of a clip or a track element */
+static gboolean
+set_edit_trim_end_values (GESTimelineElement * element, EditData * data,
+    GError ** error)
+{
+  gboolean negative = FALSE;
+  GstClockTime new_duration =
+      _clock_time_minus_diff (element->duration, data->offset, &negative);
+  if (negative || !GST_CLOCK_TIME_IS_VALID (new_duration)) {
+    GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
+        " with offset %" G_GINT64_FORMAT " because it would result in an "
+        "invalid duration", GES_ARGS (element), data->offset);
+    if (negative)
+      set_negative_duration_error (error, element, new_duration);
+    return FALSE;
+  }
+  _CHECK_END (element, element->start, new_duration);
+
+  if (GES_IS_CLIP (element)) {
+    GESClip *clip = GES_CLIP (element);
+    GstClockTime limit = ges_clip_get_duration_limit (clip);
+
+    if (GES_CLOCK_TIME_IS_LESS (limit, new_duration)) {
+      GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
+          " with offset %" G_GINT64_FORMAT " because the duration would "
+          "exceed the clip's duration-limit %" G_GINT64_FORMAT,
+          GES_ARGS (element), data->offset, limit);
+
+      set_breaks_duration_limit_error (error, clip, new_duration, limit);
+      return FALSE;
+    }
+  }
+
+  data->duration = new_duration;
+
+  if (GES_IS_GROUP (element))
+    return TRUE;
+
+  GST_INFO_OBJECT (element, "%s will trim end by setting duration to %"
+      GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->duration));
+
+  return set_layer_priority (element, data, error);
+}
+
+static gboolean
+set_edit_values (GESTimelineElement * element, EditData * data,
+    GHashTable * edit_table, GError ** error)
+{
+  switch (data->mode) {
+    case EDIT_MOVE:
+      return set_edit_move_values (element, data, error);
+    case EDIT_TRIM_START:
+      return set_edit_trim_start_values (element, data, edit_table, error);
+    case EDIT_TRIM_END:
+      return set_edit_trim_end_values (element, data, error);
+    case EDIT_TRIM_INPOINT_ONLY:
+      GST_ERROR_OBJECT (element, "Trim in-point only not handled");
+      return FALSE;
+  }
+  return FALSE;
+}
+
+static gboolean
+add_clips_to_list (GNode * node, GList ** list)
+{
+  GESTimelineElement *element = node->data;
+  GESTimelineElement *clip = NULL;
+
+  if (GES_IS_CLIP (element))
+    clip = element;
+  else if (GES_IS_CLIP (element->parent))
+    clip = element->parent;
+
+  if (clip && !g_list_find (*list, clip))
+    *list = g_list_append (*list, clip);
+
+  return FALSE;
+}
+
+static gboolean
+replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
+    GHashTable * edit_table, GError ** err)
+{
+  gboolean ret = TRUE;
+  GList *tmp, *clips = NULL;
+  GNode *node = find_node (root, group);
+  GstClockTime new_end, new_start;
+  ElementEditMode mode;
+  gint64 layer_offset;
+
+  if (!node) {
+    GST_ERROR_OBJECT (group, "Not being tracked");
     goto error;
   }
 
-  if (snapping_distance) {
-    if (snapping.element) {
-      offset =
-          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
-          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+  /* new context for the lifespan of group_data */
+  {
+    EditData *group_edit = g_hash_table_lookup (edit_table, group);
+
+    if (!group_edit) {
+      GST_ERROR_OBJECT (group, "Edit data for group was missing");
+      goto error;
+    }
+
+    group_edit->start = group->start;
+    group_edit->duration = group->duration;
+
+    /* should only set the start and duration fields, table should not be
+     * needed, so we pass NULL */
+    if (!set_edit_values (group, group_edit, NULL, err))
+      goto error;
+
+    new_start = group_edit->start;
+    new_end = _clock_time_plus (group_edit->start, group_edit->duration);
+
+    if (!GST_CLOCK_TIME_IS_VALID (new_start)
+        || !GST_CLOCK_TIME_IS_VALID (new_end)) {
+      GST_ERROR_OBJECT (group, "Edit data gave an invalid start or end");
+      goto error;
+    }
+
+    layer_offset = group_edit->layer_offset;
+    mode = group_edit->mode;
+
+    /* can traverse leaves to find all the clips since they are at _most_
+     * one step above the track elements */
+    g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+        (GNodeTraverseFunc) add_clips_to_list, &clips);
+
+    if (!clips) {
+      GST_INFO_OBJECT (group, "Contains no clips, so cannot be edited");
+      goto error;
+    }
+
+    if (!g_hash_table_remove (edit_table, group)) {
+      GST_ERROR_OBJECT (group, "Could not replace the group in the edit list");
+      goto error;
+    }
+    /* removing the group from the table frees group_edit */
+  }
+
+  for (tmp = clips; tmp; tmp = tmp->next) {
+    GESTimelineElement *clip = tmp->data;
+    gboolean edit = FALSE;
+    GstClockTimeDiff offset = G_MAXINT64;
+    ElementEditMode clip_mode = mode;
+
+    /* if at the edge of the group and being trimmed forward or backward */
+    if (mode == EDIT_MOVE) {
+      /* same offset as the group */
+      edit = TRUE;
+      offset = group->start - new_start;
+
+      GST_INFO_OBJECT (clip, "Setting clip %s to moving with offset %"
+          G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT
+          " is moving to %" GST_TIME_FORMAT, clip->name, offset,
+          GES_ARGS (group), GST_TIME_ARGS (new_start));
+
+    } else if ((mode == EDIT_TRIM_START)
+        && (clip->start <= new_start || clip->start == group->start)) {
+      /* trim to same start */
+      edit = TRUE;
+      offset = clip->start - new_start;
+
+      GST_INFO_OBJECT (clip, "Setting clip %s to trim start with offset %"
+          G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT " is "
+          "being trimmed to start %" GST_TIME_FORMAT, clip->name, offset,
+          GES_ARGS (group), GST_TIME_ARGS (new_start));
+
+    } else if (mode == EDIT_TRIM_END
+        && (_END (clip) >= new_end || _END (clip) == _END (group))) {
+      /* trim to same end */
+      edit = TRUE;
+      offset = _END (clip) - new_end;
+
+      GST_INFO_OBJECT (clip, "Setting clip %s to trim end with offset %"
+          G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT " is "
+          "being trimmed to end %" GST_TIME_FORMAT, clip->name, offset,
+          GES_ARGS (group), GST_TIME_ARGS (new_end));
+
+    } else if (layer_offset) {
+      /* still need to move layer */
+      edit = TRUE;
+      clip_mode = EDIT_MOVE;
+      offset = 0;
+    }
+    if (edit) {
+      EditData *clip_data;
+
+      if (layer_offset)
+        GST_INFO_OBJECT (clip, "Setting clip %s to move to new layer with "
+            "offset %" G_GINT64_FORMAT " since an ancestor group %"
+            GES_FORMAT " is being moved with the same offset", clip->name,
+            layer_offset, GES_ARGS (group));
+
+      if (g_hash_table_contains (edit_table, clip)) {
+        GST_ERROR_OBJECT (clip, "Already set to be edited");
+        goto error;
+      }
+      clip_data = new_edit_data (clip_mode, offset, layer_offset);
+      g_hash_table_insert (edit_table, clip, clip_data);
+      if (!set_edit_values (clip, clip_data, edit_table, err))
+        goto error;
+    }
+  }
+
+done:
+  g_list_free (clips);
+  return ret;
+
+error:
+  ret = FALSE;
+  goto done;
+}
+
+/* set the edit values for the entries in @edits
+ * any groups in @edits will be replaced by their clip children */
+static gboolean
+timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits,
+    GError ** err)
+{
+  gboolean ret = TRUE;
+  GESTimelineElement *element;
+  EditData *edit_data;
+  /* content of edit table may change when group edits are replaced by
+   * clip edits and clip edits introduce edits for non-core children */
+  GList *tmp, *elements = g_hash_table_get_keys (edits);
+
+  for (tmp = elements; tmp; tmp = tmp->next) {
+    gboolean res;
+    element = tmp->data;
+    edit_data = g_hash_table_lookup (edits, element);
+    if (!edit_data) {
+      GST_ERROR_OBJECT (element, "No edit data for the element");
+      goto error;
+    }
+    if (GES_IS_GROUP (element))
+      res = replace_group_with_clip_edits (root, element, edits, err);
+    else
+      res = set_edit_values (element, edit_data, edits, err);
+    if (!res)
+      goto error;
+  }
+
+done:
+  g_list_free (elements);
+
+  return ret;
+
+error:
+  ret = FALSE;
+  goto done;
+}
+
+/* set the moving PositionData by using their parent clips.
+ * @edit_table should already have had its values set, and any group edits
+ * replaced by clip edits. */
+static void
+set_moving_positions_from_edits (GHashTable * moving, GHashTable * edit_table)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_hash_table_iter_init (&iter, moving);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    GESTimelineElement *element = key;
+    PositionData *pos = value;
+    GESTimelineElement *parent;
+    EditData *edit;
+
+    /* a track element will end up with the same start and end as its clip */
+    /* if no parent, act as own parent */
+    parent = element->parent ? element->parent : element;
+    edit = g_hash_table_lookup (edit_table, parent);
+
+    if (edit && GST_CLOCK_TIME_IS_VALID (edit->start))
+      pos->start = edit->start;
+    else
+      pos->start = element->start;
+
+    if (edit && GST_CLOCK_TIME_IS_VALID (edit->duration))
+      pos->end = pos->start + edit->duration;
+    else
+      pos->end = pos->start + element->duration;
+
+    if (edit && edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY)
+      pos->layer_priority = edit->layer_priority;
+    else
+      pos->layer_priority = ges_timeline_element_get_layer_priority (element);
+  }
+}
+
+static void
+give_edits_same_offset (GHashTable * edits, GstClockTimeDiff offset,
+    gint64 layer_offset)
+{
+  GHashTableIter iter;
+  gpointer value;
+
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, NULL, &value)) {
+    EditData *edit_data = value;
+    edit_data->offset = offset;
+    edit_data->layer_offset = layer_offset;
+  }
+}
+
+/****************************************************
+ *         Initialise Edit Data and Moving          *
+ ****************************************************/
+
+static gboolean
+add_track_elements_to_moving (GNode * node, GHashTable * track_elements)
+{
+  GESTimelineElement *element = node->data;
+  if (GES_IS_TRACK_ELEMENT (element)) {
+    GST_LOG_OBJECT (element, "%s set as moving", element->name);
+    g_hash_table_insert (track_elements, element, g_new0 (PositionData, 1));
+  }
+  return FALSE;
+}
+
+/* add all the track elements found under the elements in @edits to @moving,
+ * but does not set their position data */
+static gboolean
+timeline_tree_add_edited_to_moving (GNode * root, GHashTable * edits,
+    GHashTable * moving)
+{
+  GHashTableIter iter;
+  gpointer key;
+
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, &key, NULL)) {
+    GESTimelineElement *element = key;
+    GNode *node = find_node (root, element);
+    if (!node) {
+      GST_ERROR_OBJECT (element, "Not being tracked");
+      return FALSE;
+    }
+    g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+        (GNodeTraverseFunc) add_track_elements_to_moving, moving);
+  }
+
+  return TRUE;
+}
+
+/* check we can handle the top and all of its children */
+static gboolean
+check_types (GESTimelineElement * element, gboolean is_top)
+{
+  if (!GES_IS_CLIP (element) && !GES_IS_GROUP (element)
+      && !GES_IS_TRACK_ELEMENT (element)) {
+    GST_ERROR_OBJECT (element, "Cannot handle a GESTimelineElement of the "
+        "type %s", G_OBJECT_TYPE_NAME (element));
+    return FALSE;
+  }
+  if (!is_top && element->parent) {
+    if ((GES_IS_CLIP (element) && !GES_IS_GROUP (element->parent))
+        || (GES_IS_GROUP (element) && !GES_IS_GROUP (element->parent))
+        || (GES_IS_TRACK_ELEMENT (element) && !GES_IS_CLIP (element->parent))) {
+      GST_ERROR_OBJECT (element, "A parent of type %s is not handled",
+          G_OBJECT_TYPE_NAME (element->parent));
+      return FALSE;
+    }
+  }
+  if (GES_IS_CONTAINER (element)) {
+    GList *tmp;
+    for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+      if (!check_types (tmp->data, FALSE))
+        return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+/* @edits: The table to add the edit to
+ * @element: The element to edit
+ * @mode: The mode for editing @element
+ *
+ * Adds an edit for @element it to the table with its EditData only set
+ * with @mode.
+ *
+ * The offsets for the edit will have to be set later.
+ */
+static gboolean
+add_element_edit (GHashTable * edits, GESTimelineElement * element,
+    ElementEditMode mode)
+{
+  if (!check_types (element, TRUE))
+    return FALSE;
+
+  if (g_hash_table_contains (edits, element)) {
+    GST_ERROR_OBJECT (element, "Already set to be edited");
+    return FALSE;
+  }
+
+  switch (mode) {
+    case EDIT_MOVE:
+      GST_LOG_OBJECT (element, "%s set to move", element->name);
+      break;
+    case EDIT_TRIM_START:
+      GST_LOG_OBJECT (element, "%s set to trim start", element->name);
+      break;
+    case EDIT_TRIM_END:
+      GST_LOG_OBJECT (element, "%s set to trim end", element->name);
+      break;
+    case EDIT_TRIM_INPOINT_ONLY:
+      GST_ERROR_OBJECT (element, "%s set to trim in-point only", element->name);
+      return FALSE;
+  }
+
+  g_hash_table_insert (edits, element, new_edit_data (mode, 0, 0));
+
+  return TRUE;
+}
+
+/********************************************
+ *   Check against current configuration    *
+ ********************************************/
+
+/* can move with no snapping or change in parent! */
+gboolean
+timeline_tree_can_move_element (GNode * root,
+    GESTimelineElement * element, guint32 priority, GstClockTime start,
+    GstClockTime duration, GError ** error)
+{
+  gboolean ret = FALSE;
+  guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
+  GstClockTime distance, new_end;
+  GHashTable *move_edits, *trim_edits, *moving;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  if (layer_prio == GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY
+      && priority != layer_prio) {
+    GST_INFO_OBJECT (element, "Cannot move to a layer when no layer "
+        "priority to begin with");
+    return FALSE;
+  }
+
+  distance = _abs_clock_time_distance (start, element->start);
+  if ((GstClockTimeDiff) distance >= G_MAXINT64) {
+    GST_WARNING_OBJECT (element, "Move in start from %" GST_TIME_FORMAT
+        " to %" GST_TIME_FORMAT " is too large to perform",
+        GST_TIME_ARGS (element->start), GST_TIME_ARGS (start));
+    return FALSE;
+  }
+
+  distance = _abs_clock_time_distance (duration, element->duration);
+  if ((GstClockTimeDiff) distance >= G_MAXINT64) {
+    GST_WARNING_OBJECT (element, "Move in duration from %" GST_TIME_FORMAT
+        " to %" GST_TIME_FORMAT " is too large to perform",
+        GST_TIME_ARGS (element->duration), GST_TIME_ARGS (duration));
+    return FALSE;
+  }
+
+  new_end = _clock_time_plus (start, duration);
+  if (!GST_CLOCK_TIME_IS_VALID (new_end)) {
+    GST_WARNING_OBJECT (element, "Move in start and duration to %"
+        GST_TIME_FORMAT " and %" GST_TIME_FORMAT " would produce an "
+        "invalid end", GST_TIME_ARGS (start), GST_TIME_ARGS (duration));
+    return FALSE;
+  }
+
+  /* treat as an EDIT_MOVE to the new priority, except on the element
+   * rather than the toplevel, followed by an EDIT_TRIM_END */
+  move_edits = new_edit_table ();
+  trim_edits = new_edit_table ();
+  moving = new_position_table ();
+
+  if (!add_element_edit (move_edits, element, EDIT_MOVE))
+    goto done;
+  /* moving should remain the same */
+  if (!add_element_edit (trim_edits, element, EDIT_TRIM_END))
+    goto done;
+
+  if (!timeline_tree_add_edited_to_moving (root, move_edits, moving)
+      || !timeline_tree_add_edited_to_moving (root, trim_edits, moving))
+    goto done;
+
+  /* no snapping */
+  give_edits_same_offset (move_edits, element->start - start,
+      (gint64) layer_prio - (gint64) priority);
+  give_edits_same_offset (trim_edits, element->duration - duration, 0);
+
+  /* assume both edits can be performed if each could occur individually */
+  /* should not effect duration or in-point */
+  if (!timeline_tree_set_element_edit_values (root, move_edits, error))
+    goto done;
+  /* should not effect start or in-point or layer */
+  if (!timeline_tree_set_element_edit_values (root, trim_edits, error))
+    goto done;
+
+  /* merge the two edits into moving positions */
+  g_hash_table_iter_init (&iter, moving);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    GESTimelineElement *el = key;
+    PositionData *pos_data = value;
+    EditData *move = NULL;
+    EditData *trim = NULL;
+
+    if (el->parent) {
+      move = g_hash_table_lookup (move_edits, el->parent);
+      trim = g_hash_table_lookup (trim_edits, el->parent);
+    }
+
+    if (!move)
+      move = g_hash_table_lookup (move_edits, el);
+    if (!trim)
+      trim = g_hash_table_lookup (trim_edits, el);
+
+    /* should always have move with a valid start */
+    if (!move || !GST_CLOCK_TIME_IS_VALID (move->start)) {
+      GST_ERROR_OBJECT (el, "Element set to moving but neither it nor its "
+          "parent are being edited");
+      goto done;
+    }
+    /* may not have trim if element is a group and the child is away
+     * from the edit position, but if we do it should have a valid duration */
+    if (trim && !GST_CLOCK_TIME_IS_VALID (trim->duration)) {
+      GST_ERROR_OBJECT (el, "Element set to trim end but neither it nor its "
+          "parent is being trimmed");
+      goto done;
+    }
+
+    pos_data->start = move->start;
+
+    if (move->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY)
+      pos_data->layer_priority = move->layer_priority;
+    else
+      pos_data->layer_priority = ges_timeline_element_get_layer_priority (el);
+
+    if (trim)
+      pos_data->end = pos_data->start + trim->duration;
+    else
+      pos_data->end = pos_data->start + el->duration;
+  }
+
+  /* check overlaps */
+  if (!timeline_tree_can_move_elements (root, moving, error))
+    goto done;
+
+  ret = TRUE;
+
+done:
+  g_hash_table_unref (trim_edits);
+  g_hash_table_unref (move_edits);
+  g_hash_table_unref (moving);
+
+  return ret;
+}
+
+/********************************************
+ *         Perform Element Edit             *
+ ********************************************/
+
+static gboolean
+perform_element_edit (GESTimelineElement * element, EditData * edit)
+{
+  gboolean ret = FALSE;
+  guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
+
+  switch (edit->mode) {
+    case EDIT_MOVE:
+      GST_INFO_OBJECT (element, "Moving %s from %" GST_TIME_FORMAT " to %"
+          GST_TIME_FORMAT, element->name, GST_TIME_ARGS (element->start),
+          GST_TIME_ARGS (edit->start));
+      break;
+    case EDIT_TRIM_START:
+      GST_INFO_OBJECT (element, "Trimming %s start from %" GST_TIME_FORMAT
+          " to %" GST_TIME_FORMAT, element->name,
+          GST_TIME_ARGS (element->start), GST_TIME_ARGS (edit->start));
+      break;
+    case EDIT_TRIM_END:
+      GST_INFO_OBJECT (element, "Trimming %s end from %" GST_TIME_FORMAT
+          " to %" GST_TIME_FORMAT, element->name,
+          GST_TIME_ARGS (_END (element)),
+          GST_TIME_ARGS (element->start + edit->duration));
+      break;
+    case EDIT_TRIM_INPOINT_ONLY:
+      GST_INFO_OBJECT (element, "Trimming %s in-point from %"
+          GST_TIME_FORMAT " to %" GST_TIME_FORMAT, element->name,
+          GST_TIME_ARGS (element->inpoint), GST_TIME_ARGS (edit->inpoint));
+      break;
+  }
+
+  if (!GES_IS_CLIP (element) && !GES_IS_TRACK_ELEMENT (element)) {
+    GST_ERROR_OBJECT (element, "Cannot perform edit on group");
+    return FALSE;
+  }
+
+  if (!GES_IS_CLIP (element)
+      && edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+    GST_ERROR_OBJECT (element, "Cannot move an element that is not a "
+        "clip to a new layer");
+    return FALSE;
+  }
 
-      GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT
-          " -- offset: %" G_GINT64_FORMAT "", GES_ARGS (snapping.element),
-          snapping.edge == GES_EDGE_END ? "end" : "start",
-          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), offset);
+  GES_TIMELINE_ELEMENT_SET_BEING_EDITED (element);
+  if (GST_CLOCK_TIME_IS_VALID (edit->start)) {
+    if (!ges_timeline_element_set_start (element, edit->start)) {
+      GST_ERROR_OBJECT (element, "Failed to set the start");
+      goto done;
     }
+  }
+  if (GST_CLOCK_TIME_IS_VALID (edit->inpoint)) {
+    if (!ges_timeline_element_set_inpoint (element, edit->inpoint)) {
+      GST_ERROR_OBJECT (element, "Failed to set the in-point");
+      goto done;
+    }
+  }
+  if (GST_CLOCK_TIME_IS_VALID (edit->duration)) {
+    if (!ges_timeline_element_set_duration (element, edit->duration)) {
+      GST_ERROR_OBJECT (element, "Failed to set the duration");
+      goto done;
+    }
+  }
+  if (edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+    GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
+    GESLayer *layer = ges_timeline_get_layer (timeline, edit->layer_priority);
+
+    GST_INFO_OBJECT (element, "Moving %s from layer %" G_GUINT32_FORMAT
+        " to layer %" G_GUINT32_FORMAT, element->name, layer_prio,
+        edit->layer_priority);
+
+    if (layer == NULL) {
+      /* make sure we won't loop forever */
+      if (ges_timeline_layer_priority_in_gap (timeline, edit->layer_priority)) {
+        GST_ERROR_OBJECT (element, "Requested layer %" G_GUINT32_FORMAT
+            " is within a gap in the timeline layers", edit->layer_priority);
+        goto done;
+      }
+
+      do {
+        layer = ges_timeline_append_layer (timeline);
+      } while (ges_layer_get_priority (layer) < edit->layer_priority);
+    } else {
+      gst_object_unref (layer);
+    }
+
+    if (!ges_clip_move_to_layer (GES_CLIP (element), layer)) {
+      GST_ERROR_OBJECT (element, "Failed to move layers");
+      goto done;
+    }
+  }
+
+  ret = TRUE;
+
+done:
+  GES_TIMELINE_ELEMENT_UNSET_BEING_EDITED (element);
+
+  return ret;
+}
+
+/* perform all the element edits found in @edits.
+ * These should only be clips of track elements. */
+static gboolean
+timeline_tree_perform_edits (GNode * root, GHashTable * edits)
+{
+  gboolean no_errors = TRUE;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  /* freeze the auto-transitions whilst we edit */
+  ges_timeline_freeze_auto_transitions (root->data, TRUE);
+
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    if (GES_IS_TRACK_ELEMENT (key))
+      ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), TRUE);
+  }
 
-    ges_timeline_emit_snapping (root->data, element,
-        snapping.element,
-        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
-            snapping.edge) : GST_CLOCK_TIME_NONE);
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    GESTimelineElement *element = key;
+    EditData *edit_data = value;
+    if (!perform_element_edit (element, edit_data))
+      no_errors = FALSE;
   }
 
-  g_hash_table_iter_init (&iter, data.moved_clips);
-  while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL))
-    trim_simple (elem, offset, edge);
+  g_hash_table_iter_init (&iter, edits);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    if (GES_IS_TRACK_ELEMENT (key))
+      ges_track_element_freeze_control_sources (GES_TRACK_ELEMENT (key), FALSE);
+  }
+
+  /* allow the transitions to update if they can */
+  ges_timeline_freeze_auto_transitions (root->data, FALSE);
 
   timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
-  timeline_update_transition (root->data);
   timeline_update_duration (root->data);
 
+  return no_errors;
+}
+
+#define _REPLACE_TRACK_ELEMENT_WITH_PARENT(element) \
+  element = (GES_IS_TRACK_ELEMENT (element) && element->parent) ? element->parent : element
+
+/********************************************
+ *                 Ripple                   *
+ ********************************************/
+
+gboolean
+timeline_tree_ripple (GNode * root, GESTimelineElement * element,
+    gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
+    GstClockTime snapping_distance, GError ** error)
+{
+  gboolean res = TRUE;
+  GNode *node;
+  GESTimelineElement *ripple_toplevel;
+  GstClockTime ripple_time;
+  GHashTable *edits = new_edit_table ();
+  GHashTable *moving = new_position_table ();
+  ElementEditMode mode;
+  SnappedPosition *snap = new_snapped_position (snapping_distance);
+
+  _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+  ripple_toplevel = ges_timeline_element_peak_toplevel (element);
+
+  /* if EDGE_END:
+   *   TRIM_END the element, and MOVE all toplevels whose start is after
+   *   the current end of the element by the same amount
+   * otherwise:
+   *   MOVE the topevel of the element, and all other toplevel elements
+   *   whose start is after the current start of the element */
+
+  switch (edge) {
+    case GES_EDGE_END:
+      GST_INFO_OBJECT (element, "Rippling end with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      mode = EDIT_TRIM_END;
+      break;
+    case GES_EDGE_START:
+      GST_INFO_OBJECT (element, "Rippling start with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      mode = EDIT_MOVE;
+      break;
+    case GES_EDGE_NONE:
+      GST_INFO_OBJECT (element, "Rippling with toplevel with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      element = ripple_toplevel;
+      mode = EDIT_MOVE;
+      break;
+    default:
+      GST_WARNING_OBJECT (element, "Edge not supported");
+      goto done;
+  }
+
+  ripple_time = ELEMENT_EDGE_VALUE (element, edge);
+
+  /* add edits */
+  if (!add_element_edit (edits, element, mode))
+    goto error;
+
+  for (node = root->children; node; node = node->next) {
+    GESTimelineElement *toplevel = node->data;
+    if (toplevel == ripple_toplevel)
+      continue;
+
+    if (toplevel->start >= ripple_time) {
+      if (!add_element_edit (edits, toplevel, EDIT_MOVE))
+        goto error;
+    }
+  }
+
+  if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+    goto error;
+
+  /* snap */
+  if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+    goto error;
+
+  /* check and set edits using snapped values */
+  give_edits_same_offset (edits, offset, layer_priority_offset);
+  if (!timeline_tree_set_element_edit_values (root, edits, error))
+    goto error;
+
+  /* check overlaps */
+  set_moving_positions_from_edits (moving, edits);
+  if (!timeline_tree_can_move_elements (root, moving, error))
+    goto error;
+
+  /* emit snapping now. Edits should only fail if a programming error
+   * occured */
+  if (snap)
+    ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+        snap->snapped);
+
+  res = timeline_tree_perform_edits (root, edits);
+
 done:
-  clean_iteration_data (&data);
+  g_hash_table_unref (edits);
+  g_hash_table_unref (moving);
+  g_free (snap);
   return res;
 
 error:
@@ -924,99 +2075,164 @@ error:
   goto done;
 }
 
+/********************************************
+ *                  Trim                    *
+ ********************************************/
+
 gboolean
-timeline_tree_move (GNode * root, GESTimelineElement * element,
+timeline_tree_trim (GNode * root, GESTimelineElement * element,
     gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
-    GstClockTime snapping_distance)
+    GstClockTime snapping_distance, GError ** error)
 {
   gboolean res = TRUE;
-  GESTimelineElement *toplevel = get_toplevel_container (element);
-  TreeIterationData data = tree_iteration_data_init;
-  SnappingData snapping = {
-    .distance = snapping_distance,
-    .on_end_only = edge == GES_EDGE_END,
-    .on_start_only = edge == GES_EDGE_END,
-    .element = NULL,
-    .edge = GES_EDGE_NONE,
-    .diff = (GstClockTimeDiff) snapping_distance,
-  };
+  GHashTable *edits = new_edit_table ();
+  GHashTable *moving = new_position_table ();
+  ElementEditMode mode;
+  SnappedPosition *snap = new_snapped_position (snapping_distance);
 
-  data.root = root;
-  data.element = edge == GES_EDGE_END ? element : toplevel;
-  data.edge = edge;
-  data.priority_diff = layer_priority_offset;
-  data.snapping = snapping_distance ? &snapping : NULL;
-  data.start_diff = edge == GES_EDGE_END ? 0 : offset;
-  data.duration_diff = edge == GES_EDGE_END ? offset : 0;
-
-  GST_INFO ("%" GES_FORMAT
-      " moving %s with offset %" G_GINT64_FORMAT ", (snaping distance: %"
-      G_GINT64_FORMAT ")", GES_ARGS (element),
-      edge == GES_EDGE_END ? "end" : "start", offset, snapping_distance);
-  g_node_traverse (find_node (root, data.element), G_IN_ORDER,
-      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
-      &data.movings);
-
-  if (!timeline_tree_can_move_element_from_data (root, &data)) {
-    GST_INFO ("Can not move object.");
+  _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+  /* TODO: 2.0 remove this warning and simply fail if no edge is specified */
+  if (edge == GES_EDGE_NONE) {
+    g_warning ("No edge specified for trimming. Defaulting to GES_EDGE_START");
+    edge = GES_EDGE_START;
+  }
+
+  switch (edge) {
+    case GES_EDGE_END:
+      GST_INFO_OBJECT (element, "Trimming end with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      mode = EDIT_TRIM_END;
+      break;
+    case GES_EDGE_START:
+      GST_INFO_OBJECT (element, "Trimming start with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      mode = EDIT_TRIM_START;
+      break;
+    default:
+      GST_WARNING_OBJECT (element, "Edge not supported");
+      goto done;
+  }
+
+  /* add edits */
+  if (!add_element_edit (edits, element, mode))
+    goto error;
+
+  if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+    goto error;
+
+  /* snap */
+  if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+    goto error;
+
+  /* check and set edits using snapped values */
+  give_edits_same_offset (edits, offset, layer_priority_offset);
+  if (!timeline_tree_set_element_edit_values (root, edits, error))
+    goto error;
+
+  /* check overlaps */
+  set_moving_positions_from_edits (moving, edits);
+  if (!timeline_tree_can_move_elements (root, moving, error)) {
     goto error;
   }
 
-  if (snapping_distance) {
-    if (snapping.element) {
-      gint64 noffset =
-          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
-          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
-
-      GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT
-          "%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT
-          " (previous offset: %" G_GINT64_FORMAT ")",
-          GES_ARGS (snapping.moving_element),
-          snapping.moving_edge == GES_EDGE_END ? "end" : "start",
-          GES_ARGS (snapping.element),
-          snapping.edge == GES_EDGE_END ? "end" : "start",
-          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset,
-          offset);
-      offset = noffset;
-      data.start_diff = edge == GES_EDGE_END ? 0 : offset;
-      data.duration_diff = edge == GES_EDGE_END ? offset : 0;
-      data.snapping = NULL;
-      if (!timeline_tree_can_move_element_from_data (root, &data)) {
-        GST_INFO ("Can not move object.");
-        goto error;
-      }
-    }
+  /* emit snapping now. Edits should only fail if a programming error
+   * occured */
+  if (snap)
+    ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+        snap->snapped);
+
+  res = timeline_tree_perform_edits (root, edits);
+
+done:
+  g_hash_table_unref (edits);
+  g_hash_table_unref (moving);
+  g_free (snap);
+  return res;
+
+error:
+  res = FALSE;
+  goto done;
+}
 
-    ges_timeline_emit_snapping (root->data, element,
-        snapping.element,
-        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
-            snapping.edge) : GST_CLOCK_TIME_NONE);
+/********************************************
+ *                  Move                    *
+ ********************************************/
+
+gboolean
+timeline_tree_move (GNode * root, GESTimelineElement * element,
+    gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
+    GstClockTime snapping_distance, GError ** error)
+{
+  gboolean res = TRUE;
+  GHashTable *edits = new_edit_table ();
+  GHashTable *moving = new_position_table ();
+  ElementEditMode mode;
+  SnappedPosition *snap = new_snapped_position (snapping_distance);
+
+  _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+  switch (edge) {
+    case GES_EDGE_END:
+      GST_INFO_OBJECT (element, "Moving end with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      mode = EDIT_TRIM_END;
+      break;
+    case GES_EDGE_START:
+      GST_INFO_OBJECT (element, "Moving start with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      mode = EDIT_MOVE;
+      break;
+    case GES_EDGE_NONE:
+      GST_INFO_OBJECT (element, "Moving with toplevel with offset %"
+          G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+          layer_priority_offset);
+      element = ges_timeline_element_peak_toplevel (element);
+      mode = EDIT_MOVE;
+      break;
+    default:
+      GST_WARNING_OBJECT (element, "Edge not supported");
+      goto done;
   }
 
-  if (!check_can_move_to_layer (toplevel, layer_priority_offset)) {
-    GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
-        GES_ARGS (toplevel));
+  /* add edits */
+  if (!add_element_edit (edits, element, mode))
     goto error;
-  }
 
-  ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  if (edge == GES_EDGE_END)
-    ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
-            element->duration));
-  else
-    ges_timeline_element_set_start (toplevel, GST_CLOCK_DIFF (offset,
-            toplevel->start));
-  move_to_new_layer (toplevel, layer_priority_offset);
-  ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+    goto error;
 
-  timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
-  timeline_update_transition (root->data);
-  timeline_update_duration (root->data);
+  /* snap */
+  if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+    goto error;
 
-  GST_LOG ("Moved %" GES_FORMAT, GES_ARGS (element));
+  /* check and set edits using snapped values */
+  give_edits_same_offset (edits, offset, layer_priority_offset);
+  if (!timeline_tree_set_element_edit_values (root, edits, error))
+    goto error;
+
+  /* check overlaps */
+  set_moving_positions_from_edits (moving, edits);
+  if (!timeline_tree_can_move_elements (root, moving, error)) {
+    goto error;
+  }
+
+  /* emit snapping now. Edits should only fail if a programming error
+   * occured */
+  if (snap)
+    ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+        snap->snapped);
+
+  res = timeline_tree_perform_edits (root, edits);
 
 done:
-  clean_iteration_data (&data);
+  g_hash_table_unref (edits);
+  g_hash_table_unref (moving);
+  g_free (snap);
   return res;
 
 error:
@@ -1024,144 +2240,184 @@ error:
   goto done;
 }
 
+/********************************************
+ *                  Roll                    *
+ ********************************************/
+
+static gboolean
+is_descendant (GESTimelineElement * element, GESTimelineElement * ancestor)
+{
+  GESTimelineElement *parent = element;
+  while ((parent = parent->parent)) {
+    if (parent == ancestor)
+      return TRUE;
+  }
+  return FALSE;
+}
+
 static gboolean
 find_neighbour (GNode * node, TreeIterationData * data)
 {
-  gboolean in_same_track = FALSE;
   GList *tmp;
+  gboolean in_same_track = FALSE;
+  GESTimelineElement *edge_element, *element = node->data;
 
-  if (!GES_IS_SOURCE (node->data)) {
+  if (!GES_IS_SOURCE (element))
     return FALSE;
-  }
-
 
-  for (tmp = GES_CONTAINER_CHILDREN (data->element); tmp; tmp = tmp->next) {
-    if (tmp->data == node->data)
-      return FALSE;
+  /* if the element is controlled by the trimmed element (a group or a
+   * clip) it is not a neighbour */
+  if (is_descendant (element, data->element))
+    return FALSE;
 
-    if (ges_track_element_get_track (node->data) ==
+  /* test if we share a track with one of the sources at the edge */
+  for (tmp = data->sources; tmp; tmp = tmp->next) {
+    if (ges_track_element_get_track (GES_TRACK_ELEMENT (element)) ==
         ges_track_element_get_track (tmp->data)) {
       in_same_track = TRUE;
+      break;
     }
   }
 
-  if (!in_same_track) {
+  if (!in_same_track)
     return FALSE;
-  }
 
-  if (ELEMENT_EDGE_VALUE (node->data,
-          data->edge == GES_EDGE_START ? GES_EDGE_END : GES_EDGE_START) ==
-      ELEMENT_EDGE_VALUE (data->element, data->edge)) {
-    if (!g_list_find (data->neighbours,
-            GES_TIMELINE_ELEMENT_PARENT (node->data)))
-      data->neighbours =
-          g_list_prepend (data->neighbours,
-          GES_TIMELINE_ELEMENT_PARENT (node->data));
+  /* get the most toplevel element whose edge touches the position */
+  edge_element = NULL;
+  while (element && ELEMENT_EDGE_VALUE (element, data->edge) == data->position) {
+    edge_element = element;
+    element = element->parent;
   }
 
+  if (edge_element && !g_list_find (data->neighbours, edge_element))
+    data->neighbours = g_list_prepend (data->neighbours, edge_element);
+
+  return FALSE;
+}
+
+static gboolean
+find_sources_at_position (GNode * node, TreeIterationData * data)
+{
+  GESTimelineElement *element = node->data;
+
+  if (!GES_IS_SOURCE (element))
+    return FALSE;
+
+  if (ELEMENT_EDGE_VALUE (element, data->edge) == data->position)
+    data->sources = g_list_append (data->sources, element);
+
   return FALSE;
 }
 
 gboolean
 timeline_tree_roll (GNode * root, GESTimelineElement * element,
-    GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance)
+    GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance,
+    GError ** error)
 {
   gboolean res = TRUE;
   GList *tmp;
-  GESEdge neighbour_edge;
+  GNode *node;
   TreeIterationData data = tree_iteration_data_init;
-  SnappingData snapping = {
-    .distance = snapping_distance,
-    .on_end_only = edge == GES_EDGE_END,
-    .on_start_only = edge == GES_EDGE_END,
-    .element = NULL,
-    .edge = GES_EDGE_NONE,
-    .moving_edge = GES_EDGE_NONE,
-    .diff = (GstClockTimeDiff) snapping_distance,
-  };
-
-  data.root = root;
-  data.element = element;
-  data.edge = edge;
-  data.snapping = snapping_distance ? &snapping : NULL;
-  data.start_diff = edge == GES_EDGE_END ? 0 : offset;
-  data.inpoint_diff = edge == GES_EDGE_END ? 0 : offset;
-  data.duration_diff = edge == GES_EDGE_END ? offset : -offset;
-  data.ripple_time = GST_CLOCK_TIME_NONE;
-  neighbour_edge = data.edge == GES_EDGE_END ? GES_EDGE_START : GES_EDGE_END;
-
-  SET_TRIMMING_DATA (data, edge, offset);
-  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
-      (GNodeTraverseFunc) find_neighbour, &data);
-
-  if (data.neighbours == NULL) {
-    GST_INFO ("%s doesn't have any direct neighbour on edge %s",
-        element->name, ges_edge_name (edge));
-
-    return timeline_tree_trim (root, element, 0, offset, edge,
-        snapping_distance);
+  GHashTable *edits = new_edit_table ();
+  GHashTable *moving = new_position_table ();
+  ElementEditMode mode;
+  SnappedPosition *snap = new_snapped_position (snapping_distance);
+
+  _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+  /* if EDGE_END:
+   *   TRIM_END the element, and TRIM_START the neighbouring clips to the
+   *   end edge
+   * otherwise:
+   *   TRIM_START the element, and TRIM_END the neighbouring clips to the
+   *   start edge */
+
+  switch (edge) {
+    case GES_EDGE_END:
+      GST_INFO_OBJECT (element, "Rolling end with offset %"
+          G_GINT64_FORMAT, offset);
+      mode = EDIT_TRIM_END;
+      break;
+    case GES_EDGE_START:
+      GST_INFO_OBJECT (element, "Rolling start with offset %"
+          G_GINT64_FORMAT, offset);
+      mode = EDIT_TRIM_START;
+      break;
+    case GES_EDGE_NONE:
+      GST_WARNING_OBJECT (element, "Need to select an edge when rolling.");
+      goto done;
+    default:
+      GST_WARNING_OBJECT (element, "Edge not supported");
+      goto done;
   }
 
-  GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
-      GES_ARGS (data.element), ges_edge_name (edge), offset);
+  /* add edits */
+  if (!add_element_edit (edits, element, mode))
+    goto error;
 
-  if (!timeline_tree_can_move_element_from_data (root, &data))
+  /* first, find all the sources at the edge */
+  node = find_node (root, element);
+  if (!node) {
+    GST_ERROR_OBJECT (element, "Not being tracked");
     goto error;
+  }
 
+  data.element = element;
+  data.edge = (edge == GES_EDGE_END) ? GES_EDGE_END : GES_EDGE_START;
+  data.position = ELEMENT_EDGE_VALUE (element, data.edge);
+  data.sources = NULL;
 
-  if (snapping_distance) {
-    if (snapping.element) {
-      gint64 noffset =
-          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
-          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+  g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+      (GNodeTraverseFunc) find_sources_at_position, &data);
 
-      GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT
-          "%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT
-          " (previous offset: %" G_GINT64_FORMAT ")",
-          GES_ARGS (snapping.moving_element),
-          snapping.moving_edge == GES_EDGE_END ? "end" : "start",
-          GES_ARGS (snapping.element),
-          snapping.edge == GES_EDGE_END ? "end" : "start",
-          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset,
-          offset);
-      offset = noffset;
+  /* find elements that whose opposite edge touches the edge of the
+   * element and shares a track with one of the found sources */
+  data.edge = (edge == GES_EDGE_END) ? GES_EDGE_START : GES_EDGE_END;
+  data.neighbours = NULL;
 
-      SET_TRIMMING_DATA (data, edge, offset);
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAVES, -1,
+      (GNodeTraverseFunc) find_neighbour, &data);
 
-      if (!timeline_tree_can_move_element_from_data (root, &data)) {
-        GST_INFO ("Can not move object.");
-        goto error;
-      }
-    }
+  for (tmp = data.neighbours; tmp; tmp = tmp->next) {
+    GESTimelineElement *clip = tmp->data;
+    ElementEditMode opposite =
+        (mode == EDIT_TRIM_END) ? EDIT_TRIM_START : EDIT_TRIM_END;
+    if (!add_element_edit (edits, clip, opposite))
+      goto error;
   }
 
-  if (snapping_distance && snapping.element) {
-    ges_timeline_emit_snapping (root->data, element,
-        snapping.element,
-        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
-            snapping.edge) : GST_CLOCK_TIME_NONE);
-  }
+  if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+    goto error;
 
-  data.snapping = NULL;
-  SET_TRIMMING_DATA (data, neighbour_edge, offset);
-  for (tmp = data.neighbours; tmp; tmp = tmp->next) {
-    data.element = tmp->data;
+  /* snap */
+  if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+    goto error;
 
-    GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
-        GES_ARGS (data.element), ges_edge_name (data.edge), offset);
-    if (!timeline_tree_can_move_element_from_data (root, &data)) {
-      GST_INFO ("Can not move object.");
-      goto error;
-    }
+  /* check and set edits using snapped values */
+  give_edits_same_offset (edits, offset, 0);
+  if (!timeline_tree_set_element_edit_values (root, edits, error))
+    goto error;
+
+  /* check overlaps */
+  set_moving_positions_from_edits (moving, edits);
+  if (!timeline_tree_can_move_elements (root, moving, error)) {
+    goto error;
   }
 
-  trim_simple (element, offset, edge);
-  for (tmp = data.neighbours; tmp; tmp = tmp->next)
-    trim_simple (tmp->data, offset, data.edge);
+  /* emit snapping now. Edits should only fail if a programming error
+   * occured */
+  if (snap)
+    ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+        snap->snapped);
+
+  res = timeline_tree_perform_edits (root, edits);
 
 done:
-  timeline_update_duration (root->data);
+  g_hash_table_unref (edits);
+  g_hash_table_unref (moving);
   g_list_free (data.neighbours);
+  g_list_free (data.sources);
+  g_free (snap);
   return res;
 
 error:
@@ -1186,6 +2442,9 @@ create_transition_if_needed (GESTimeline * timeline, GESTrackElement * prev,
         "]", _START (next), duration);
     ges_timeline_create_transition (timeline, prev, next, NULL, layer,
         _START (next), duration);
+  } else {
+    GST_INFO ("Already have transition %" GST_PTR_FORMAT " between %" GES_FORMAT
+        " and %" GES_FORMAT, trans, GES_ARGS (prev), GES_ARGS (next));
   }
 }
 
@@ -1197,13 +2456,10 @@ create_transitions (GNode * node,
   GESTimeline *timeline;
   GESLayer *layer;
 
-  if (G_NODE_IS_ROOT (node))
+  if (!GES_IS_SOURCE (node->data))
     return FALSE;
 
   timeline = GES_TIMELINE_ELEMENT_TIMELINE (node->data);
-  data.element = node->data;
-  if (!GES_IS_SOURCE (node->data))
-    return FALSE;
 
   if (!timeline) {
     GST_INFO ("%" GES_FORMAT " not in timeline yet", GES_ARGS (node->data));
@@ -1219,8 +2475,9 @@ create_transitions (GNode * node,
   if (!ges_layer_get_auto_transition (layer))
     return FALSE;
 
-  g_node_traverse (g_node_get_root (node), G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
-      (GNodeTraverseFunc) check_track_elements_overlaps_and_values, &data);
+  GST_LOG (node->data, "Checking for overlaps");
+  data.root = g_node_get_root (node);
+  check_all_overlaps_with_element (node, &data);
 
   if (data.overlaping_on_start)
     create_transition_if_needed (timeline,
@@ -1235,6 +2492,16 @@ create_transitions (GNode * node,
 }
 
 void
+timeline_tree_create_transitions_for_track_element (GNode * root,
+    GESTrackElement * element, GESTreeGetAutoTransitionFunc get_auto_transition)
+{
+  GNode *node = find_node (root, element);
+  g_assert (node);
+
+  create_transitions (node, get_auto_transition);
+}
+
+void
 timeline_tree_create_transitions (GNode * root,
     GESTreeGetAutoTransitionFunc get_auto_transition)
 {
@@ -1261,3 +2528,48 @@ timeline_tree_get_duration (GNode * root)
 
   return duration;
 }
+
+static gboolean
+reset_layer_activness (GNode * node, GESLayer * layer)
+{
+  GESTrack *track;
+
+
+  if (!GES_IS_TRACK_ELEMENT (node->data))
+    return FALSE;
+
+  track = ges_track_element_get_track (node->data);
+  if (!track || (ges_timeline_element_get_layer_priority (node->data) !=
+          ges_layer_get_priority (layer)))
+    return FALSE;
+
+  ges_track_element_set_layer_active (node->data,
+      ges_layer_get_active_for_track (layer, track));
+
+  return FALSE;
+}
+
+void
+timeline_tree_reset_layer_active (GNode * root, GESLayer * layer)
+{
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+      (GNodeTraverseFunc) reset_layer_activness, layer);
+}
+
+static gboolean
+set_is_smart_rendering (GNode * node, gboolean * is_rendering_smartly)
+{
+  if (!GES_IS_SOURCE (node->data))
+    return FALSE;
+
+  ges_source_set_rendering_smartly (GES_SOURCE (node->data),
+      *is_rendering_smartly);
+  return FALSE;
+}
+
+void
+timeline_tree_set_smart_rendering (GNode * root, gboolean rendering_smartly)
+{
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+      (GNodeTraverseFunc) set_is_smart_rendering, &rendering_smartly);
+}
index e21c748..2ab4703 100644 (file)
@@ -1,5 +1,4 @@
-#ifndef __GES_TIMELINE_TREE__
-#define __GES_TIMELINE_TREE__
+#pragma once
 
 #include <ges/ges.h>
 #include "ges-auto-transition.h"
@@ -15,18 +14,19 @@ gboolean timeline_tree_can_move_element   (GNode *root,
                                            guint32 priority,
                                            GstClockTime start,
                                            GstClockTime duration,
-                                           GList *moving_track_elements);
+                                           GError ** error);
 
 gboolean timeline_tree_ripple             (GNode *root,
+                                           GESTimelineElement *element,
                                            gint64 layer_priority_offset,
                                            GstClockTimeDiff offset,
-                                           GESTimelineElement *rippled_element,
-                                           GESEdge moving_edge,
-                                           GstClockTime snapping_distance);
+                                           GESEdge edge,
+                                           GstClockTime snapping_distance,
+                                           GError ** error);
 
 void ges_timeline_emit_snapping           (GESTimeline * timeline,
-                                           GESTimelineElement * elem1,
-                                           GESTimelineElement * elem2,
+                                           GESTrackElement * elem1,
+                                           GESTrackElement * elem2,
                                            GstClockTime snap_time);
 
 gboolean timeline_tree_trim               (GNode *root,
@@ -34,7 +34,8 @@ gboolean timeline_tree_trim               (GNode *root,
                                            gint64 layer_priority_offset,
                                            GstClockTimeDiff offset,
                                            GESEdge edge,
-                                           GstClockTime snapping_distance);
+                                           GstClockTime snapping_distance,
+                                           GError ** error);
 
 
 gboolean timeline_tree_move               (GNode *root,
@@ -42,13 +43,15 @@ gboolean timeline_tree_move               (GNode *root,
                                            gint64 layer_priority_offset,
                                            GstClockTimeDiff offset,
                                            GESEdge edge,
-                                           GstClockTime snapping_distance);
+                                           GstClockTime snapping_distance,
+                                           GError ** error);
 
 gboolean timeline_tree_roll               (GNode * root,
                                            GESTimelineElement * element,
                                            GstClockTimeDiff offset,
                                            GESEdge edge,
-                                           GstClockTime snapping_distance);
+                                           GstClockTime snapping_distance,
+                                           GError ** error);
 
 typedef GESAutoTransition *
 (*GESTreeGetAutoTransitionFunc)           (GESTimeline * timeline,
@@ -56,6 +59,10 @@ typedef GESAutoTransition *
                                            GESTrackElement * next,
                                            GstClockTime transition_duration);
 
+void
+timeline_tree_create_transitions_for_track_element (GNode * root,
+                                                    GESTrackElement * element,
+                                                    GESTreeGetAutoTransitionFunc get_auto_transition);
 void timeline_tree_create_transitions     (GNode *root,
                                            GESTreeGetAutoTransitionFunc get_auto_transition);
 
@@ -74,6 +81,7 @@ ges_timeline_find_auto_transition         (GESTimeline * timeline, GESTrackEleme
 void
 timeline_update_duration                  (GESTimeline * timeline);
 
-void timeline_tree_init_debug             (void);
+void timeline_tree_reset_layer_active     (GNode *root, GESLayer *layer);
+void timeline_tree_set_smart_rendering    (GNode * root, gboolean rendering_smartly);
 
-#endif // __GES_TIMELINE_TREE__
+void timeline_tree_init_debug             (void);
index cfb94c7..6fe3f21 100644 (file)
  *
  * #GESTimeline is the central object for any multimedia timeline.
  *
- * Contains a list of #GESLayer which users should use to arrange the
- * various clips through time.
- *
- * The output type is determined by the #GESTrack that are set on
- * the #GESTimeline.
- *
- * To save/load a timeline, you can use the ges_timeline_load_from_uri() and
- * ges_timeline_save_to_uri() methods to use the default format. If you wish
- *
- * Note that any change you make in the timeline will not actually be taken
- * into account until you call the #ges_timeline_commit method.
+ * A timeline is composed of a set of #GESTrack-s and a set of
+ * #GESLayer-s, which are added to the timeline using
+ * ges_timeline_add_track() and ges_timeline_append_layer(), respectively.
+ *
+ * The contained tracks define the supported types of the timeline
+ * and provide the media output. Essentially, each track provides an
+ * additional source #GstPad.
+ *
+ * Most usage of a timeline will likely only need a single #GESAudioTrack
+ * and/or a single #GESVideoTrack. You can create such a timeline with
+ * ges_timeline_new_audio_video(). After this, you are unlikely to need to
+ * work with the tracks directly.
+ *
+ * A timeline's layers contain #GESClip-s, which in turn control the
+ * creation of #GESTrackElement-s, which are added to the timeline's
+ * tracks. See #GESTimeline::select-tracks-for-object if you wish to have
+ * more control over which track a clip's elements are added to.
+ *
+ * The layers are ordered, with higher priority layers having their
+ * content prioritised in the tracks. This ordering can be changed using
+ * ges_timeline_move_layer().
+ *
+ * ## Editing
+ *
+ * See #GESTimelineElement for the various ways the elements of a timeline
+ * can be edited.
+ *
+ * If you change the timing or ordering of a timeline's
+ * #GESTimelineElement-s, then these changes will not actually be taken
+ * into account in the output of the timeline's tracks until the
+ * ges_timeline_commit() method is called. This allows you to move its
+ * elements around, say, in response to an end user's mouse dragging, with
+ * little expense before finalising their effect on the produced data.
+ *
+ * ## Overlaps and Auto-Transitions
+ *
+ * There are certain restrictions placed on how #GESSource-s may overlap
+ * in a #GESTrack that belongs to a timeline. These will be enforced by
+ * GES, so the user will not need to keep track of them, but they should
+ * be aware that certain edits will be refused as a result if the overlap
+ * rules would be broken.
+ *
+ * Consider two #GESSource-s, `A` and `B`, with start times `startA` and
+ * `startB`, and end times `endA` and `endB`, respectively. The start
+ * time refers to their #GESTimelineElement:start, and the end time is
+ * their #GESTimelineElement:start + #GESTimelineElement:duration. These
+ * two sources *overlap* if:
+ *
+ * + they share the same #GESTrackElement:track (non %NULL), which belongs
+ *   to the timeline;
+ * + they share the same #GES_TIMELINE_ELEMENT_LAYER_PRIORITY; and
+ * + `startA < endB` and `startB < endA `.
+ *
+ * Note that when `startA = endB` or `startB = endA` then the two sources
+ * will *touch* at their edges, but are not considered overlapping.
+ *
+ * If, in addition, `startA < startB < endA`, then we can say that the
+ * end of `A` overlaps the start of `B`.
+ *
+ * If, instead, `startA <= startB` and `endA >= endB`, then we can say
+ * that `A` fully overlaps `B`.
+ *
+ * The overlap rules for a timeline are that:
+ *
+ * 1. One source cannot fully overlap another source.
+ * 2. A source can only overlap the end of up to one other source at its
+ *    start.
+ * 3. A source can only overlap the start of up to one other source at its
+ *    end.
+ *
+ * The last two rules combined essentially mean that at any given timeline
+ * position, only up to two #GESSource-s may overlap at that position. So
+ * triple or more overlaps are not allowed.
+ *
+ * If you switch on #GESTimeline:auto-transition, then at any moment when
+ * the end of one source (the first source) overlaps the start of another
+ * (the second source), a #GESTransitionClip will be automatically created
+ * for the pair in the same layer and it will cover their overlap. If the
+ * two elements are edited in a way such that the end of the first source
+ * no longer overlaps the start of the second, the transition will be
+ * automatically removed from the timeline. However, if the two sources
+ * still overlap at the same edges after the edit, then the same
+ * transition object will be kept, but with its timing and layer adjusted
+ * accordingly.
+ *
+ * ## Saving
+ *
+ * To save/load a timeline, you can use the ges_timeline_load_from_uri()
+ * and ges_timeline_save_to_uri() methods that use the default format.
+ *
+ * ## Playing
+ *
+ * A timeline is a #GstBin with a source #GstPad for each of its
+ * tracks, which you can fetch with ges_timeline_get_pad_for_track(). You
+ * will likely want to link these to some compatible sink #GstElement-s to
+ * be able to play or capture the content of the timeline.
+ *
+ * You can use a #GESPipeline to easily preview/play the timeline's
+ * content, or render it to a file.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -112,13 +200,17 @@ struct _GESTimelinePrivate
   GESTrackElement *last_snaped1;
   GESTrackElement *last_snaped2;
 
-  /* This variable is set to %TRUE when it makes sense to update the transitions,
-   * and %FALSE otherwize */
-  gboolean needs_transitions_update;
+  GESTrack *auto_transition_track;
+  GESTrack *new_track;
 
   /* While we are creating and adding the TrackElements for a clip, we need to
    * ignore the child-added signal */
-  GESClip *ignore_track_element_added;
+  gboolean track_elements_moving;
+  /* whether any error occurred during track selection, including
+   * programming or usage errors */
+  gboolean has_any_track_selection_error;
+  /* error set for non-programming/usage errors */
+  GError *track_selection_error;
   GList *groups;
 
   guint stream_start_group_id;
@@ -133,8 +225,15 @@ struct _GESTimelinePrivate
   /* For ges_timeline_commit_sync */
   GMutex commited_lock;
   GCond commited_cond;
+  gboolean commit_frozen;
+  gboolean commit_delayed;
 
   GThread *valid_thread;
+  gboolean disposed;
+
+  GstStreamCollection *stream_collection;
+
+  gboolean rendering_smartly;
 };
 
 /* private structure to contain our track-related information */
@@ -145,8 +244,10 @@ typedef struct
   GESTrack *track;
   GstPad *pad;                  /* Pad from the track */
   GstPad *ghostpad;
+  gulong track_element_added_sigid;
 
   gulong probe_id;
+  GstStream *stream;
 } TrackPrivate;
 
 enum
@@ -173,6 +274,7 @@ enum
   SNAPING_ENDED,
   SELECT_TRACKS_FOR_OBJECT,
   COMMITED,
+  SELECT_ELEMENT_TRACK,
   LAST_SIGNAL
 };
 
@@ -270,6 +372,12 @@ ges_timeline_set_property (GObject * object, guint property_id,
   }
 }
 
+gboolean
+ges_timeline_is_disposed (GESTimeline * timeline)
+{
+  return timeline->priv->disposed;
+}
+
 static void
 ges_timeline_dispose (GObject * object)
 {
@@ -277,6 +385,7 @@ ges_timeline_dispose (GObject * object)
   GESTimelinePrivate *priv = tl->priv;
   GList *tmp, *groups;
 
+  priv->disposed = TRUE;
   while (tl->layers) {
     GESLayer *layer = (GESLayer *) tl->layers->data;
     ges_timeline_remove_layer (GES_TIMELINE (object), layer);
@@ -287,21 +396,30 @@ ges_timeline_dispose (GObject * object)
    * objects aren't notified that their nleobjects have been destroyed.
    */
 
+  LOCK_DYN (tl);
   while (tl->tracks)
     ges_timeline_remove_track (GES_TIMELINE (object), tl->tracks->data);
+  UNLOCK_DYN (tl);
 
-  groups = g_list_copy (priv->groups);
+  /* NOTE: the timeline should not contain empty groups */
+  groups = g_list_copy_deep (priv->groups, (GCopyFunc) gst_object_ref, NULL);
   for (tmp = groups; tmp; tmp = tmp->next) {
     GList *elems = ges_container_ungroup (tmp->data, FALSE);
 
     g_list_free_full (elems, gst_object_unref);
   }
-  g_list_free (priv->groups);
-  g_list_free (groups);
+  g_list_free_full (groups, gst_object_unref);
+  g_list_free_full (priv->groups, gst_object_unref);
 
   g_list_free_full (priv->auto_transitions, gst_object_unref);
 
   g_hash_table_unref (priv->all_elements);
+  gst_object_unref (priv->stream_collection);
+
+  gst_clear_object (&priv->auto_transition_track);
+  gst_clear_object (&priv->new_track);
+  g_clear_error (&priv->track_selection_error);
+  priv->track_selection_error = NULL;
 
   G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
 }
@@ -317,13 +435,22 @@ ges_timeline_finalize (GObject * object)
   G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
 }
 
-
-
 static void
 ges_timeline_handle_message (GstBin * bin, GstMessage * message)
 {
   GESTimeline *timeline = GES_TIMELINE (bin);
 
+  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ASYNC_START) {
+    GST_INFO_OBJECT (timeline, "Dropping %" GST_PTR_FORMAT, message);
+    return;
+  }
+
+  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ASYNC_DONE) {
+    GST_INFO_OBJECT (timeline, "Dropping %" GST_PTR_FORMAT, message);
+
+    return;
+  }
+
   if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) {
     GstMessage *amessage = NULL;
     const GstStructure *mstructure = gst_message_get_structure (message);
@@ -341,7 +468,9 @@ ges_timeline_handle_message (GstBin * bin, GstMessage * message)
       GST_OBJECT_LOCK (timeline);
       if (timeline->priv->expected_async_done == 0) {
         amessage = gst_message_new_async_start (GST_OBJECT_CAST (bin));
+        LOCK_DYN (timeline);
         timeline->priv->expected_async_done = g_list_length (timeline->tracks);
+        UNLOCK_DYN (timeline);
         GST_INFO_OBJECT (timeline, "Posting ASYNC_START %s",
             gst_structure_get_string (mstructure, "reason"));
       }
@@ -368,14 +497,74 @@ ges_timeline_handle_message (GstBin * bin, GstMessage * message)
       GST_OBJECT_UNLOCK (timeline);
     }
 
-    if (amessage)
+    if (amessage) {
+      gst_message_unref (message);
       gst_element_post_message (GST_ELEMENT_CAST (bin), amessage);
+      return;
+    }
   }
 
 forward:
   gst_element_post_message (GST_ELEMENT_CAST (bin), message);
 }
 
+static GstStateChangeReturn
+ges_timeline_change_state (GstElement * element, GstStateChange transition)
+{
+  GstStateChangeReturn res;
+  GESTimeline *timeline = GES_TIMELINE (element);
+
+  res = GST_ELEMENT_CLASS (ges_timeline_parent_class)->change_state (element,
+      transition);
+
+  if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
+    gst_element_post_message ((GstElement *) timeline,
+        gst_message_new_stream_collection ((GstObject *) timeline,
+            timeline->priv->stream_collection));
+  return res;
+}
+
+static gboolean
+ges_timeline_send_event (GstElement * element, GstEvent * event)
+{
+  GESTimeline *timeline = GES_TIMELINE (element);
+
+  if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
+    GList *stream_ids = NULL, *tmp, *to_remove =
+        ges_timeline_get_tracks (timeline);
+
+    gst_event_parse_select_streams (event, &stream_ids);
+    for (tmp = stream_ids; tmp; tmp = tmp->next) {
+      GList *trackit;
+      gchar *stream_id = tmp->data;
+
+      LOCK_DYN (timeline);
+      for (trackit = timeline->priv->priv_tracks; trackit;
+          trackit = trackit->next) {
+        TrackPrivate *tr_priv = trackit->data;
+
+        if (!g_strcmp0 (gst_stream_get_stream_id (tr_priv->stream), stream_id)) {
+          to_remove = g_list_remove (to_remove, tr_priv->track);
+        }
+      }
+      UNLOCK_DYN (timeline);
+    }
+    for (tmp = to_remove; tmp; tmp = tmp->next) {
+      GST_INFO_OBJECT (timeline, "Removed unselected track: %" GST_PTR_FORMAT,
+          tmp->data);
+      ges_timeline_remove_track (timeline, tmp->data);
+    }
+
+    g_list_free_full (stream_ids, g_free);
+    g_list_free (to_remove);
+
+    return TRUE;
+  }
+
+  return GST_ELEMENT_CLASS (ges_timeline_parent_class)->send_event (element,
+      event);
+}
+
 /* we collect the first result */
 static gboolean
 _gst_array_accumulator (GSignalInvocationHint * ihint,
@@ -394,6 +583,7 @@ static void
 ges_timeline_class_init (GESTimelineClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GstElementClass *element_class = (GstElementClass *) klass;
   GstBinClass *bin_class = GST_BIN_CLASS (klass);
 
   GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline",
@@ -407,12 +597,17 @@ ges_timeline_class_init (GESTimelineClass * klass)
   object_class->dispose = ges_timeline_dispose;
   object_class->finalize = ges_timeline_finalize;
 
+  element_class->change_state = GST_DEBUG_FUNCPTR (ges_timeline_change_state);
+  element_class->send_event = GST_DEBUG_FUNCPTR (ges_timeline_send_event);
+
   bin_class->handle_message = GST_DEBUG_FUNCPTR (ges_timeline_handle_message);
 
   /**
    * GESTimeline:duration:
    *
-   * Current duration (in nanoseconds) of the #GESTimeline
+   * The current duration (in nanoseconds) of the timeline. A timeline
+   * 'starts' at time 0, so this is the maximum end time of all of its
+   * #GESTimelineElement-s.
    */
   properties[PROP_DURATION] =
       g_param_spec_uint64 ("duration", "Duration",
@@ -424,7 +619,10 @@ ges_timeline_class_init (GESTimelineClass * klass)
   /**
    * GESTimeline:auto-transition:
    *
-   * Sets whether transitions are added automagically when clips overlap.
+   * Whether to automatically create a transition whenever two
+   * #GESSource-s overlap in a track of the timeline. See
+   * #GESLayer:auto-transition if you want this to only happen in some
+   * layers.
    */
   g_object_class_install_property (object_class, PROP_AUTO_TRANSITION,
       g_param_spec_boolean ("auto-transition", "Auto-Transition",
@@ -433,98 +631,130 @@ ges_timeline_class_init (GESTimelineClass * klass)
   /**
    * GESTimeline:snapping-distance:
    *
-   * Distance (in nanoseconds) from which a moving object will snap
-   * with it neighboors. 0 means no snapping.
+   * The distance (in nanoseconds) at which a #GESTimelineElement being
+   * moved within the timeline should snap one of its #GESSource-s with
+   * another #GESSource-s edge. See #GESEditMode for which edges can
+   * snap during an edit. 0 means no snapping.
    */
   properties[PROP_SNAPPING_DISTANCE] =
       g_param_spec_uint64 ("snapping-distance", "Snapping distance",
-      "Distance from which moving an object will snap with neighboors", 0,
+      "Distance from which moving an object will snap with neighbours", 0,
       G_MAXUINT64, 0, G_PARAM_READWRITE);
   g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE,
       properties[PROP_SNAPPING_DISTANCE]);
 
   /**
    * GESTimeline::track-added:
-   * @timeline: the #GESTimeline
-   * @track: the #GESTrack that was added to the timeline
+   * @timeline: The #GESTimeline
+   * @track: The track that was added to @timeline
    *
-   * Will be emitted after the track was added to the timeline.
+   * Will be emitted after the track is added to the timeline.
+   *
+   * Note that this should not be emitted whilst a timeline is being
+   * loaded from its #GESProject asset. You should connect to the
+   * project's #GESProject::loaded signal if you want to know which
+   * tracks were created for the timeline.
    */
   ges_timeline_signals[TRACK_ADDED] =
       g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL,
-      NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_TRACK);
+      NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TRACK);
 
   /**
    * GESTimeline::track-removed:
-   * @timeline: the #GESTimeline
-   * @track: the #GESTrack that was removed from the timeline
+   * @timeline: The #GESTimeline
+   * @track: The track that was removed from @timeline
    *
-   * Will be emitted after the track was removed from the timeline.
+   * Will be emitted after the track is removed from the timeline.
    */
   ges_timeline_signals[TRACK_REMOVED] =
       g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_TRACK);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_TRACK);
 
   /**
    * GESTimeline::layer-added:
-   * @timeline: the #GESTimeline
-   * @layer: the #GESLayer that was added to the timeline
+   * @timeline: The #GESTimeline
+   * @layer: The layer that was added to @timeline
+   *
+   * Will be emitted after the layer is added to the timeline.
    *
-   * Will be emitted after a new layer is added to the timeline.
+   * Note that this should not be emitted whilst a timeline is being
+   * loaded from its #GESProject asset. You should connect to the
+   * project's #GESProject::loaded signal if you want to know which
+   * layers were created for the timeline.
    */
   ges_timeline_signals[LAYER_ADDED] =
       g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL,
-      NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_LAYER);
+      NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_LAYER);
 
   /**
    * GESTimeline::layer-removed:
-   * @timeline: the #GESTimeline
-   * @layer: the #GESLayer that was removed from the timeline
+   * @timeline: The #GESTimeline
+   * @layer: The layer that was removed from @timeline
    *
-   * Will be emitted after the layer was removed from the timeline.
+   * Will be emitted after the layer is removed from the timeline.
    */
   ges_timeline_signals[LAYER_REMOVED] =
       g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_LAYER);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_LAYER);
 
   /**
    * GESTimeline::group-added
-   * @timeline: the #GESTimeline
-   * @group: the #GESGroup
+   * @timeline: The #GESTimeline
+   * @group: The group that was added to @timeline
+   *
+   * Will be emitted after the group is added to to the timeline. This can
+   * happen when grouping with `ges_container_group`, or by adding
+   * containers to a newly created group.
    *
-   * Will be emitted after a new group is added to to the timeline.
+   * Note that this should not be emitted whilst a timeline is being
+   * loaded from its #GESProject asset. You should connect to the
+   * project's #GESProject::loaded signal if you want to know which groups
+   * were created for the timeline.
    */
   ges_timeline_signals[GROUP_ADDED] =
       g_signal_new ("group-added", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, group_added), NULL,
-      NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GES_TYPE_GROUP);
+      NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_GROUP);
 
   /**
    * GESTimeline::group-removed
-   * @timeline: the #GESTimeline
-   * @group: the #GESGroup
-   * @children: (element-type GES.Container) (transfer container): a list of #GESContainer
+   * @timeline: The #GESTimeline
+   * @group: The group that was removed from @timeline
+   * @children: (element-type GESContainer) (transfer none): A list
+   * of #GESContainer-s that _were_ the children of the removed @group
    *
-   * Will be emitted after a group has been removed from the timeline.
+   * Will be emitted after the group is removed from the timeline through
+   * `ges_container_ungroup`. Note that @group will no longer contain its
+   * former children, these are held in @children.
+   *
+   * Note that if a group is emptied, then it will no longer belong to the
+   * timeline, but this signal will **not** be emitted in such a case.
    */
   ges_timeline_signals[GROUP_REMOVED] =
       g_signal_new ("group-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, group_removed),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, GES_TYPE_GROUP,
-      G_TYPE_PTR_ARRAY);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, GES_TYPE_GROUP, G_TYPE_PTR_ARRAY);
 
   /**
    * GESTimeline::snapping-started:
-   * @timeline: the #GESTimeline
-   * @obj1: the first #GESTrackElement that was snapping.
-   * @obj2: the second #GESTrackElement that was snapping.
-   * @position: the position where the two objects finally snapping.
+   * @timeline: The #GESTimeline
+   * @obj1: The first element that is snapping
+   * @obj2: The second element that is snapping
+   * @position: The position where the two objects will snap to
+   *
+   * Will be emitted whenever an element's movement invokes a snapping
+   * event during an edit (usually of one of its ancestors) because its
+   * start or end point lies within the #GESTimeline:snapping-distance of
+   * another element's start or end point.
+   *
+   * See #GESEditMode to see what can snap during an edit.
    *
-   * Will be emitted when the 2 #GESTrackElement first snapped
+   * Note that only up to one snapping-started signal will be emitted per
+   * element edit within a timeline.
    */
   ges_timeline_signals[SNAPING_STARTED] =
       g_signal_new ("snapping-started", G_TYPE_FROM_CLASS (klass),
@@ -534,12 +764,16 @@ ges_timeline_class_init (GESTimelineClass * klass)
 
   /**
    * GESTimeline::snapping-ended:
-   * @timeline: the #GESTimeline
-   * @obj1: the first #GESTrackElement that was snapping.
-   * @obj2: the second #GESTrackElement that was snapping.
-   * @position: the position where the two objects finally snapping.
+   * @timeline: The #GESTimeline
+   * @obj1: The first element that was snapping
+   * @obj2: The second element that was snapping
+   * @position: The position where the two objects were to be snapped to
    *
-   * Will be emitted when the 2 #GESTrackElement ended to snap
+   * Will be emitted whenever a snapping event ends. After a snap event
+   * has started (see #GESTimeline::snapping-started), it can later end
+   * because either another timeline edit has occurred (which may or may
+   * not have created a new snapping event), or because the timeline has
+   * been committed.
    */
   ges_timeline_signals[SNAPING_ENDED] =
       g_signal_new ("snapping-ended", G_TYPE_FROM_CLASS (klass),
@@ -549,11 +783,65 @@ ges_timeline_class_init (GESTimelineClass * klass)
 
   /**
    * GESTimeline::select-tracks-for-object:
-   * @timeline: the #GESTimeline
-   * @clip: The #GESClip on which @track_element will land
-   * @track_element: The #GESTrackElement for which to choose the tracks it should land into
+   * @timeline: The #GESTimeline
+   * @clip: The clip that @track_element is being added to
+   * @track_element: The element being added
+   *
+   * This will be emitted whenever the timeline needs to determine which
+   * tracks a clip's children should be added to. The track element will
+   * be added to each of the tracks given in the return. If a track
+   * element is selected to go into multiple tracks, it will be copied
+   * into the additional tracks, under the same clip. Note that the copy
+   * will *not* keep its properties or state in sync with the original.
+   *
+   * Connect to this signal once if you wish to control which element
+   * should be added to which track. Doing so will overwrite the default
+   * behaviour, which adds @track_element to all tracks whose
+   * #GESTrack:track-type includes the @track_element's
+   * #GESTrackElement:track-type.
    *
-   * Returns: (transfer full) (element-type GESTrack): a #GPtrArray of #GESTrack-s where that object should be added
+   * Note that under the default track selection, if a clip would produce
+   * multiple core children of the same #GESTrackType, it will choose
+   * one of the core children arbitrarily to place in the corresponding
+   * tracks, with a warning for the other core children that are not
+   * placed in the track. For example, this would happen for a #GESUriClip
+   * that points to a file that contains multiple audio streams. If you
+   * wish to choose the stream, you could connect to this signal, and use,
+   * say, ges_uri_source_asset_get_stream_info() to choose which core
+   * source to add.
+   *
+   * When a clip is first added to a timeline, its core elements will
+   * be created for the current tracks in the timeline if they have not
+   * already been created. Then this will be emitted for each of these
+   * core children to select which tracks, if any, they should be added
+   * to. It will then be called for any non-core children in the clip.
+   *
+   * In addition, if a new track element is ever added to a clip in a
+   * timeline (and it is not already part of a track) this will be emitted
+   * to select which tracks the element should be added to.
+   *
+   * Finally, as a special case, if a track is added to the timeline
+   * *after* it already contains clips, then it will request the creation
+   * of the clips' core elements of the corresponding type, if they have
+   * not already been created, and this signal will be emitted for each of
+   * these newly created elements. In addition, this will also be released
+   * for all other track elements in the timeline's clips that have not
+   * yet been assigned a track. However, in this final case, the timeline
+   * will only check whether the newly added track appears in the track
+   * list. If it does appear, the track element will be added to the newly
+   * added track. All other tracks in the returned track list are ignored.
+   *
+   * In this latter case, track elements that are already part of a track
+   * will not be asked if they want to be copied into the new track. If
+   * you wish to do this, you can use ges_clip_add_child_to_track().
+   *
+   * Note that the returned #GPtrArray should own a new reference to each
+   * of its contained #GESTrack. The timeline will set the #GDestroyNotify
+   * free function on the #GPtrArray to dereference the elements.
+   *
+   * Returns: (transfer full) (element-type GESTrack): An array of
+   * #GESTrack-s that @track_element should be added to, or %NULL to
+   * not add the element to any track.
    */
   ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT] =
       g_signal_new ("select-tracks-for-object", G_TYPE_FROM_CLASS (klass),
@@ -561,12 +849,32 @@ ges_timeline_class_init (GESTimelineClass * klass)
       G_TYPE_PTR_ARRAY, 2, GES_TYPE_CLIP, GES_TYPE_TRACK_ELEMENT);
 
   /**
+   * GESTimeline::select-element-track:
+   * @timeline: The #GESTimeline
+   * @clip: The clip that @track_element is being added to
+   * @track_element: The element being added
+   *
+   * Simplified version of #GESTimeline::select-tracks-for-object which only
+   * allows @track_element to be added to a single #GESTrack.
+   *
+   * Returns: (transfer full): A track to put @track_element into, or %NULL if
+   * it should be discarded.
+   *
+   * Since: 1.18
+   */
+  ges_timeline_signals[SELECT_ELEMENT_TRACK] =
+      g_signal_new ("select-element-track", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+      GES_TYPE_TRACK, 2, GES_TYPE_CLIP, GES_TYPE_TRACK_ELEMENT);
+
+  /**
    * GESTimeline::commited:
-   * @timeline: the #GESTimeline
+   * @timeline: The #GESTimeline
    *
-   * This signal will be emitted once the changes initiated by #ges_timeline_commit
-   * have been executed in the backend. Use #ges_timeline_commit_sync if you
-   * don't need to do anything in the meantime.
+   * This signal will be emitted once the changes initiated by
+   * ges_timeline_commit() have been executed in the backend. Use
+   * ges_timeline_commit_sync() if you do not want to have to connect
+   * to this signal.
    */
   ges_timeline_signals[COMMITED] =
       g_signal_new ("commited", G_TYPE_FROM_CLASS (klass),
@@ -593,12 +901,12 @@ ges_timeline_init (GESTimeline * self)
   self->priv->last_snap_ts = GST_CLOCK_TIME_NONE;
 
   priv->priv_tracks = NULL;
-  priv->needs_transitions_update = TRUE;
 
   priv->all_elements =
       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, gst_object_unref);
 
   priv->stream_start_group_id = -1;
+  priv->stream_collection = gst_stream_collection_new (NULL);
 
   g_signal_connect_after (self, "select-tracks-for-object",
       G_CALLBACK (select_tracks_for_object_default), NULL);
@@ -610,18 +918,6 @@ ges_timeline_init (GESTimeline * self)
 
 /* Private methods */
 
-static inline GESContainer *
-get_toplevel_container (gpointer element)
-{
-  GESTimelineElement *ret =
-      ges_timeline_element_get_toplevel_parent ((GESTimelineElement
-          *) (element));
-
-  /*  We own a ref to the elements ourself */
-  gst_object_unref (ret);
-  return (GESContainer *) ret;
-}
-
 /* Sorting utils*/
 static gint
 sort_layers (gpointer a, gpointer b)
@@ -635,9 +931,9 @@ sort_layers (gpointer a, gpointer b)
   prio_a = ges_layer_get_priority (layer_a);
   prio_b = ges_layer_get_priority (layer_b);
 
-  if ((gint) prio_a > (guint) prio_b)
+  if (prio_a > prio_b)
     return 1;
-  if ((guint) prio_a < (guint) prio_b)
+  if (prio_a < prio_b)
     return -1;
 
   return 0;
@@ -703,25 +999,46 @@ ges_timeline_create_transition (GESTimeline * timeline,
     GESTrackElement * previous, GESTrackElement * next, GESClip * transition,
     GESLayer * layer, guint64 start, guint64 duration)
 {
-  GESAsset *asset;
   GESAutoTransition *auto_transition;
+  GESTrackElement *child;
+  /* track should not be NULL */
+  GESTrack *track = ges_track_element_get_track (next);
 
   if (transition == NULL) {
-    /* TODO make it possible to specify a Transition asset in the API */
+    GESAsset *asset;
+
+    LOCK_DYN (timeline);
+    timeline->priv->auto_transition_track = gst_object_ref (track);
+    UNLOCK_DYN (timeline);
+
     asset = ges_asset_request (GES_TYPE_TRANSITION_CLIP, "crossfade", NULL);
-    transition =
-        ges_layer_add_asset (layer, asset, start, 0, duration,
+    transition = ges_layer_add_asset (layer, asset, start, 0, duration,
         ges_track_element_get_track_type (next));
-    g_object_unref (asset);
+    gst_object_unref (asset);
+
+    LOCK_DYN (timeline);
+    /* should have been set to NULL, but clear just in case */
+    gst_clear_object (&timeline->priv->auto_transition_track);
+    UNLOCK_DYN (timeline);
   } else {
     GST_DEBUG_OBJECT (timeline,
         "Reusing already existing transition: %" GST_PTR_FORMAT, transition);
   }
 
+  g_return_val_if_fail (transition, NULL);
+  g_return_val_if_fail (g_list_length (GES_CONTAINER_CHILDREN (transition)) ==
+      1, NULL);
+  child = GES_CONTAINER_CHILDREN (transition)->data;
+  if (ges_track_element_get_track (child) != track) {
+    GST_ERROR_OBJECT (timeline, "The auto transition element %"
+        GES_FORMAT " for elements %" GES_FORMAT " and %" GES_FORMAT
+        " is not in the same track %" GST_PTR_FORMAT,
+        GES_ARGS (child), GES_ARGS (previous), GES_ARGS (next), track);
+    return NULL;
+  }
+
   /* We know there is only 1 TrackElement */
-  auto_transition =
-      ges_auto_transition_new (GES_CONTAINER_CHILDREN (transition)->data,
-      previous, next);
+  auto_transition = ges_auto_transition_new (child, previous, next);
 
   g_signal_connect (auto_transition, "destroy-me",
       G_CALLBACK (_destroy_auto_transition_cb), timeline);
@@ -758,6 +1075,37 @@ ges_timeline_find_auto_transition (GESTimeline * timeline,
   return NULL;
 }
 
+GESAutoTransition *
+ges_timeline_get_auto_transition_at_edge (GESTimeline * timeline,
+    GESTrackElement * source, GESEdge edge)
+{
+  GList *tmp, *auto_transitions;
+  GESAutoTransition *ret = NULL;
+
+  LOCK_DYN (timeline);
+  auto_transitions = g_list_copy_deep (timeline->priv->auto_transitions,
+      (GCopyFunc) gst_object_ref, NULL);
+  UNLOCK_DYN (timeline);
+
+  for (tmp = auto_transitions; tmp; tmp = tmp->next) {
+    GESAutoTransition *auto_trans = (GESAutoTransition *) tmp->data;
+
+    /* We already have a transition linked to one of the elements we want to
+     * find a transition for */
+    if (edge == GES_EDGE_END && auto_trans->previous_source == source) {
+      ret = gst_object_ref (auto_trans);
+      break;
+    } else if (edge == GES_EDGE_START && auto_trans->next_source == source) {
+      ret = gst_object_ref (auto_trans);
+      break;
+    }
+  }
+
+  g_list_free_full (auto_transitions, gst_object_unref);
+
+  return ret;
+}
+
 static GESAutoTransition *
 _create_auto_transition_from_transitions (GESTimeline * timeline,
     GESTrackElement * prev, GESTrackElement * next,
@@ -807,8 +1155,8 @@ _create_auto_transition_from_transitions (GESTimeline * timeline,
 }
 
 void
-ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1,
-    GESTimelineElement * elem2, GstClockTime snap_time)
+ges_timeline_emit_snapping (GESTimeline * timeline, GESTrackElement * elem1,
+    GESTrackElement * elem2, GstClockTime snap_time)
 {
   GESTimelinePrivate *priv = timeline->priv;
   GstClockTime last_snap_ts = timeline->priv->last_snap_ts;
@@ -826,207 +1174,181 @@ ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1,
   }
 
   g_assert (elem1 != elem2);
-  if (GES_IS_CLIP (elem1)) {
-    g_assert (GES_CONTAINER_CHILDREN (elem1));
-    elem1 = GES_CONTAINER_CHILDREN (elem1)->data;
-  }
 
-  if (GES_IS_CLIP (elem2)) {
-    g_assert (GES_CONTAINER_CHILDREN (elem2));
-    elem2 = GES_CONTAINER_CHILDREN (elem2)->data;
-  }
-
-  if (last_snap_ts != snap_time) {
+  if (GST_CLOCK_TIME_IS_VALID (last_snap_ts))
     g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
         priv->last_snaped1, priv->last_snaped2, (last_snap_ts));
 
-    /* We want the snap start signal to be emited anyway */
-    timeline->priv->last_snap_ts = GST_CLOCK_TIME_NONE;
-  }
-
-  if (!GST_CLOCK_TIME_IS_VALID (timeline->priv->last_snap_ts)) {
-    priv->last_snaped1 = (GESTrackElement *) elem1;
-    priv->last_snaped2 = (GESTrackElement *) elem2;
-    timeline->priv->last_snap_ts = snap_time;
-
-    g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
-        elem1, elem2, snap_time);
-  }
-
+  priv->last_snaped1 = elem1;
+  priv->last_snaped2 = elem2;
+  timeline->priv->last_snap_ts = snap_time;
+  g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
+      elem1, elem2, snap_time);
 }
 
-gboolean
-ges_timeline_trim_object_simple (GESTimeline * timeline,
-    GESTimelineElement * element, guint32 new_layer_priority,
-    GList * layers, GESEdge edge, guint64 position, gboolean snapping)
+/* Accept @self == NULL, making it use default framerate */
+void
+timeline_get_framerate (GESTimeline * self, gint * fps_n, gint * fps_d)
 {
+  GList *tmp;
 
-  return timeline_trim_object (timeline, element, new_layer_priority, layers,
-      edge, position);
-}
+  *fps_n = *fps_d = -1;
+  if (!self)
+    goto done;
 
-gboolean
-timeline_ripple_object (GESTimeline * timeline, GESTimelineElement * obj,
-    gint new_layer_priority, GList * layers, GESEdge edge, guint64 position)
-{
-  gboolean res = TRUE;
-  guint64 new_duration;
-  GstClockTimeDiff diff;
+  LOCK_DYN (self);
+  for (tmp = self->tracks; tmp; tmp = tmp->next) {
+    if (GES_IS_VIDEO_TRACK (tmp->data)) {
+      GstCaps *restriction = ges_track_get_restriction_caps (tmp->data);
+      gint i;
 
-  switch (edge) {
-    case GES_EDGE_NONE:
-      GST_DEBUG ("Simply rippling");
-      diff = GST_CLOCK_DIFF (position, _START (obj));
+      if (!restriction)
+        continue;
 
-      timeline->priv->needs_transitions_update = FALSE;
-      res = timeline_tree_ripple (timeline->priv->tree,
-          (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) -
-          (gint64) new_layer_priority, diff, obj,
-          GES_EDGE_NONE, timeline->priv->snapping_distance);
-      timeline->priv->needs_transitions_update = TRUE;
+      for (i = 0; i < gst_caps_get_size (restriction); i++) {
+        gint n, d;
 
-      break;
-    case GES_EDGE_END:
-      GST_DEBUG ("Rippling end");
-
-      timeline->priv->needs_transitions_update = FALSE;
-      new_duration =
-          CLAMP (position - obj->start, 0, obj->maxduration - obj->inpoint);
-      res =
-          timeline_tree_ripple (timeline->priv->tree,
-          (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) -
-          (gint64) new_layer_priority,
-          _DURATION (obj) - new_duration, obj,
-          GES_EDGE_END, timeline->priv->snapping_distance);
-      timeline->priv->needs_transitions_update = TRUE;
-
-      GST_DEBUG ("Done Rippling end");
-      break;
-    case GES_EDGE_START:
-      GST_INFO ("Ripple start doesn't make sense, trimming instead");
-      if (!timeline_trim_object (timeline, obj, -1, layers, edge, position))
-        goto error;
-      break;
-    default:
-      GST_DEBUG ("Can not ripple edge: %i", edge);
+        if (!gst_structure_get_fraction (gst_caps_get_structure (restriction,
+                    i), "framerate", &n, &d))
+          continue;
 
-      break;
-  }
-
-  return res;
+        if (*fps_n != -1 && *fps_d != -1 && !(n == *fps_n && d == *fps_d)) {
+          GST_WARNING_OBJECT (self,
+              "Various framerates specified, this is not supported"
+              " First one will be used.");
+          continue;
+        }
 
-error:
+        *fps_n = n;
+        *fps_d = d;
+      }
+      gst_caps_unref (restriction);
+    }
+  }
+  UNLOCK_DYN (self);
 
-  return FALSE;
+done:
+  if (*fps_n == -1 && *fps_d == -1) {
+    GST_INFO_OBJECT (self,
+        "No framerate found, using default " G_STRINGIFY (FRAMERATE_N) "/ "
+        G_STRINGIFY (FRAMERATE_D));
+    *fps_n = DEFAULT_FRAMERATE_N;
+    *fps_d = DEFAULT_FRAMERATE_D;
+  }
 }
 
-gboolean
-timeline_slide_object (GESTimeline * timeline, GESTrackElement * obj,
-    GList * layers, GESEdge edge, guint64 position)
+void
+ges_timeline_freeze_auto_transitions (GESTimeline * timeline, gboolean freeze)
 {
-
-  /* FIXME implement me! */
-  GST_FIXME_OBJECT (timeline, "Slide mode editing not implemented yet");
-
-  return FALSE;
+  GList *tmp, *trans = g_list_copy (timeline->priv->auto_transitions);
+  for (tmp = trans; tmp; tmp = tmp->next) {
+    GESAutoTransition *auto_transition = tmp->data;
+    auto_transition->frozen = freeze;
+    if (freeze == FALSE) {
+      GST_LOG_OBJECT (timeline, "Un-Freezing %" GES_FORMAT,
+          GES_ARGS (auto_transition->transition_clip));
+      ges_auto_transition_update (auto_transition);
+    } else {
+      GST_LOG_OBJECT (timeline, "Freezing %" GES_FORMAT,
+          GES_ARGS (auto_transition->transition_clip));
+    }
+  }
+  g_list_free (trans);
 }
 
-static gboolean
-_trim_transition (GESTimeline * timeline, GESTimelineElement * element,
-    GESEdge edge, GstClockTime position)
+static gint
+_edit_auto_transition (GESTimeline * timeline, GESTimelineElement * element,
+    gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
+    GstClockTime position, GError ** error)
 {
   GList *tmp;
-  GESLayer *layer = ges_timeline_get_layer (timeline,
-      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element));
+  guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
+  GESLayer *layer = ges_timeline_get_layer (timeline, layer_prio);
 
-  if (!ges_layer_get_auto_transition (layer))
-    goto fail;
+  if (!ges_layer_get_auto_transition (layer)) {
+    gst_object_unref (layer);
+    return -1;
+  }
 
   gst_object_unref (layer);
   for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
+    GESTimelineElement *replace;
     GESAutoTransition *auto_transition = tmp->data;
 
     if (GES_TIMELINE_ELEMENT (auto_transition->transition) == element ||
         GES_TIMELINE_ELEMENT (auto_transition->transition_clip) == element) {
-      /* Trimming an auto transition means trimming its neighboors */
-      if (!auto_transition->positioning) {
-        if (edge == GES_EDGE_END) {
-          ges_container_edit (GES_CONTAINER (auto_transition->previous_clip),
-              NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, position);
-        } else {
-          ges_container_edit (GES_CONTAINER (auto_transition->next_clip),
-              NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, position);
-        }
-
-        return TRUE;
+      if (auto_transition->positioning) {
+        GST_ERROR_OBJECT (element, "Trying to edit an auto-transition "
+            "whilst it is being positioned");
+        return FALSE;
+      }
+      if (new_layer_priority != layer_prio) {
+        GST_WARNING_OBJECT (element, "Cannot edit an auto-transition to a "
+            "new layer");
+        return FALSE;
+      }
+      if (mode != GES_EDIT_MODE_TRIM) {
+        GST_WARNING_OBJECT (element, "Cannot edit an auto-transition "
+            "under the edit mode %i", mode);
+        return FALSE;
       }
 
-      return FALSE;
+      if (edge == GES_EDGE_END)
+        replace = GES_TIMELINE_ELEMENT (auto_transition->previous_source);
+      else
+        replace = GES_TIMELINE_ELEMENT (auto_transition->next_source);
+
+      GST_INFO_OBJECT (element, "Trimming %" GES_FORMAT " in place  of "
+          "trimming the corresponding auto-transition", GES_ARGS (replace));
+      return ges_timeline_element_edit_full (replace, -1, mode, edge,
+          position, error);
     }
   }
 
-  return FALSE;
-
-fail:
-  gst_object_unref (layer);
-  return FALSE;
+  return -1;
 }
 
-
 gboolean
-timeline_trim_object (GESTimeline * timeline, GESTimelineElement * object,
-    guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position)
-{
-  if ((GES_IS_TRANSITION (object) || GES_IS_TRANSITION_CLIP (object)) &&
-      !ELEMENT_FLAG_IS_SET (object, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
-    return _trim_transition (timeline, object, edge, position);
+ges_timeline_edit (GESTimeline * timeline, GESTimelineElement * element,
+    gint64 new_layer_priority, GESEditMode mode, GESEdge edge,
+    guint64 position, GError ** error)
+{
+  GstClockTimeDiff edge_diff = (edge == GES_EDGE_END ?
+      GST_CLOCK_DIFF (position, element->start + element->duration) :
+      GST_CLOCK_DIFF (position, element->start));
+  gint64 prio_diff = (gint64) ges_timeline_element_get_layer_priority (element)
+      - new_layer_priority;
+  gint res = -1;
+
+  if ((GES_IS_TRANSITION (element) || GES_IS_TRANSITION_CLIP (element)))
+    res = _edit_auto_transition (timeline, element, new_layer_priority, mode,
+        edge, position, error);
+
+  if (res != -1)
+    return res;
+
+  switch (mode) {
+    case GES_EDIT_MODE_RIPPLE:
+      return timeline_tree_ripple (timeline->priv->tree, element, prio_diff,
+          edge_diff, edge, timeline->priv->snapping_distance, error);
+    case GES_EDIT_MODE_TRIM:
+      return timeline_tree_trim (timeline->priv->tree, element, prio_diff,
+          edge_diff, edge, timeline->priv->snapping_distance, error);
+    case GES_EDIT_MODE_NORMAL:
+      return timeline_tree_move (timeline->priv->tree, element, prio_diff,
+          edge_diff, edge, timeline->priv->snapping_distance, error);
+    case GES_EDIT_MODE_ROLL:
+      if (prio_diff != 0) {
+        GST_WARNING_OBJECT (element, "Cannot roll an element to a new layer");
+        return FALSE;
+      }
+      return timeline_tree_roll (timeline->priv->tree, element,
+          edge_diff, edge, timeline->priv->snapping_distance, error);
+    case GES_EDIT_MODE_SLIDE:
+      GST_ERROR_OBJECT (element, "Sliding not implemented.");
+      return FALSE;
   }
-
-  return timeline_tree_trim (timeline->priv->tree,
-      GES_TIMELINE_ELEMENT (object), new_layer_priority > 0 ? (gint64)
-      ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) -
-      new_layer_priority : 0, edge == GES_EDGE_END ? GST_CLOCK_DIFF (position,
-          _START (object) + _DURATION (object)) : GST_CLOCK_DIFF (position,
-          GES_TIMELINE_ELEMENT_START (object)), edge,
-      timeline->priv->snapping_distance);
-}
-
-gboolean
-timeline_roll_object (GESTimeline * timeline, GESTimelineElement * element,
-    GList * layers, GESEdge edge, guint64 position)
-{
-  return timeline_tree_roll (timeline->priv->tree,
-      element,
-      (edge == GES_EDGE_END) ?
-      GST_CLOCK_DIFF (position, _END (element)) :
-      GST_CLOCK_DIFF (position, _START (element)),
-      edge, timeline->priv->snapping_distance);
-}
-
-gboolean
-timeline_move_object (GESTimeline * timeline, GESTimelineElement * object,
-    guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position)
-{
-  gboolean ret = FALSE;
-  GstClockTimeDiff offset = edge == GES_EDGE_END ?
-      GST_CLOCK_DIFF (position, _START (object) + _DURATION (object)) :
-      GST_CLOCK_DIFF (position, GES_TIMELINE_ELEMENT_START (object));
-
-  ret = timeline_tree_move (timeline->priv->tree,
-      GES_TIMELINE_ELEMENT (object), new_layer_priority < 0 ? 0 : (gint64)
-      ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) -
-      new_layer_priority, offset, edge, timeline->priv->snapping_distance);
-
-  return ret;
-}
-
-gboolean
-ges_timeline_move_object_simple (GESTimeline * timeline,
-    GESTimelineElement * element, GList * layers, GESEdge edge,
-    guint64 position)
-{
-  return timeline_move_object (timeline, element,
-      ges_timeline_element_get_layer_priority (element), NULL, edge, position);
+  return FALSE;
 }
 
 void
@@ -1040,20 +1362,9 @@ timeline_add_group (GESTimeline * timeline, GESGroup * group)
   ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline);
 }
 
-void
-timeline_update_transition (GESTimeline * timeline)
-{
-  GList *tmp, *auto_transs;
-
-  auto_transs = g_list_copy (timeline->priv->auto_transitions);
-  for (tmp = auto_transs; tmp; tmp = tmp->next)
-    ges_auto_transition_update (tmp->data);
-  g_list_free (auto_transs);
-}
-
 /**
  * timeline_emit_group_added:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  * @group: group that was added
  *
  * Emit group-added signal.
@@ -1066,7 +1377,7 @@ timeline_emit_group_added (GESTimeline * timeline, GESGroup * group)
 
 /**
  * timeline_emit_group_removed:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  * @group: group that was removed
  *
  * Emit group-removed signal.
@@ -1090,231 +1401,493 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group)
   gst_object_unref (group);
 }
 
+static GESTrackElement *
+_core_in_track (GESTrack * track, GESClip * clip)
+{
+  GList *tmp;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *el = tmp->data;
+    if (ges_track_element_is_core (el)
+        && ges_track_element_get_track (el) == track) {
+      return tmp->data;
+    }
+  }
+  return NULL;
+}
+
 static GPtrArray *
 select_tracks_for_object_default (GESTimeline * timeline,
     GESClip * clip, GESTrackElement * tr_object, gpointer user_data)
 {
   GPtrArray *result;
   GList *tmp;
+  GESTrackElement *core;
 
   result = g_ptr_array_new ();
 
+  LOCK_DYN (timeline);
   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
     GESTrack *track = GES_TRACK (tmp->data);
 
     if ((track->type & ges_track_element_get_track_type (tr_object))) {
+      if (ges_track_element_is_core (tr_object)) {
+        core = _core_in_track (track, clip);
+        if (core) {
+          GST_WARNING_OBJECT (timeline, "The clip '%s' contains multiple "
+              "core elements of the same %s track type. The core child "
+              "'%s' has already been chosen arbitrarily for the track %"
+              GST_PTR_FORMAT ", which means that the other core child "
+              "'%s' of the same type can not be added to the track. "
+              "Consider connecting to "
+              "GESTimeline::select-tracks-for-objects to be able to "
+              "specify which core element should land in the track",
+              GES_TIMELINE_ELEMENT_NAME (clip),
+              ges_track_type_name (track->type),
+              GES_TIMELINE_ELEMENT_NAME (core), track,
+              GES_TIMELINE_ELEMENT_NAME (tr_object));
+          continue;
+        }
+      }
       gst_object_ref (track);
       g_ptr_array_add (result, track);
     }
   }
+  UNLOCK_DYN (timeline);
 
   return result;
 }
 
-static void
-add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
+static GPtrArray *
+_get_selected_tracks (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element)
 {
-  gint i;
-  GList *tmp, *list;
-  GESTrackType types, visited_type = GES_TRACK_TYPE_UNKNOWN;
+  guint i, j;
+  GPtrArray *tracks = NULL;
+  GESTrack *track = NULL;
 
-  GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
-      " trackelements and adding them to our tracks", clip);
+  g_signal_emit (G_OBJECT (timeline),
+      ges_timeline_signals[SELECT_ELEMENT_TRACK], 0, clip, track_element,
+      &track);
 
-  types = ges_clip_get_supported_formats (clip);
   if (track) {
-    if ((types & track->type) == 0)
-      return;
-    types = track->type;
-  }
+    tracks = g_ptr_array_new ();
 
-  for (i = 0, tmp = timeline->tracks; tmp; tmp = tmp->next, i++) {
-    GESTrack *track = GES_TRACK (tmp->data);
+    g_ptr_array_add (tracks, track);
+  } else {
+    g_signal_emit (G_OBJECT (timeline),
+        ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
+        &tracks);
+  }
+
+  if (tracks == NULL)
+    tracks = g_ptr_array_new ();
+
+  g_ptr_array_set_free_func (tracks, gst_object_unref);
+
+  /* make sure unique */
+  for (i = 0; i < tracks->len;) {
+    GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
+
+    for (j = i + 1; j < tracks->len;) {
+      if (track == g_ptr_array_index (tracks, j)) {
+        GST_WARNING_OBJECT (timeline, "Found the track %" GST_PTR_FORMAT
+            " more than once in the return for select-tracks-for-object "
+            "signal for track element %" GES_FORMAT " in clip %"
+            GES_FORMAT ". Ignoring the extra track", track,
+            GES_ARGS (track_element), GES_ARGS (clip));
+        g_ptr_array_remove_index (tracks, j);
+        /* don't increase index since the next track is in its place */
+        continue;
+      }
+      j++;
+    }
 
-    if (((track->type & types) == 0 || (track->type & visited_type)))
+    if (ges_track_get_timeline (track) != timeline) {
+      GST_WARNING_OBJECT (timeline, "The track %" GST_PTR_FORMAT
+          " found in the return for select-tracks-for-object belongs "
+          "to a different timeline %" GST_PTR_FORMAT ". Ignoring this "
+          "track", track, ges_track_get_timeline (track));
+      g_ptr_array_remove_index (tracks, i);
+      /* don't increase index since the next track is in its place */
       continue;
+    }
+    i++;
+  }
 
-    list = ges_clip_create_track_elements (clip, track->type);
-    g_list_free (list);
+  return tracks;
+}
+
+/* returns TRUE if track element was successfully added to all the
+ * selected tracks */
+static gboolean
+_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, GError ** error)
+{
+  guint i;
+  gboolean ret = TRUE;
+  GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
+
+  for (i = 0; i < tracks->len; i++) {
+    GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
+    if (!ges_clip_add_child_to_track (clip, track_element, track, error)) {
+      ret = FALSE;
+      if (error)
+        break;
+    }
   }
+
+  g_ptr_array_unref (tracks);
+
+  return ret;
 }
 
-static void
-layer_auto_transition_changed_cb (GESLayer * layer,
-    GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
+static gboolean
+_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, GESTrack * track, GError ** error)
 {
-  GList *tmp, *clips;
+  gboolean no_error = TRUE;
+  GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
 
-  timeline_tree_create_transitions (timeline->priv->tree,
-      _create_auto_transition_from_transitions);
-  clips = ges_layer_get_clips (layer);
-  for (tmp = clips; tmp; tmp = tmp->next) {
-    if (GES_IS_TRANSITION_CLIP (tmp->data)) {
-      GList *tmpautotrans;
-      gboolean found = FALSE;
+  /* if we are trying to add the element to a newly added track, then
+   * we only check whether the track list contains the newly added track,
+   * if it does we add the track element to the track, or add a copy if
+   * the track element is already in a track */
+  if (g_ptr_array_find (tracks, track, NULL)) {
+    if (!ges_clip_add_child_to_track (clip, track_element, track, error))
+      no_error = FALSE;
+  }
 
-      for (tmpautotrans = timeline->priv->auto_transitions; tmpautotrans;
-          tmpautotrans = tmpautotrans->next) {
-        if (GES_AUTO_TRANSITION (tmpautotrans->data)->transition_clip ==
-            tmp->data) {
-          found = TRUE;
-          break;
-        }
-      }
+  g_ptr_array_unref (tracks);
+  return no_error;
+}
 
-      if (!found) {
-        GST_ERROR_OBJECT (timeline,
-            "Transition %s could not be wrapped into an auto transition"
-            " REMOVING it", GES_TIMELINE_ELEMENT_NAME (tmp->data));
+/* accepts NULL */
+void
+ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving)
+{
+  if (timeline) {
+    LOCK_DYN (timeline);
+    timeline->priv->track_elements_moving = moving;
+    UNLOCK_DYN (timeline);
+  }
+}
 
-        ges_layer_remove_clip (layer, tmp->data);
-      }
+void
+ges_timeline_set_track_selection_error (GESTimeline * timeline,
+    gboolean was_error, GError * error)
+{
+  GESTimelinePrivate *priv;
+
+  LOCK_DYN (timeline);
+
+  priv = timeline->priv;
+  g_clear_error (&priv->track_selection_error);
+  priv->track_selection_error = error;
+  priv->has_any_track_selection_error = was_error;
+
+  UNLOCK_DYN (timeline);
+}
+
+gboolean
+ges_timeline_take_track_selection_error (GESTimeline * timeline,
+    GError ** error)
+{
+  gboolean ret;
+  GESTimelinePrivate *priv;
+
+  LOCK_DYN (timeline);
+
+  priv = timeline->priv;
+  if (error) {
+    if (*error) {
+      GST_ERROR_OBJECT (timeline, "Error not handled %s", (*error)->message);
+      g_error_free (*error);
     }
+    *error = priv->track_selection_error;
+  } else if (priv->track_selection_error) {
+    GST_WARNING_OBJECT (timeline, "Got track selection error: %s",
+        priv->track_selection_error->message);
+    g_error_free (priv->track_selection_error);
   }
-  g_list_free_full (clips, gst_object_unref);
+  priv->track_selection_error = NULL;
+  ret = priv->has_any_track_selection_error;
+  priv->has_any_track_selection_error = FALSE;
+
+  UNLOCK_DYN (timeline);
+
+  return ret;
 }
 
 static void
 clip_track_element_added_cb (GESClip * clip,
     GESTrackElement * track_element, GESTimeline * timeline)
 {
-  guint i;
-  GESTrack *track;
-  gboolean is_source;
-  GPtrArray *tracks = NULL;
-  GESTrackElement *existing_src = NULL;
-
-  if (timeline->priv->ignore_track_element_added == clip) {
-    GST_DEBUG_OBJECT (timeline, "Ignoring element added (%" GST_PTR_FORMAT
-        " in %" GST_PTR_FORMAT, track_element, clip);
+  GESTrack *auto_trans_track, *new_track;
+  GError *error = NULL;
+  gboolean success = FALSE;
 
+  if (timeline->priv->track_elements_moving) {
+    GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT
+        " in %" GES_FORMAT, GES_ARGS (track_element), GES_ARGS (clip));
     return;
   }
 
-  if (ges_track_element_get_track (track_element)) {
-    GST_WARNING_OBJECT (track_element, "Already in a track");
-
+  if (ges_track_element_get_track (track_element) != NULL) {
+    GST_DEBUG_OBJECT (timeline, "Not selecting tracks for %" GES_FORMAT
+        " in %" GES_FORMAT " because it already part of the track %"
+        GST_PTR_FORMAT, GES_ARGS (track_element), GES_ARGS (clip),
+        ges_track_element_get_track (track_element));
     return;
   }
 
-  g_signal_emit (G_OBJECT (timeline),
-      ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
-      &tracks);
+  LOCK_DYN (timeline);
+  /* take ownership of auto_transition_track. For auto-transitions, this
+   * should be used exactly once! */
+  auto_trans_track = timeline->priv->auto_transition_track;
+  timeline->priv->auto_transition_track = NULL;
+  /* don't take ownership of new_track */
+  new_track = timeline->priv->new_track;
+  UNLOCK_DYN (timeline);
 
-  if (!tracks || tracks->len == 0) {
-    GST_WARNING_OBJECT (timeline, "Got no Track to add %p (type %s), removing"
-        " from clip (stopping 'child-added' signal emission).",
-        track_element, ges_track_type_name (ges_track_element_get_track_type
-            (track_element)));
+  if (auto_trans_track) {
+    /* don't use track-selection */
+    success = ! !ges_clip_add_child_to_track (clip, track_element,
+        auto_trans_track, &error);
+    gst_object_unref (auto_trans_track);
+  } else {
+    if (new_track)
+      success = _try_add_track_element_to_track (timeline, clip, track_element,
+          new_track, &error);
+    else
+      success = _add_track_element_to_tracks (timeline, clip, track_element,
+          &error);
+  }
 
-    if (tracks)
-      g_ptr_array_unref (tracks);
+  if (error || !success) {
+    if (!error)
+      GST_WARNING_OBJECT (timeline, "Track selection failed for %" GES_FORMAT,
+          GES_ARGS (track_element));
+    ges_timeline_set_track_selection_error (timeline, TRUE, error);
+  }
+}
+
+static void
+clip_track_element_removed_cb (GESClip * clip,
+    GESTrackElement * track_element, GESTimeline * timeline)
+{
+  GESTrack *track = ges_track_element_get_track (track_element);
 
-    g_signal_stop_emission_by_name (clip, "child-added");
-    ges_container_remove (GES_CONTAINER (clip),
-        GES_TIMELINE_ELEMENT (track_element));
+  if (timeline->priv->track_elements_moving) {
+    GST_DEBUG_OBJECT (timeline, "Ignoring element removed (%" GST_PTR_FORMAT
+        " in %" GST_PTR_FORMAT, track_element, clip);
 
     return;
   }
 
-  /* We add the current element to the first track */
-  track = g_ptr_array_index (tracks, 0);
+  if (track) {
+    /* if we have non-core elements in the same track, they should be
+     * removed from them to preserve the rule that a non-core can only be
+     * in the same track as a core element from the same clip */
+    if (ges_track_element_is_core (track_element))
+      ges_clip_empty_from_track (clip, track);
+    ges_track_remove_element (track, track_element);
+  }
+}
+
+static void
+track_element_added_cb (GESTrack * track, GESTrackElement * element,
+    GESTimeline * timeline)
+{
+  if (GES_IS_SOURCE (element))
+    timeline_tree_create_transitions_for_track_element (timeline->priv->tree,
+        element, ges_timeline_find_auto_transition);
+}
+
+/* returns TRUE if no errors in adding to tracks */
+static gboolean
+_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip,
+    gboolean add_core, GESTrack * new_track, GList * blacklist, GError ** error)
+{
+  GList *tmp, *children;
+  gboolean no_errors = TRUE;
+
+  /* list of children may change if some are copied into tracks */
+  children = ges_container_get_children (GES_CONTAINER (clip), FALSE);
+  for (tmp = children; tmp; tmp = tmp->next) {
+    GESTrackElement *el = tmp->data;
+    if (ges_track_element_is_core (el) != add_core)
+      continue;
+    if (g_list_find (blacklist, el))
+      continue;
+    if (ges_track_element_get_track (el) == NULL) {
+      gboolean res;
+      if (new_track)
+        res = _try_add_track_element_to_track (timeline, clip, el, new_track,
+            error);
+      else
+        res = _add_track_element_to_tracks (timeline, clip, el, error);
+      if (!res) {
+        no_errors = FALSE;
+        if (error)
+          goto done;
+      }
+    }
+  }
+
+done:
+  g_list_free_full (children, gst_object_unref);
+
+  return no_errors;
+}
 
-  is_source = g_type_is_a (G_OBJECT_TYPE (track_element), GES_TYPE_SOURCE);
-  if (is_source)
-    existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
+/* returns TRUE if no errors in adding to tracks */
+static gboolean
+add_object_to_tracks (GESTimeline * timeline, GESClip * clip,
+    GESTrack * new_track, GError ** error)
+{
+  GList *tracks, *tmp, *list, *created, *just_added = NULL;
+  gboolean no_errors = TRUE;
+
+  GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
+      " trackelements and adding them to our tracks", clip);
+
+  LOCK_DYN (timeline);
+  tracks =
+      g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
+  timeline->priv->new_track = new_track ? gst_object_ref (new_track) : NULL;
+  UNLOCK_DYN (timeline);
+
+  /* create core elements */
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    GESTrack *track = GES_TRACK (tmp->data);
+    if (new_track && track != new_track)
+      continue;
+
+    list = ges_clip_create_track_elements (clip, track->type);
+    /* just_added only used for pointer comparison, so safe to include
+     * elements that may be destroyed because they fail to be added to
+     * the clip */
+    just_added = g_list_concat (just_added, list);
+
+    for (created = list; created; created = created->next) {
+      GESTimelineElement *el = created->data;
+
+      gst_object_ref (el);
+
+      /* make track selection be handled by clip_track_element_added_cb
+       * This is needed for backward-compatibility: when adding a clip to
+       * a layer, the track is set for the core elements of the clip
+       * during the child-added signal emission, just before the user's
+       * own connection.
+       * NOTE: for the children that have not just been created, they
+       * are already part of the clip and so child-added will not be
+       * released. And when a child is selected for multiple tracks, their
+       * copy will be added to the clip before the track is selected, so
+       * the track will not be set in the child-added signal */
+      ges_timeline_set_track_selection_error (timeline, FALSE, NULL);
+      ges_clip_set_add_error (clip, NULL);
+      if (!ges_container_add (GES_CONTAINER (clip), el)) {
+        no_errors = FALSE;
+        if (!error)
+          GST_ERROR_OBJECT (clip, "Could not add the core element %s "
+              "to the clip", el->name);
+      }
+      gst_object_unref (el);
+      ges_clip_take_add_error (clip, error);
+
+      if (error && !no_errors)
+        goto done;
+
+      if (ges_timeline_take_track_selection_error (timeline, error)) {
+        no_errors = FALSE;
+        if (error)
+          goto done;
+        /* else, carry on as much as we can */
+      }
+    }
+  }
+
+  /* set the tracks for the other children, with core elements first to
+   * make sure the non-core can be placed above them in the track (a
+   * non-core can not be in a track by itself) */
+  /* include just_added as a blacklist to ensure we do not try the track
+   * selection a second time when track selection returns no tracks */
+  if (!_add_clip_children_to_tracks (timeline, clip, TRUE, new_track,
+          just_added, error)) {
+    no_errors = FALSE;
+    if (error)
+      goto done;
+  }
 
-  if (existing_src == NULL) {
-    if (!ges_track_add_element (track, track_element)) {
-      GST_WARNING_OBJECT (clip, "Failed to add track element to track");
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element));
-      g_ptr_array_unref (tracks);
-      return;
-    }
-  } else {
-    GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
-        " of type %s, removing new one. (stopping 'child-added' emission)",
-        track, G_OBJECT_TYPE_NAME (track_element));
-    g_signal_stop_emission_by_name (clip, "child-added");
-    ges_container_remove (GES_CONTAINER (clip),
-        GES_TIMELINE_ELEMENT (track_element));
+  if (!_add_clip_children_to_tracks (timeline, clip, FALSE, new_track,
+          just_added, error)) {
+    no_errors = FALSE;
+    if (error)
+      goto done;
   }
-  gst_object_unref (track);
-  g_clear_object (&existing_src);
-
-  /* And create copies to add to other tracks */
-  timeline->priv->ignore_track_element_added = clip;
-  for (i = 1; i < tracks->len; i++) {
-    GESTrack *track;
-    GESTrackElement *track_element_copy;
-
-    track = g_ptr_array_index (tracks, i);
-    if (is_source)
-      existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
-    if (existing_src == NULL) {
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element));
-      gst_object_unref (track);
-      g_ptr_array_unref (tracks);
-      continue;
-    } else {
-      GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
-          " of type %s, removing new one. (stopping 'child-added' emission)",
-          track, G_OBJECT_TYPE_NAME (track_element));
-      g_signal_stop_emission_by_name (clip, "child-added");
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element));
-    }
-    g_clear_object (&existing_src);
 
-    track_element_copy =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (track_element), TRUE));
+done:
+  g_list_free_full (tracks, gst_object_unref);
 
-    GST_LOG_OBJECT (timeline, "Trying to add %p to track %p",
-        track_element_copy, track);
+  LOCK_DYN (timeline);
+  gst_clear_object (&timeline->priv->new_track);
+  UNLOCK_DYN (timeline);
 
-    if (!ges_container_add (GES_CONTAINER (clip),
-            GES_TIMELINE_ELEMENT (track_element_copy))) {
-      GST_WARNING_OBJECT (clip, "Failed to add track element to clip");
-      gst_object_unref (track_element_copy);
-      g_ptr_array_unref (tracks);
-      return;
-    }
+  g_list_free (just_added);
 
-    if (!ges_track_add_element (track, track_element_copy)) {
-      GST_WARNING_OBJECT (clip, "Failed to add track element to track");
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element_copy));
-      gst_object_unref (track_element_copy);
-      g_ptr_array_unref (tracks);
-      return;
-    }
+  return no_errors;
+}
 
-    gst_object_unref (track);
-  }
-  timeline->priv->ignore_track_element_added = NULL;
-  g_ptr_array_unref (tracks);
-  if (GES_IS_SOURCE (track_element))
-    timeline_tree_create_transitions (timeline->priv->tree,
-        ges_timeline_find_auto_transition);
+static void
+layer_active_changed_cb (GESLayer * layer, gboolean active G_GNUC_UNUSED,
+    GPtrArray * tracks G_GNUC_UNUSED, GESTimeline * timeline)
+{
+  timeline_tree_reset_layer_active (timeline->priv->tree, layer);
 }
 
 static void
-clip_track_element_removed_cb (GESClip * clip,
-    GESTrackElement * track_element, GESTimeline * timeline)
+layer_auto_transition_changed_cb (GESLayer * layer,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
 {
-  GESTrack *track = ges_track_element_get_track (track_element);
+  GList *tmp, *clips;
 
-  if (track)
-    ges_track_remove_element (track, track_element);
+  timeline_tree_create_transitions (timeline->priv->tree,
+      _create_auto_transition_from_transitions);
+  clips = ges_layer_get_clips (layer);
+  for (tmp = clips; tmp; tmp = tmp->next) {
+    if (GES_IS_TRANSITION_CLIP (tmp->data)) {
+      GList *tmpautotrans;
+      gboolean found = FALSE;
+
+      for (tmpautotrans = timeline->priv->auto_transitions; tmpautotrans;
+          tmpautotrans = tmpautotrans->next) {
+        if (GES_AUTO_TRANSITION (tmpautotrans->data)->transition_clip ==
+            tmp->data) {
+          found = TRUE;
+          break;
+        }
+      }
+
+      if (!found) {
+        GST_ERROR_OBJECT (timeline,
+            "Transition %s could not be wrapped into an auto transition"
+            " REMOVING it", GES_TIMELINE_ELEMENT_NAME (tmp->data));
+
+        ges_layer_remove_clip (layer, tmp->data);
+      }
+    }
+  }
+  g_list_free_full (clips, gst_object_unref);
 }
 
-static void
-layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
+/* returns TRUE if selecting of tracks did not error */
+gboolean
+ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip, GError ** error)
 {
   GESProject *project;
+  gboolean ret;
+
+  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), timeline);
 
   /* We make sure not to be connected twice */
   g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
@@ -1328,23 +1901,24 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
   g_signal_connect (clip, "child-removed",
       G_CALLBACK (clip_track_element_removed_cb), timeline);
 
-  if (ges_clip_is_moving_from_layer (clip)) {
-    GST_DEBUG ("Clip %p moving from one layer to another, not creating "
-        "TrackElement", clip);
-    timeline_tree_create_transitions (timeline->priv->tree,
-        ges_timeline_find_auto_transition);
-    return;
-  }
-
-  add_object_to_tracks (timeline, clip, NULL);
-
   GST_DEBUG ("Making sure that the asset is in our project");
   project =
       GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
   ges_project_add_asset (project,
       ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
 
+  if (ges_clip_is_moving_from_layer (clip)) {
+    GST_DEBUG ("Clip %p moving from one layer to another, not creating "
+        "TrackElement", clip);
+    /* timeline-tree handles creation of auto-transitions */
+    ret = TRUE;
+  } else {
+    ret = add_object_to_tracks (timeline, clip, NULL, error);
+  }
+
   GST_DEBUG ("Done");
+
+  return ret;
 }
 
 static void
@@ -1358,11 +1932,10 @@ layer_priority_changed_cb (GESLayer * layer,
       sort_layers);
 }
 
-static void
-layer_object_removed_cb (GESLayer * layer, GESClip * clip,
-    GESTimeline * timeline)
+void
+ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip)
 {
-  GList *trackelements, *tmp;
+  GList *tmp;
 
   if (ges_clip_is_moving_from_layer (clip)) {
     GST_DEBUG ("Clip %p is moving from a layer to another, not doing"
@@ -1370,73 +1943,59 @@ layer_object_removed_cb (GESLayer * layer, GESClip * clip,
     return;
   }
 
-  GST_DEBUG ("Clip %p removed from layer %p", clip, layer);
-
-  /* Go over the clip's track element and figure out which one belongs to
-   * the list of tracks we control */
-
-  trackelements = ges_container_get_children (GES_CONTAINER (clip), FALSE);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    GESTrackElement *track_element = (GESTrackElement *) tmp->data;
-    GESTrack *track = ges_track_element_get_track (track_element);
-
-    if (!track)
-      continue;
-
-    GST_DEBUG_OBJECT (timeline, "Trying to remove TrackElement %p",
-        track_element);
+  GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer",
+      GES_ARGS (clip));
 
-    /* FIXME Check if we should actually check that we control the
-     * track in the new management of TrackElement context */
-    LOCK_DYN (timeline);
-    if (G_LIKELY (g_list_find_custom (timeline->priv->priv_tracks, track,
-                (GCompareFunc) custom_find_track) || track == NULL)) {
-      GST_DEBUG ("Belongs to one of the tracks we control");
+  LOCK_DYN (timeline);
+  for (tmp = timeline->tracks; tmp; tmp = tmp->next)
+    ges_clip_empty_from_track (clip, tmp->data);
+  UNLOCK_DYN (timeline);
 
-      ges_track_remove_element (track, track_element);
-    }
-    UNLOCK_DYN (timeline);
-  }
   g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
       timeline);
   g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb,
       timeline);
 
-  g_list_free_full (trackelements, gst_object_unref);
+  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL);
 
   GST_DEBUG ("Done");
 }
 
-static void
-trackelement_start_changed_cb (GESTrackElement * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
+static gboolean
+update_stream_object (TrackPrivate * tr_priv)
 {
-  timeline_update_duration (timeline);
-}
+  gboolean res = FALSE;
+  GstStreamType type = GST_STREAM_TYPE_UNKNOWN;
+  gchar *stream_id;
 
-static void
-track_element_added_cb (GESTrack * track, GESTrackElement * track_element,
-    GESTimeline * timeline)
-{
-  /* Auto transition should be updated before we receive the signal */
-  g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::start",
-      G_CALLBACK (trackelement_start_changed_cb), timeline);
-}
+  g_object_get (tr_priv->track, "id", &stream_id, NULL);
+  if (tr_priv->track->type == GES_TRACK_TYPE_VIDEO)
+    type = GST_STREAM_TYPE_VIDEO;
+  if (tr_priv->track->type == GES_TRACK_TYPE_AUDIO)
+    type = GST_STREAM_TYPE_AUDIO;
 
-static void
-track_element_removed_cb (GESTrack * track,
-    GESTrackElement * track_element, GESTimeline * timeline)
-{
-  /* Disconnect all signal handlers */
-  g_signal_handlers_disconnect_by_func (track_element,
-      trackelement_start_changed_cb, timeline);
+  if (!tr_priv->stream ||
+      g_strcmp0 (stream_id, gst_stream_get_stream_id (tr_priv->stream))) {
+    res = TRUE;
+    gst_object_replace ((GstObject **) & tr_priv->stream,
+        (GstObject *) gst_stream_new (stream_id,
+            (GstCaps *) ges_track_get_caps (tr_priv->track),
+            type, GST_STREAM_FLAG_NONE)
+        );
+  }
+
+  g_free (stream_id);
+
+  return res;
 }
 
 static GstPadProbeReturn
 _pad_probe_cb (GstPad * mixer_pad, GstPadProbeInfo * info,
-    GESTimeline * timeline)
+    TrackPrivate * tr_priv)
 {
   GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+  GESTimeline *timeline = tr_priv->timeline;
+
   if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START) {
     LOCK_DYN (timeline);
     if (timeline->priv->stream_start_group_id == -1) {
@@ -1445,9 +2004,11 @@ _pad_probe_cb (GstPad * mixer_pad, GstPadProbeInfo * info,
         timeline->priv->stream_start_group_id = gst_util_group_id_next ();
     }
 
-    info->data = gst_event_make_writable (event);
-    gst_event_set_group_id (GST_PAD_PROBE_INFO_EVENT (info),
-        timeline->priv->stream_start_group_id);
+    gst_event_unref (event);
+    event = info->data =
+        gst_event_new_stream_start (gst_stream_get_stream_id (tr_priv->stream));
+    gst_event_set_stream (event, tr_priv->stream);
+    gst_event_set_group_id (event, timeline->priv->stream_start_group_id);
     UNLOCK_DYN (timeline);
 
     return GST_PAD_PROBE_REMOVE;
@@ -1500,7 +2061,7 @@ _ghost_track_srcpad (TrackPrivate * tr_priv)
 
   tr_priv->probe_id = gst_pad_add_probe (pad,
       GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
-      (GstPadProbeCallback) _pad_probe_cb, tr_priv->timeline, NULL);
+      (GstPadProbeCallback) _pad_probe_cb, tr_priv, NULL);
 
   UNLOCK_DYN (tr_priv->timeline);
 }
@@ -1508,6 +2069,7 @@ _ghost_track_srcpad (TrackPrivate * tr_priv)
 gboolean
 timeline_add_element (GESTimeline * timeline, GESTimelineElement * element)
 {
+  /* FIXME: handle NULL element->name */
   GESTimelineElement *same_name =
       g_hash_table_lookup (timeline->priv->all_elements,
       element->name);
@@ -1519,10 +2081,19 @@ timeline_add_element (GESTimeline * timeline, GESTimelineElement * element)
     return FALSE;
   }
 
+  /* FIXME: why is the hash table using the name of the element, rather than
+   * the pointer to the element itself as the key? This makes it awkward
+   * to change the name of an element after it has been added. See
+   * ges_timeline_element_set_name. It means we have to remove and then
+   * re-add the element. */
   g_hash_table_insert (timeline->priv->all_elements,
       ges_timeline_element_get_name (element), gst_object_ref (element));
 
   timeline_tree_track_element (timeline->priv->tree, element);
+  if (GES_IS_SOURCE (element)) {
+    ges_source_set_rendering_smartly (GES_SOURCE (element),
+        timeline->priv->rendering_smartly);
+  }
 
   return TRUE;
 }
@@ -1544,9 +2115,11 @@ timeline_fill_gaps (GESTimeline * timeline)
 {
   GList *tmp;
 
+  LOCK_DYN (timeline);
   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
     track_resort_and_fill_gaps (tmp->data);
   }
+  UNLOCK_DYN (timeline);
 }
 
 GNode *
@@ -1555,11 +2128,37 @@ timeline_get_tree (GESTimeline * timeline)
   return timeline->priv->tree;
 }
 
+void
+ges_timeline_set_smart_rendering (GESTimeline * timeline,
+    gboolean rendering_smartly)
+{
+  if (rendering_smartly) {
+    GList *tmp;
+
+    for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
+      if (ges_track_get_mixing (tmp->data)) {
+        GST_INFO_OBJECT (timeline, "Smart rendering will not"
+            " work as track %" GST_PTR_FORMAT " is doing mixing", tmp->data);
+      } else {
+        ges_track_set_smart_rendering (tmp->data, rendering_smartly);
+      }
+    }
+  }
+  timeline_tree_set_smart_rendering (timeline->priv->tree, rendering_smartly);
+  timeline->priv->rendering_smartly = rendering_smartly;
+}
+
+gboolean
+ges_timeline_get_smart_rendering (GESTimeline * timeline)
+{
+  return timeline->priv->rendering_smartly;
+}
+
 /**** API *****/
 /**
  * ges_timeline_new:
  *
- * Creates a new empty #GESTimeline.
+ * Creates a new empty timeline.
  *
  * Returns: (transfer floating): The new timeline.
  */
@@ -1578,8 +2177,9 @@ ges_timeline_new (void)
 
 /**
  * ges_timeline_new_from_uri:
- * @uri: the URI to load from
- * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
+ * @uri: The URI to load from
+ * @error: (out) (allow-none): An error to be set if loading fails, or
+ * %NULL to ignore
  *
  * Creates a timeline from the given URI.
  *
@@ -1600,14 +2200,14 @@ ges_timeline_new_from_uri (const gchar * uri, GError ** error)
 
 /**
  * ges_timeline_load_from_uri:
- * @timeline: an empty #GESTimeline into which to load the formatter
+ * @timeline: An empty #GESTimeline into which to load the formatter
  * @uri: The URI to load from
- * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
+ * @error: (out) (allow-none): An error to be set if loading fails, or
+ * %NULL to ignore
  *
- * Loads the contents of URI into the given timeline.
+ * Loads the contents of URI into the timeline.
  *
- * Returns: %TRUE if the timeline was loaded successfully, or %FALSE if the uri
- * could not be loaded.
+ * Returns: %TRUE if the timeline was loaded successfully from @uri.
  */
 gboolean
 ges_timeline_load_from_uri (GESTimeline * timeline, const gchar * uri,
@@ -1629,18 +2229,18 @@ ges_timeline_load_from_uri (GESTimeline * timeline, const gchar * uri,
 
 /**
  * ges_timeline_save_to_uri:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  * @uri: The location to save to
- * @formatter_asset: (allow-none): The formatter asset to use or %NULL. If %NULL,
- * will try to save in the same format as the one from which the timeline as been loaded
- * or default to the formatter with highest rank
+ * @formatter_asset: (allow-none): The formatter asset to use, or %NULL
  * @overwrite: %TRUE to overwrite file if it exists
- * @error: (out) (allow-none): An error to be set in case something wrong happens or %NULL
+ * @error: (out) (allow-none): An error to be set if saving fails, or
+ * %NULL to ignore
  *
- * Saves the timeline to the given location
+ * Saves the timeline to the given location. If @formatter_asset is %NULL,
+ * the method will attempt to save in the same format the timeline was
+ * loaded from, before defaulting to the formatter with highest rank.
  *
- * Returns: %TRUE if the timeline was successfully saved to the given location,
- * else %FALSE.
+ * Returns: %TRUE if @timeline was successfully saved to @uri.
  */
 gboolean
 ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri,
@@ -1670,12 +2270,12 @@ ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri,
 
 /**
  * ges_timeline_get_groups:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Get the list of #GESGroup present in the Timeline.
+ * Get the list of #GESGroup-s present in the timeline.
  *
- * Returns: (transfer none) (element-type GESGroup): the list of
- * #GESGroup that contain clips present in the timeline's layers.
+ * Returns: (transfer none) (element-type GESGroup): The list of
+ * groups that contain clips present in @timeline's layers.
  * Must not be changed.
  */
 GList *
@@ -1689,17 +2289,17 @@ ges_timeline_get_groups (GESTimeline * timeline)
 
 /**
  * ges_timeline_append_layer:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Append a newly created #GESLayer to @timeline
- * Note that you do not own any reference to the returned layer.
+ * Append a newly created layer to the timeline. The layer will
+ * be added at the lowest #GESLayer:priority (numerically, the highest).
  *
- * Returns: (transfer none): The newly created #GESLayer, or the last (empty)
- * #GESLayer of @timeline.
+ * Returns: (transfer none): The newly created layer.
  */
 GESLayer *
 ges_timeline_append_layer (GESTimeline * timeline)
 {
+  GList *tmp;
   guint32 priority;
   GESLayer *layer;
 
@@ -1707,7 +2307,11 @@ ges_timeline_append_layer (GESTimeline * timeline)
   CHECK_THREAD (timeline);
 
   layer = ges_layer_new ();
-  priority = g_list_length (timeline->layers);
+
+  priority = 0;
+  for (tmp = timeline->layers; tmp; tmp = tmp->next)
+    priority = MAX (priority, ges_layer_get_priority (tmp->data) + 1);
+
   ges_layer_set_priority (layer, priority);
 
   ges_timeline_add_layer (timeline, layer);
@@ -1717,13 +2321,27 @@ ges_timeline_append_layer (GESTimeline * timeline)
 
 /**
  * ges_timeline_add_layer:
- * @timeline: a #GESTimeline
- * @layer: (transfer floating): the #GESLayer to add
- *
- * Add the layer to the timeline. The reference to the @layer will be stolen
- * by the @timeline.
- *
- * Returns: %TRUE if the layer was properly added, else %FALSE.
+ * @timeline: The #GESTimeline
+ * @layer: (transfer floating): The layer to add
+ *
+ * Add a layer to the timeline.
+ *
+ * If the layer contains #GESClip-s, then this may trigger the creation of
+ * their core track element children for the timeline's tracks, and the
+ * placement of the clip's children in the tracks of the timeline using
+ * #GESTimeline::select-tracks-for-object. Some errors may occur if this
+ * would break one of the configuration rules of the timeline in one of
+ * its tracks. In such cases, some track elements would fail to be added
+ * to their tracks, but this method would still return %TRUE. As such, it
+ * is advised that you only add clips to layers that already part of a
+ * timeline. In such situations, ges_layer_add_clip() is able to fail if
+ * adding the clip would cause such an error.
+ *
+ * Deprecated: 1.18: This method requires you to ensure the layer's
+ * #GESLayer:priority will be unique to the timeline. Use
+ * ges_timeline_append_layer() and ges_timeline_move_layer() instead.
+ *
+ * Returns: %TRUE if @layer was properly added.
  */
 gboolean
 ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
@@ -1753,6 +2371,10 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
     return FALSE;
   }
 
+  /* FIXME: ensure the layer->priority does not conflict with an existing
+   * layer in the timeline. Currently can add several layers with equal
+   * layer priorities */
+
   auto_transition = ges_layer_get_auto_transition (layer);
 
   /* If the user doesn't explicitely set layer auto_transition, then set our */
@@ -1769,40 +2391,33 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
   ges_layer_set_timeline (layer, timeline);
 
   /* Connect to 'clip-added'/'clip-removed' signal from the new layer */
-  g_signal_connect_after (layer, "clip-added",
-      G_CALLBACK (layer_object_added_cb), timeline);
-  g_signal_connect_after (layer, "clip-removed",
-      G_CALLBACK (layer_object_removed_cb), timeline);
   g_signal_connect (layer, "notify::priority",
       G_CALLBACK (layer_priority_changed_cb), timeline);
   g_signal_connect (layer, "notify::auto-transition",
       G_CALLBACK (layer_auto_transition_changed_cb), timeline);
+  g_signal_connect_after (layer, "active-changed",
+      G_CALLBACK (layer_active_changed_cb), timeline);
 
   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
 
   /* add any existing clips to the timeline */
   objects = ges_layer_get_clips (layer);
-  for (tmp = objects; tmp; tmp = tmp->next) {
-    layer_object_added_cb (layer, tmp->data, timeline);
-    gst_object_unref (tmp->data);
-    tmp->data = NULL;
-  }
-  g_list_free (objects);
+  for (tmp = objects; tmp; tmp = tmp->next)
+    ges_timeline_add_clip (timeline, tmp->data, NULL);
+  g_list_free_full (objects, gst_object_unref);
 
   return TRUE;
 }
 
 /**
  * ges_timeline_remove_layer:
- * @timeline: a #GESTimeline
- * @layer: the #GESLayer to remove
+ * @timeline: The #GESTimeline
+ * @layer: The layer to remove
  *
- * Removes the layer from the timeline. The reference that the @timeline holds on
- * the layer will be dropped. If you wish to use the @layer after calling this
- * method, you need to take a reference before calling.
+ * Removes a layer from the timeline.
  *
- * Returns: %TRUE if the layer was properly removed, else %FALSE.
+ * Returns: %TRUE if @layer was properly removed.
  */
 
 gboolean
@@ -1812,7 +2427,9 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
 
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
   g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
-  CHECK_THREAD (timeline);
+
+  if (!timeline->priv->disposed)
+    CHECK_THREAD (timeline);
 
   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
 
@@ -1824,25 +2441,22 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
   /* remove objects from any private data structures */
 
   layer_objects = ges_layer_get_clips (layer);
-  for (tmp = layer_objects; tmp; tmp = tmp->next) {
-    layer_object_removed_cb (layer, GES_CLIP (tmp->data), timeline);
-    gst_object_unref (G_OBJECT (tmp->data));
-    tmp->data = NULL;
-  }
-  g_list_free (layer_objects);
+  for (tmp = layer_objects; tmp; tmp = tmp->next)
+    ges_timeline_remove_clip (timeline, tmp->data);
+  g_list_free_full (layer_objects, gst_object_unref);
 
   /* Disconnect signals */
   GST_DEBUG ("Disconnecting signal callbacks");
-  g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
-  g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
-      timeline);
   g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb,
       timeline);
   g_signal_handlers_disconnect_by_func (layer,
       layer_auto_transition_changed_cb, timeline);
+  g_signal_handlers_disconnect_by_func (layer, layer_active_changed_cb,
+      timeline);
 
   timeline->layers = g_list_remove (timeline->layers, layer);
   ges_layer_set_timeline (layer, NULL);
+  /* FIXME: we should resync the layer priorities */
 
   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
 
@@ -1853,13 +2467,21 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
 
 /**
  * ges_timeline_add_track:
- * @timeline: a #GESTimeline
- * @track: (transfer full): the #GESTrack to add
+ * @timeline: The #GESTimeline
+ * @track: (transfer full): The track to add
+ *
+ * Add a track to the timeline.
  *
- * Add a track to the timeline. The reference to the track will be stolen by the
- * pipeline.
+ * If the timeline already contains clips, then this may trigger the
+ * creation of their core track element children for the track, and the
+ * placement of the clip's children in the track of the timeline using
+ * #GESTimeline::select-tracks-for-object. Some errors may occur if this
+ * would break one of the configuration rules for the timeline in the
+ * track. In such cases, some track elements would fail to be added to the
+ * track, but this method would still return %TRUE. As such, it is advised
+ * that you avoid adding tracks to timelines that already contain clips.
  *
- * Returns: %TRUE if the track was properly added, else %FALSE.
+ * Returns: %TRUE if @track was properly added.
  */
 
 /* FIXME: create track elements for clips which have already been
@@ -1879,7 +2501,9 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
 
   /* make sure we don't already control it */
+  LOCK_DYN (timeline);
   if (G_UNLIKELY (g_list_find (timeline->tracks, (gconstpointer) track))) {
+    UNLOCK_DYN (timeline);
     GST_WARNING ("Track is already controlled by this timeline");
     return FALSE;
   }
@@ -1887,6 +2511,7 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
   /* Add the track to ourself (as a GstBin)
    * Reference is stolen ! */
   if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
+    UNLOCK_DYN (timeline);
     GST_WARNING ("Couldn't add track to ourself (GST)");
     return FALSE;
   }
@@ -1894,12 +2519,16 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
   tr_priv = g_new0 (TrackPrivate, 1);
   tr_priv->timeline = timeline;
   tr_priv->track = track;
+  tr_priv->track_element_added_sigid = g_signal_connect (track,
+      "track-element-added", G_CALLBACK (track_element_added_cb), timeline);
+
+  update_stream_object (tr_priv);
+  gst_stream_collection_add_stream (timeline->priv->stream_collection,
+      gst_object_ref (tr_priv->stream));
 
   /* Add the track to the list of tracks we track */
-  LOCK_DYN (timeline);
   timeline->priv->priv_tracks = g_list_append (timeline->priv->priv_tracks,
       tr_priv);
-  UNLOCK_DYN (timeline);
   timeline->tracks = g_list_append (timeline->tracks, track);
 
   /* Inform the track that it's currently being used by ourself */
@@ -1908,6 +2537,7 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
   GST_DEBUG ("Done adding track, emitting 'track-added' signal");
 
   _ghost_track_srcpad (tr_priv);
+  UNLOCK_DYN (timeline);
 
   /* emit 'track-added' */
   g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
@@ -1915,23 +2545,14 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
   /* ensure that each existing clip has the opportunity to create a
    * track element for this track*/
 
-  /* We connect to the object for the timeline editing mode management */
-  g_signal_connect (G_OBJECT (track), "track-element-added",
-      G_CALLBACK (track_element_added_cb), timeline);
-  g_signal_connect (G_OBJECT (track), "track-element-removed",
-      G_CALLBACK (track_element_removed_cb), timeline);
-
   for (tmp = timeline->layers; tmp; tmp = tmp->next) {
     GList *objects, *obj;
     objects = ges_layer_get_clips (tmp->data);
 
-    for (obj = objects; obj; obj = obj->next) {
-      GESClip *clip = obj->data;
+    for (obj = objects; obj; obj = obj->next)
+      add_object_to_tracks (timeline, obj->data, track, NULL);
 
-      add_object_to_tracks (timeline, clip, track);
-      gst_object_unref (clip);
-    }
-    g_list_free (objects);
+    g_list_free_full (objects, gst_object_unref);
   }
 
   /* FIXME Check if we should rollback if we can't sync state */
@@ -1943,14 +2564,12 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
 
 /**
  * ges_timeline_remove_track:
- * @timeline: a #GESTimeline
- * @track: the #GESTrack to remove
+ * @timeline: The #GESTimeline
+ * @track: The track to remove
  *
- * Remove the @track from the @timeline. The reference stolen when adding the
- * @track will be removed. If you wish to use the @track after calling this
- * function you must ensure that you have a reference to it.
+ * Remove a track from the timeline.
  *
- * Returns: %TRUE if the @track was properly removed, else %FALSE.
+ * Returns: %TRUE if @track was properly removed.
  */
 
 /* FIXME: release any track elements associated with this layer. currenly this
@@ -1967,7 +2586,6 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
 
   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
-  CHECK_THREAD (timeline);
 
   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
 
@@ -1984,8 +2602,22 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
   gst_object_unref (tr_priv->pad);
   priv->priv_tracks = g_list_remove (priv->priv_tracks, tr_priv);
   UNLOCK_DYN (timeline);
-  timeline->tracks = g_list_remove (timeline->tracks, track);
 
+  /* empty track of all elements that belong to the timeline's clips */
+  /* elements with no parent can stay in the track, but their timeline
+   * will be set to NULL when the track's timeline is set to NULL */
+
+  for (tmp = timeline->layers; tmp; tmp = tmp->next) {
+    GList *clips, *clip;
+    clips = ges_layer_get_clips (tmp->data);
+
+    for (clip = clips; clip; clip = clip->next)
+      ges_clip_empty_from_track (clip->data, track);
+
+    g_list_free_full (clips, gst_object_unref);
+  }
+
+  timeline->tracks = g_list_remove (timeline->tracks, track);
   ges_track_set_timeline (track, NULL);
 
   /* Remove ghost pad */
@@ -1996,12 +2628,6 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
     gst_element_remove_pad (GST_ELEMENT (timeline), tr_priv->ghostpad);
   }
 
-  /* Remove pad-added/-removed handlers */
-  g_signal_handlers_disconnect_by_func (track, track_element_added_cb,
-      timeline);
-  g_signal_handlers_disconnect_by_func (track, track_element_removed_cb,
-      timeline);
-
   /* Signal track removal to all layers/objects */
   g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
 
@@ -2013,6 +2639,8 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
     return FALSE;
   }
 
+  g_signal_handler_disconnect (track, tr_priv->track_element_added_sigid);
+
   /* set track state to NULL */
   gst_element_set_state (GST_ELEMENT (track), GST_STATE_NULL);
 
@@ -2026,12 +2654,12 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
 /**
  * ges_timeline_get_track_for_pad:
  * @timeline: The #GESTimeline
- * @pad: The #GstPad
+ * @pad: A pad
  *
- * Search the #GESTrack corresponding to the given @timeline's @pad.
+ * Search for the #GESTrack corresponding to the given timeline's pad.
  *
- * Returns: (transfer none) (nullable): The corresponding #GESTrack if it is
- * found, or %NULL if there is an error.
+ * Returns: (transfer none) (nullable): The track corresponding to @pad,
+ * or %NULL if there is an error.
  */
 
 GESTrack *
@@ -2057,12 +2685,13 @@ ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
 /**
  * ges_timeline_get_pad_for_track:
  * @timeline: The #GESTimeline
- * @track: The #GESTrack
+ * @track: A track
  *
- * Search the #GstPad corresponding to the given @timeline's @track.
+ * Search for the #GstPad corresponding to the given timeline's track.
+ * You can link to this pad to receive the output data of the given track.
  *
- * Returns: (transfer none) (nullable): The corresponding #GstPad if it is
- * found, or %NULL if there is an error.
+ * Returns: (transfer none) (nullable): The pad corresponding to @track,
+ * or %NULL if there is an error.
  */
 
 GstPad *
@@ -2089,31 +2718,34 @@ ges_timeline_get_pad_for_track (GESTimeline * timeline, GESTrack * track)
 
 /**
  * ges_timeline_get_tracks:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Returns the list of #GESTrack used by the Timeline.
+ * Get the list of #GESTrack-s used by the timeline.
  *
- * Returns: (transfer full) (element-type GESTrack): A list of #GESTrack.
- * The caller should unref each track once he is done with them.
+ * Returns: (transfer full) (element-type GESTrack): The list of tracks
+ * used by @timeline.
  */
 GList *
 ges_timeline_get_tracks (GESTimeline * timeline)
 {
+  GList *res = NULL;
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
-  CHECK_THREAD (timeline);
 
-  return g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
+  LOCK_DYN (timeline);
+  res = g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
+  UNLOCK_DYN (timeline);
+
+  return res;
 }
 
 /**
  * ges_timeline_get_layers:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Get the list of #GESLayer present in the Timeline.
+ * Get the list of #GESLayer-s present in the timeline.
  *
- * Returns: (transfer full) (element-type GESLayer): the list of
- * #GESLayer present in the Timeline sorted by priority.
- * The caller should unref each Layer once he is done with them.
+ * Returns: (transfer full) (element-type GESLayer): The list of
+ * layers present in @timeline sorted by priority.
  */
 GList *
 ges_timeline_get_layers (GESTimeline * timeline)
@@ -2154,6 +2786,12 @@ ges_timeline_commit_unlocked (GESTimeline * timeline)
   GList *tmp;
   gboolean res = TRUE;
 
+  if (timeline->priv->commit_frozen) {
+    GST_DEBUG_OBJECT (timeline, "commit locked");
+    timeline->priv->commit_delayed = TRUE;
+    return res;
+  }
+
   GST_DEBUG_OBJECT (timeline, "commiting changes");
 
   timeline_tree_create_transitions (timeline->priv->tree,
@@ -2171,12 +2809,26 @@ ges_timeline_commit_unlocked (GESTimeline * timeline)
   if (timeline->priv->expected_commited == 0) {
     g_signal_emit (timeline, ges_timeline_signals[COMMITED], 0);
   } else {
+    GstStreamCollection *collection = gst_stream_collection_new (NULL);
+
+    LOCK_DYN (timeline);
     for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
+      TrackPrivate *tr_priv =
+          g_list_find_custom (timeline->priv->priv_tracks, tmp->data,
+          (GCompareFunc) custom_find_track)->data;
+
+      update_stream_object (tr_priv);
+      gst_stream_collection_add_stream (collection,
+          gst_object_ref (tr_priv->stream));
       g_signal_connect (tmp->data, "commited", G_CALLBACK (track_commited_cb),
           timeline);
       if (!ges_track_commit (GES_TRACK (tmp->data)))
         res = FALSE;
     }
+
+    gst_object_unref (timeline->priv->stream_collection);
+    timeline->priv->stream_collection = collection;
+    UNLOCK_DYN (timeline);
   }
 
   return res;
@@ -2184,37 +2836,36 @@ ges_timeline_commit_unlocked (GESTimeline * timeline)
 
 /**
  * ges_timeline_commit:
- * @timeline: a #GESTimeline
+ * @timeline: A #GESTimeline
  *
  * Commit all the pending changes of the clips contained in the
- * @timeline.
- *
- * When changes happen in a timeline, they are not
- * directly executed in the non-linear engine. Call this method once you are
- * done with a set of changes and want it to be executed.
+ * timeline.
  *
- * The #GESTimeline::commited signal will be emitted when the (possibly updated)
- * #GstPipeline is ready to output data again, except if the state of the
- * timeline was #GST_STATE_READY or #GST_STATE_NULL.
- *
- * Note that all the pending changes will automatically be executed when the
- * timeline goes from #GST_STATE_READY to #GST_STATE_PAUSED, which usually is
- * triggered by corresponding state changes in a containing #GESPipeline.
+ * When changes happen in a timeline, they are not immediately executed
+ * internally, in a way that effects the output data of the timeline. You
+ * should call this method when you are done with a set of changes and you
+ * want them to be executed.
  *
+ * Any pending changes will be executed in the backend. The
+ * #GESTimeline::commited signal will be emitted once this has completed.
  * You should not try to change the state of the timeline, seek it or add
- * tracks to it during a commit operation, that is between a call to this
- * function and after receiving the #GESTimeline::commited signal.
+ * tracks to it before receiving this signal. You can use
+ * ges_timeline_commit_sync() if you do not want to perform other tasks in
+ * the mean time.
  *
- * See #ges_timeline_commit_sync if you don't want to bother with waiting
- * for the signal.
+ * Note that all the pending changes will automatically be executed when
+ * the timeline goes from #GST_STATE_READY to #GST_STATE_PAUSED, which is
+ * usually triggered by a corresponding state changes in a containing
+ * #GESPipeline.
  *
- * Returns: %TRUE if pending changes were commited or %FALSE if nothing needed
- * to be commited
+ * Returns: %TRUE if pending changes were committed, or %FALSE if nothing
+ * needed to be committed.
  */
 gboolean
 ges_timeline_commit (GESTimeline * timeline)
 {
   gboolean ret;
+  GstStreamCollection *pcollection = timeline->priv->stream_collection;
 
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
 
@@ -2222,6 +2873,12 @@ ges_timeline_commit (GESTimeline * timeline)
   ret = ges_timeline_commit_unlocked (timeline);
   UNLOCK_DYN (timeline);
 
+  if (pcollection != timeline->priv->stream_collection) {
+    gst_element_post_message ((GstElement *) timeline,
+        gst_message_new_stream_collection ((GstObject *) timeline,
+            timeline->priv->stream_collection));
+  }
+
   ges_timeline_emit_snapping (timeline, NULL, NULL, GST_CLOCK_TIME_NONE);
   return ret;
 }
@@ -2236,24 +2893,15 @@ commited_cb (GESTimeline * timeline)
 
 /**
  * ges_timeline_commit_sync:
- * @timeline: a #GESTimeline
- *
- * Commit all the pending changes of the #GESClips contained in the
- * @timeline.
+ * @timeline: A #GESTimeline
  *
- * Will return once the update is complete, that is when the
- * (possibly updated) #GstPipeline is ready to output data again, or if the
- * state of the timeline was #GST_STATE_READY or #GST_STATE_NULL.
- *
- * This function will wait for any pending state change of the timeline by
- * calling #gst_element_get_state with a #GST_CLOCK_TIME_NONE timeout, you
- * should not try to change the state from another thread before this function
- * has returned.
+ * Commit all the pending changes of the clips contained in the
+ * timeline and wait for the changes to complete.
  *
- * See #ges_timeline_commit for more information.
+ * See ges_timeline_commit().
  *
- * Returns: %TRUE if pending changes were commited or %FALSE if nothing needed
- * to be commited
+ * Returns: %TRUE if pending changes were committed, or %FALSE if nothing
+ * needed to be committed.
  */
 gboolean
 ges_timeline_commit_sync (GESTimeline * timeline)
@@ -2293,12 +2941,57 @@ ges_timeline_commit_sync (GESTimeline * timeline)
 }
 
 /**
+ * ges_timeline_freeze_commit:
+ * @timeline: The #GESTimeline
+ *
+ * Freezes the timeline from being committed. This is usually needed while the
+ * timeline is being rendered to ensure that not change to the timeline are
+ * taken into account during that moment. Once the rendering is done, you
+ * should call #ges_timeline_thaw_commit so that comiting becomes possible
+ * again and any call to `commit()` that happened during the rendering is
+ * actually taken into account.
+ *
+ * Since: 1.20
+ *
+ */
+void
+ges_timeline_freeze_commit (GESTimeline * timeline)
+{
+  LOCK_DYN (timeline);
+  timeline->priv->commit_frozen = TRUE;
+  UNLOCK_DYN (timeline);
+}
+
+/**
+ * ges_timeline_thaw_commit:
+ * @timeline: The #GESTimeline
+ *
+ * Thaw the timeline so that comiting becomes possible
+ * again and any call to `commit()` that happened during the rendering is
+ * actually taken into account.
+ *
+ * Since: 1.20
+ *
+ */
+void
+ges_timeline_thaw_commit (GESTimeline * timeline)
+{
+  LOCK_DYN (timeline);
+  timeline->priv->commit_frozen = FALSE;
+  UNLOCK_DYN (timeline);
+  if (timeline->priv->commit_delayed) {
+    ges_timeline_commit (timeline);
+    timeline->priv->commit_delayed = FALSE;
+  }
+}
+
+/**
  * ges_timeline_get_duration:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Get the current duration of @timeline
+ * Get the current #GESTimeline:duration of the timeline
  *
- * Returns: The current duration of @timeline
+ * Returns: The current duration of @timeline.
  */
 GstClockTime
 ges_timeline_get_duration (GESTimeline * timeline)
@@ -2311,12 +3004,11 @@ ges_timeline_get_duration (GESTimeline * timeline)
 
 /**
  * ges_timeline_get_auto_transition:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Gets whether transitions are automatically added when objects
- * overlap or not.
+ * Gets #GESTimeline:auto-transition for the timeline.
  *
- * Returns: %TRUE if transitions are automatically added, else %FALSE.
+ * Returns: The auto-transition of @self.
  */
 gboolean
 ges_timeline_get_auto_transition (GESTimeline * timeline)
@@ -2329,11 +3021,14 @@ ges_timeline_get_auto_transition (GESTimeline * timeline)
 
 /**
  * ges_timeline_set_auto_transition:
- * @timeline: a #GESLayer
- * @auto_transition: whether the auto_transition is active
+ * @timeline: The #GESTimeline
+ * @auto_transition: Whether transitions should be automatically added
+ * to @timeline's layers
  *
- * Sets the layer to the given @auto_transition. See the documentation of the
- * property auto_transition for more information.
+ * Sets #GESTimeline:auto-transition for the timeline. This will also set
+ * the corresponding #GESLayer:auto-transition for all of the timeline's
+ * layers to the same value. See ges_layer_set_auto_transition() if you
+ * wish to set the layer's #GESLayer:auto-transition individually.
  */
 void
 ges_timeline_set_auto_transition (GESTimeline * timeline,
@@ -2357,13 +3052,11 @@ ges_timeline_set_auto_transition (GESTimeline * timeline,
 
 /**
  * ges_timeline_get_snapping_distance:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Gets the configured snapping distance of the timeline. See
- * the documentation of the property snapping_distance for more
- * information.
+ * Gets the #GESTimeline:snapping-distance for the timeline.
  *
- * Returns: The @snapping_distance property of the timeline
+ * Returns: The snapping distance (in nanoseconds) of @timeline.
  */
 GstClockTime
 ges_timeline_get_snapping_distance (GESTimeline * timeline)
@@ -2377,17 +3070,19 @@ ges_timeline_get_snapping_distance (GESTimeline * timeline)
 
 /**
  * ges_timeline_set_snapping_distance:
- * @timeline: a #GESLayer
- * @snapping_distance: whether the snapping_distance is active
+ * @timeline: The #GESTimeline
+ * @snapping_distance: The snapping distance to use (in nanoseconds)
  *
- * Sets the @snapping_distance of the timeline. See the documentation of the
- * property snapping_distance for more information.
+ * Sets #GESTimeline:snapping-distance for the timeline. This new value
+ * will only effect future snappings and will not be used to snap the
+ * current element positions within the timeline.
  */
 void
 ges_timeline_set_snapping_distance (GESTimeline * timeline,
     GstClockTime snapping_distance)
 {
   g_return_if_fail (GES_IS_TIMELINE (timeline));
+  g_return_if_fail (GST_CLOCK_TIME_IS_VALID (snapping_distance));
   CHECK_THREAD (timeline);
 
   timeline->priv->snapping_distance = snapping_distance;
@@ -2395,12 +3090,13 @@ ges_timeline_set_snapping_distance (GESTimeline * timeline,
 
 /**
  * ges_timeline_get_element:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
+ * @name: The name of the element to find
  *
- * Gets a #GESTimelineElement contained in the timeline
+ * Gets the element contained in the timeline with the given name.
  *
- * Returns: (transfer full) (nullable): The #GESTimelineElement or %NULL if
- * not found.
+ * Returns: (transfer full) (nullable): The timeline element in @timeline
+ * with the given @name, or %NULL if it was not found.
  */
 GESTimelineElement *
 ges_timeline_get_element (GESTimeline * timeline, const gchar * name)
@@ -2410,6 +3106,7 @@ ges_timeline_get_element (GESTimeline * timeline, const gchar * name)
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), NULL);
   CHECK_THREAD (timeline);
 
+  /* FIXME: handle NULL name */
   ret = g_hash_table_lookup (timeline->priv->all_elements, name);
 
   if (ret)
@@ -2434,11 +3131,11 @@ ges_timeline_get_element (GESTimeline * timeline, const gchar * name)
 
 /**
  * ges_timeline_is_empty:
- * @timeline: a #GESTimeline
+ * @timeline: The #GESTimeline
  *
- * Check whether a #GESTimeline is empty or not
+ * Check whether the timeline is empty or not.
  *
- * Returns: %TRUE if the timeline is empty %FALSE otherwize
+ * Returns: %TRUE if @timeline is empty.
  */
 gboolean
 ges_timeline_is_empty (GESTimeline * timeline)
@@ -2464,13 +3161,14 @@ ges_timeline_is_empty (GESTimeline * timeline)
 
 /**
  * ges_timeline_get_layer:
- * @timeline: The #GESTimeline to retrive a layer from
- * @priority: The priority of the layer to find
+ * @timeline: The #GESTimeline to retrieve a layer from
+ * @priority: The priority/index of the layer to find
  *
- * Retrieve the layer with @priority as a priority
+ * Retrieve the layer whose index in the timeline matches the given
+ * priority.
  *
- * Returns: (transfer full) (nullable): A #GESLayer or %NULL if no layer with
- * @priority was found
+ * Returns: (transfer full) (nullable): The layer with the given
+ * @priority, or %NULL if none was found.
  *
  * Since 1.6
  */
@@ -2497,20 +3195,54 @@ ges_timeline_get_layer (GESTimeline * timeline, guint priority)
   return layer;
 }
 
+gboolean
+ges_timeline_layer_priority_in_gap (GESTimeline * timeline, guint priority)
+{
+  GList *tmp;
+
+  CHECK_THREAD (timeline);
+
+  for (tmp = timeline->layers; tmp; tmp = tmp->next) {
+    GESLayer *layer = GES_LAYER (tmp->data);
+    guint tmp_priority = ges_layer_get_priority (layer);
+
+    if (tmp_priority == priority)
+      return FALSE;
+    else if (tmp_priority > priority)
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
 /**
  * ges_timeline_paste_element:
- * @timeline: The #GESTimeline onto which the #GESTimelineElement should be pasted
- * @element: The #GESTimelineElement to paste
- * @position: The position in the timeline the element should
- * be pasted to, meaning it will become the start of @element
- * @layer_priority: The #GESLayer to which the element should be pasted to.
- * -1 means paste to the same layer from which the @element has been copied from.
- *
- * Paste @element inside the timeline. @element must have been
- * created using ges_timeline_element_copy with deep=TRUE set,
- * i.e. it must be a deep copy, otherwise it will fail.
- *
- * Returns: (transfer none): Shallow copy of the @element pasted
+ * @timeline: The #GESTimeline onto which @element should be pasted
+ * @element: The element to paste
+ * @position: The position in the timeline @element should be pasted to,
+ * i.e. the #GESTimelineElement:start value for the pasted element.
+ * @layer_priority: The layer into which the element should be pasted.
+ * -1 means paste to the same layer from which @element has been copied from
+ *
+ * Paste an element inside the timeline. @element **must** be the return of
+ * ges_timeline_element_copy() with `deep=TRUE`,
+ * and it should not be changed before pasting. @element itself is not
+ * placed in the timeline, instead a new element is created, alike to the
+ * originally copied element. Note that the originally copied element must
+ * also lie within @timeline, at both the point of copying and pasting.
+ *
+ * Pasting may fail if it would place the timeline in an unsupported
+ * configuration.
+ *
+ * After calling this function @element should not be used. In particular,
+ * @element can **not** be pasted again. Instead, you can copy the
+ * returned element and paste that copy (although, this is only possible
+ * if the paste was successful).
+ *
+ * See also ges_timeline_element_paste().
+ *
+ * Returns: (transfer full) (nullable): The newly created element, or
+ * %NULL if pasting fails.
  */
 GESTimelineElement *
 ges_timeline_paste_element (GESTimeline * timeline,
@@ -2521,9 +3253,11 @@ ges_timeline_paste_element (GESTimeline * timeline,
 
   g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (element), FALSE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE);
   CHECK_THREAD (timeline);
 
   element_class = GES_TIMELINE_ELEMENT_GET_CLASS (element);
+  /* steal ownership of the copied element */
   copied_from = ges_timeline_element_get_copied_from (element);
 
   if (!copied_from) {
@@ -2534,7 +3268,7 @@ ges_timeline_paste_element (GESTimeline * timeline,
 
   if (!element_class->paste) {
     GST_ERROR_OBJECT (element, "No paste vmethod implemented");
-
+    gst_object_unref (copied_from);
     return NULL;
   }
 
@@ -2545,26 +3279,28 @@ ges_timeline_paste_element (GESTimeline * timeline,
   if (layer_priority != -1) {
     GST_WARNING_OBJECT (timeline,
         "Only -1 value for layer priority is supported");
+    gst_object_unref (copied_from);
+    return NULL;
   }
 
   res = element_class->paste (element, copied_from, position);
 
-  g_clear_object (&copied_from);
+  gst_object_unref (copied_from);
 
-  return g_object_ref (res);
+  return res ? g_object_ref_sink (res) : res;
 }
 
 /**
  * ges_timeline_move_layer:
- * @timeline: The timeline in which @layer must be
- * @layer: The layer to move at @new_layer_priority
- * @new_layer_priority: The index at which @layer should land
+ * @timeline: A #GESTimeline
+ * @layer: A layer within @timeline, whose priority should be changed
+ * @new_layer_priority: The new index for @layer
  *
- * Moves @layer at @new_layer_priority meaning that @layer
- * we land at that position in the stack of layers inside
- * the timeline. If @new_layer_priority is superior than the number
- * of layers present in the time, it will move to the end of the
- * stack of layers.
+ * Moves a layer within the timeline to the index given by
+ * @new_layer_priority.
+ * An index of 0 corresponds to the layer with the highest priority in a
+ * timeline. If @new_layer_priority is greater than the number of layers
+ * present in the timeline, it will become the lowest priority layer.
  *
  * Since: 1.16
  */
@@ -2596,3 +3332,58 @@ ges_timeline_move_layer (GESTimeline * timeline, GESLayer * layer,
 
   return TRUE;
 }
+
+/**
+ * ges_timeline_get_frame_time:
+ * @self: The self on which to retrieve the timestamp for @frame_number
+ * @frame_number: The frame number to get the corresponding timestamp of in the
+ *                timeline coordinates
+ *
+ * This method allows you to convert a timeline output frame number into a
+ * timeline #GstClockTime. For example, this time could be used to seek to a
+ * particular frame in the timeline's output, or as the edit position for
+ * an element within the timeline.
+ *
+ * Returns: The timestamp corresponding to @frame_number in the output of @self.
+ *
+ * Since: 1.18
+ */
+GstClockTime
+ges_timeline_get_frame_time (GESTimeline * self, GESFrameNumber frame_number)
+{
+  gint fps_n, fps_d;
+
+  g_return_val_if_fail (GES_IS_TIMELINE (self), GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (GES_FRAME_NUMBER_IS_VALID (frame_number),
+      GST_CLOCK_TIME_NONE);
+
+  timeline_get_framerate (self, &fps_n, &fps_d);
+
+  return gst_util_uint64_scale_ceil (frame_number, fps_d * GST_SECOND, fps_n);
+}
+
+/**
+ * ges_timeline_get_frame_at:
+ * @self: A #GESTimeline
+ * @timestamp: The timestamp to get the corresponding frame number of
+ *
+ * This method allows you to convert a timeline #GstClockTime into its
+ * corresponding #GESFrameNumber in the timeline's output.
+ *
+ * Returns: The frame number @timestamp corresponds to.
+ *
+ * Since: 1.18
+ */
+GESFrameNumber
+ges_timeline_get_frame_at (GESTimeline * self, GstClockTime timestamp)
+{
+  gint fps_n, fps_d;
+
+  g_return_val_if_fail (GES_IS_TIMELINE (self), GES_FRAME_NUMBER_NONE);
+  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp),
+      GES_FRAME_NUMBER_NONE);
+
+  timeline_get_framerate (self, &fps_n, &fps_d);
+
+  return gst_util_uint64_scale (timestamp, fps_n, fps_d * GST_SECOND);
+}
index 35bae53..00947d8 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TIMELINE
-#define _GES_TIMELINE
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TIMELINE ges_timeline_get_type()
-
-#define GES_TIMELINE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TIMELINE, GESTimeline))
-
-#define GES_TIMELINE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TIMELINE, GESTimelineClass))
-
-#define GES_IS_TIMELINE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TIMELINE))
-
-#define GES_IS_TIMELINE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TIMELINE))
-
-#define GES_TIMELINE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TIMELINE, GESTimelineClass))
+GES_DECLARE_TYPE(Timeline, timeline, TIMELINE);
 
 #define GES_TIMELINE_GET_TRACKS(obj) (GES_TIMELINE (obj)->tracks)
 #define GES_TIMELINE_GET_LAYERS(obj) (GES_TIMELINE (obj)->layers)
@@ -52,7 +37,7 @@ G_BEGIN_DECLS
  * ges_timeline_get_project:
  * @obj: The #GESTimeline from which to retrieve the project
  *
- * Helper macro to retrieve the project from which a #GESTimeline as been extracted
+ * Helper macro to retrieve the project from which @obj was extracted
  */
 #define ges_timeline_get_project(obj) (GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE(obj))))
 
@@ -60,8 +45,10 @@ typedef struct _GESTimelinePrivate GESTimelinePrivate;
 
 /**
  * GESTimeline:
- * @layers: (element-type GES.Layer): A list of #GESLayer sorted by priority NOTE: Do not modify.
- * @tracks: (element-type GES.Track): A list of #GESTrack sorted by priority NOTE: Do not modify.
+ * @layers: (element-type GES.Layer): A list of #GESLayer-s sorted by
+ * priority. NOTE: Do not modify.
+ * @tracks: Deprecated:1.10: (element-type GES.Track): This is not thread
+ * safe, use #ges_timeline_get_tracks instead.
  */
 struct _GESTimeline {
   GstBin parent;
@@ -100,9 +87,6 @@ struct _GESTimelineClass {
 };
 
 GES_API
-GType ges_timeline_get_type (void);
-
-GES_API
 GESTimeline* ges_timeline_new (void);
 GES_API
 GESTimeline* ges_timeline_new_from_uri (const gchar *uri, GError **error);
@@ -142,6 +126,10 @@ GES_API
 gboolean ges_timeline_commit (GESTimeline * timeline);
 GES_API
 gboolean ges_timeline_commit_sync (GESTimeline * timeline);
+GES_API
+void ges_timeline_freeze_commit (GESTimeline * timeline);
+GES_API
+void ges_timeline_thaw_commit (GESTimeline * timeline);
 
 GES_API
 GstClockTime ges_timeline_get_duration (GESTimeline *timeline);
@@ -164,7 +152,12 @@ GESTimelineElement * ges_timeline_paste_element (GESTimeline * timeline,
 GES_API
 gboolean ges_timeline_move_layer (GESTimeline *timeline, GESLayer *layer, guint new_layer_priority);
 
-G_END_DECLS
+GES_API
+GstClockTime ges_timeline_get_frame_time(GESTimeline *self,
+                                         GESFrameNumber frame_number);
 
-#endif /* _GES_TIMELINE */
+GES_API
+GESFrameNumber ges_timeline_get_frame_at (GESTimeline *self,
+                                          GstClockTime timestamp);
 
+G_END_DECLS
index f663a22..b5fafd9 100644 (file)
@@ -106,32 +106,17 @@ static void
 ges_title_clip_set_property (GObject * object, guint property_id,
     const GValue * value, GParamSpec * pspec)
 {
-  GESTitleClip *uriclip = GES_TITLE_CLIP (object);
-
   switch (property_id) {
     case PROP_TEXT:
-      ges_title_clip_set_text (uriclip, g_value_get_string (value));
-      break;
     case PROP_FONT_DESC:
-      ges_title_clip_set_font_desc (uriclip, g_value_get_string (value));
-      break;
     case PROP_HALIGNMENT:
-      ges_title_clip_set_halignment (uriclip, g_value_get_enum (value));
-      break;
     case PROP_VALIGNMENT:
-      ges_title_clip_set_valignment (uriclip, g_value_get_enum (value));
-      break;
     case PROP_COLOR:
-      ges_title_clip_set_color (uriclip, g_value_get_uint (value));
-      break;
     case PROP_BACKGROUND:
-      ges_title_clip_set_background (uriclip, g_value_get_uint (value));
-      break;
     case PROP_XPOS:
-      ges_title_clip_set_xpos (uriclip, g_value_get_double (value));
-      break;
     case PROP_YPOS:
-      ges_title_clip_set_ypos (uriclip, g_value_get_double (value));
+      ges_timeline_element_set_child_property (GES_TIMELINE_ELEMENT (object),
+          pspec->name, value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -148,8 +133,6 @@ static void
 ges_title_clip_class_init (GESTitleClipClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESTimelineElementClass *timeline_element_class =
-      GES_TIMELINE_ELEMENT_CLASS (klass);
   GESClipClass *clip_class = GES_CLIP_CLASS (klass);
   GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
 
@@ -162,8 +145,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * The text to diplay
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
   g_object_class_install_property (object_class, PROP_TEXT,
       g_param_spec_string ("text", "Text", "The text to display",
@@ -175,8 +159,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * Pango font description string
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
       g_param_spec_string ("font-desc", "font description",
@@ -191,8 +176,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * Vertical alignent of the text
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
       g_param_spec_enum ("valignment", "vertical alignment",
@@ -205,8 +191,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * Horizontal alignment of the text
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
       g_param_spec_enum ("halignment", "horizontal alignment",
@@ -216,7 +203,6 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
           GES_PARAM_NO_SERIALIZATION));
 
   clip_class->create_track_element = ges_title_clip_create_track_element;
-  timeline_element_class->set_inpoint = NULL;
 
   container_class->child_added = _child_added;
   container_class->child_removed = _child_removed;
@@ -226,8 +212,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * The color of the text
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
 
   g_object_class_install_property (object_class, PROP_COLOR,
@@ -240,8 +227,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * The background of the text
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
 
   g_object_class_install_property (object_class, PROP_BACKGROUND,
@@ -254,8 +242,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * The horizontal position of the text
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
 
   g_object_class_install_property (object_class, PROP_XPOS,
@@ -268,8 +257,9 @@ ges_title_clip_class_init (GESTitleClipClass * klass)
    *
    * The vertical position of the text
    *
-   * Deprecated: use ges_track_element_get/set_children_properties on the
-   * underlying GESTrackElement instead
+   * Deprecated:1.6: use #ges_timeline_element_set_children_properties or
+   * #ges_timeline_element_get_children_properties instead.
+   * See #GESTitleSource for more information about exposed properties
    */
 
   g_object_class_install_property (object_class, PROP_YPOS,
@@ -295,8 +285,8 @@ ges_title_clip_init (GESTitleClip * self)
  *
  * Sets the text this clip will render.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_text (GESTitleClip * self, const gchar * text)
@@ -306,7 +296,7 @@ ges_title_clip_set_text (GESTitleClip * self, const gchar * text)
   GST_DEBUG_OBJECT (self, "text:%s", text);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data, "text", text, NULL);
+    ges_timeline_element_set_child_properties (tmp->data, "text", text, NULL);
   }
 }
 
@@ -317,8 +307,8 @@ ges_title_clip_set_text (GESTitleClip * self, const gchar * text)
  *
  * Sets the pango font description of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_font_desc (GESTitleClip * self, const gchar * font_desc)
@@ -328,7 +318,7 @@ ges_title_clip_set_font_desc (GESTitleClip * self, const gchar * font_desc)
   GST_DEBUG_OBJECT (self, "font_desc:%s", font_desc);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data,
+    ges_timeline_element_set_child_properties (tmp->data,
         "font-desc", font_desc, NULL);
   }
 }
@@ -340,8 +330,8 @@ ges_title_clip_set_font_desc (GESTitleClip * self, const gchar * font_desc)
  *
  * Sets the horizontal aligment of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_halignment (GESTitleClip * self, GESTextHAlign halign)
@@ -351,7 +341,7 @@ ges_title_clip_set_halignment (GESTitleClip * self, GESTextHAlign halign)
   GST_DEBUG_OBJECT (self, "halign:%d", halign);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data,
+    ges_timeline_element_set_child_properties (tmp->data,
         "halignment", halign, NULL);
   }
 }
@@ -363,8 +353,8 @@ ges_title_clip_set_halignment (GESTitleClip * self, GESTextHAlign halign)
  *
  * Sets the vertical aligment of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_valignment (GESTitleClip * self, GESTextVAlign valign)
@@ -374,7 +364,7 @@ ges_title_clip_set_valignment (GESTitleClip * self, GESTextVAlign valign)
   GST_DEBUG_OBJECT (self, "valign:%d", valign);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data,
+    ges_timeline_element_set_child_properties (tmp->data,
         "valignment", valign, NULL);
   }
 }
@@ -386,8 +376,8 @@ ges_title_clip_set_valignment (GESTitleClip * self, GESTextVAlign valign)
  *
  * Sets the color of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_color (GESTitleClip * self, guint32 color)
@@ -397,7 +387,7 @@ ges_title_clip_set_color (GESTitleClip * self, guint32 color)
   GST_DEBUG_OBJECT (self, "color:%d", color);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data, "color", color, NULL);
+    ges_timeline_element_set_child_properties (tmp->data, "color", color, NULL);
   }
 }
 
@@ -408,8 +398,8 @@ ges_title_clip_set_color (GESTitleClip * self, guint32 color)
  *
  * Sets the background of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_background (GESTitleClip * self, guint32 background)
@@ -419,12 +409,11 @@ ges_title_clip_set_background (GESTitleClip * self, guint32 background)
   GST_DEBUG_OBJECT (self, "background:%d", background);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data,
+    ges_timeline_element_set_child_properties (tmp->data,
         "foreground-color", background, NULL);
   }
 }
 
-
 /**
  * ges_title_clip_set_xpos:
  * @self: the #GESTitleClip* to set
@@ -432,8 +421,8 @@ ges_title_clip_set_background (GESTitleClip * self, guint32 background)
  *
  * Sets the horizontal position of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_xpos (GESTitleClip * self, gdouble position)
@@ -443,7 +432,8 @@ ges_title_clip_set_xpos (GESTitleClip * self, gdouble position)
   GST_DEBUG_OBJECT (self, "xpos:%f", position);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data, "xpos", position, NULL);
+    ges_timeline_element_set_child_properties (tmp->data, "xpos", position,
+        NULL);
   }
 }
 
@@ -454,8 +444,8 @@ ges_title_clip_set_xpos (GESTitleClip * self, gdouble position)
  *
  * Sets the vertical position of the text.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_set_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 void
 ges_title_clip_set_ypos (GESTitleClip * self, gdouble position)
@@ -465,7 +455,8 @@ ges_title_clip_set_ypos (GESTitleClip * self, gdouble position)
   GST_DEBUG_OBJECT (self, "ypos:%f", position);
 
   for (tmp = self->priv->track_titles; tmp; tmp = tmp->next) {
-    ges_track_element_set_child_properties (tmp->data, "ypos", position, NULL);
+    ges_timeline_element_set_child_properties (tmp->data, "ypos", position,
+        NULL);
   }
 }
 
@@ -477,15 +468,15 @@ ges_title_clip_set_ypos (GESTitleClip * self, gdouble position)
  *
  * Returns: The text currently set on @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 const gchar *
 ges_title_clip_get_text (GESTitleClip * self)
 {
   gchar *text;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "text", &text, NULL);
 
   return text;
@@ -499,15 +490,15 @@ ges_title_clip_get_text (GESTitleClip * self)
  *
  * Returns: The pango font description used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 const char *
 ges_title_clip_get_font_desc (GESTitleClip * self)
 {
   gchar *font_desc;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "font-desc", &font_desc, NULL);
 
   return font_desc;
@@ -521,15 +512,15 @@ ges_title_clip_get_font_desc (GESTitleClip * self)
  *
  * Returns: The horizontal aligment used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 GESTextHAlign
 ges_title_clip_get_halignment (GESTitleClip * self)
 {
   GESTextHAlign halign;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "halignment", &halign, NULL);
 
   return halign;
@@ -543,15 +534,15 @@ ges_title_clip_get_halignment (GESTitleClip * self)
  *
  * Returns: The vertical aligment used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 GESTextVAlign
 ges_title_clip_get_valignment (GESTitleClip * self)
 {
   GESTextVAlign valign;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "valignment", &valign, NULL);
 
   return valign;
@@ -565,15 +556,15 @@ ges_title_clip_get_valignment (GESTitleClip * self)
  *
  * Returns: The color used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 const guint32
 ges_title_clip_get_text_color (GESTitleClip * self)
 {
   guint32 color;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "color", &color, NULL);
 
   return color;
@@ -587,15 +578,15 @@ ges_title_clip_get_text_color (GESTitleClip * self)
  *
  * Returns: The color used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 const guint32
 ges_title_clip_get_background_color (GESTitleClip * self)
 {
   guint32 color;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "foreground-color", &color, NULL);
 
   return color;
@@ -609,15 +600,15 @@ ges_title_clip_get_background_color (GESTitleClip * self)
  *
  * Returns: The horizontal position used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_properties instead.
+ * See #GESTitleSource for more information about exposed properties
  */
 const gdouble
 ges_title_clip_get_xpos (GESTitleClip * self)
 {
   gdouble xpos;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "xpos", &xpos, NULL);
 
   return xpos;
@@ -631,15 +622,14 @@ ges_title_clip_get_xpos (GESTitleClip * self)
  *
  * Returns: The vertical position used by @self.
  *
- * Deprecated: use ges_track_element_get/set_children_properties on the
- * underlying GESTrackElement instead
+ * Deprecated:1.6: use #ges_timeline_element_get_children_property instead
  */
 const gdouble
 ges_title_clip_get_ypos (GESTitleClip * self)
 {
   gdouble ypos;
 
-  ges_track_element_get_child_properties (self->priv->track_titles->data,
+  ges_timeline_element_get_child_properties (self->priv->track_titles->data,
       "ypos", &ypos, NULL);
 
   return ypos;
index e337830..9df0b34 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TIMELINE_TITLESOURCE
-#define _GES_TIMELINE_TITLESOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TITLE_CLIP ges_title_clip_get_type()
-
-#define GES_TITLE_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TITLE_CLIP, GESTitleClip))
-
-#define GES_TITLE_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TITLE_CLIP, GESTitleClipClass))
-
-#define GES_IS_TITLE_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TITLE_CLIP))
-
-#define GES_IS_TITLE_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TITLE_CLIP))
-
-#define GES_TITLE_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TITLE_CLIP, GESTitleClipClass))
-
-typedef struct _GESTitleClipPrivate GESTitleClipPrivate;
+GES_DECLARE_TYPE(TitleClip, title_clip, TITLE_CLIP);
 
 /**
  * GESTitleClip:
@@ -71,69 +54,55 @@ struct _GESTitleClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_title_clip_get_type (void);
-
-GES_API void
-ges_title_clip_set_text( GESTitleClip * self,
-    const gchar * text);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_text( GESTitleClip * self, const gchar * text);
 
-GES_API void
-ges_title_clip_set_font_desc (GESTitleClip * self,
-    const gchar * font_desc);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_font_desc (GESTitleClip * self, const gchar * font_desc);
 
-GES_API void
-ges_title_clip_set_valignment (GESTitleClip * self,
-    GESTextVAlign valign);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_valignment (GESTitleClip * self, GESTextVAlign valign);
 
-GES_API void
-ges_title_clip_set_halignment (GESTitleClip * self,
-    GESTextHAlign halign);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_halignment (GESTitleClip * self, GESTextHAlign halign);
 
-GES_API void
-ges_title_clip_set_color (GESTitleClip * self,
-    guint32 color);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_color (GESTitleClip * self, guint32 color);
 
-GES_API void
-ges_title_clip_set_background (GESTitleClip * self,
-    guint32 background);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_background (GESTitleClip * self, guint32 background);
 
-GES_API void
-ges_title_clip_set_xpos (GESTitleClip * self,
-    gdouble position);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_xpos (GESTitleClip * self, gdouble position);
 
-GES_API void
-ges_title_clip_set_ypos (GESTitleClip * self,
-    gdouble position);
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties) void
+ges_title_clip_set_ypos (GESTitleClip * self, gdouble position);
 
-GES_API const gchar*
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const gchar*
 ges_title_clip_get_font_desc (GESTitleClip * self);
 
-GES_API GESTextVAlign
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) GESTextVAlign
 ges_title_clip_get_valignment (GESTitleClip * self);
 
-GES_API GESTextHAlign
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) GESTextHAlign
 ges_title_clip_get_halignment (GESTitleClip * self);
 
-GES_API const guint32
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const guint32
 ges_title_clip_get_text_color (GESTitleClip * self);
 
-GES_API const guint32
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const guint32
 ges_title_clip_get_background_color (GESTitleClip * self);
 
-GES_API const gdouble
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const gdouble
 ges_title_clip_get_xpos (GESTitleClip * self);
 
-GES_API const gdouble
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties) const gdouble
 ges_title_clip_get_ypos (GESTitleClip * self);
 
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties)
 const gchar* ges_title_clip_get_text (GESTitleClip * self);
 
 GES_API
 GESTitleClip* ges_title_clip_new (void);
 
 G_END_DECLS
-
-#endif /* _GES_TIMELINE_TITLESOURCE */
-
index f67115b..d20ed2f 100644 (file)
  *
  * #GESTitleSource is a GESTimelineElement that implements the notion
  * of titles in GES.
- *
- * ## Children Properties
- *
- * You can use the following children properties through the
- * #ges_track_element_set_child_property and alike set of methods:
- * <informaltable frame="none">
- * <tgroup cols="3">
- * <colspec colname="properties_type" colwidth="150px"/>
- * <colspec colname="properties_name" colwidth="200px"/>
- * <colspec colname="properties_flags" colwidth="400px"/>
- * <tbody>
- * <row>
- *  <entry role="property_type"><link linkend="guint"><type>guint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--background">background</link></entry>
- *  <entry>The color of the background</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="guint"><type>guint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--color">color</link></entry>
- *  <entry>The color of the text</entry>
- * </row>
- * <row>
- *   <entry role="property_type"><link linkend="gchar"><type>gchar</type></link></entry>
- *   <entry role="property_name"><link linkend="GESTileSource--font-desc">font-desc</link></entry>
- *   <entry>Pango font description string</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="GESTextHAlign"><type>GESTextHAlign</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--halignment">halignment</link></entry>
- *  <entry>Horizontal alignment of the text</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gchar"><type>gchar</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--text">text</link></entry>
- *  <entry>The text to be rendered</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="GESTextVAlign"><type>GESTextVAlign</type></link>
- *  </entry><entry role="property_name"><link linkend="GESTileSource--valignment">valignment</link>
- *  </entry><entry>Vertical alignent of the text</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gdouble"><type>gdouble</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--xpos">xpos</link></entry>
- *  <entry>The horizontal position of the text</entry>
- * </row>
- * <row><entry role="property_type"><link linkend="gdouble"><type>gdouble</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--ypos">ypos</link></entry>
- *  <entry>The vertical position of the text</entry>
- * </row>
- * <row><entry role="property_type"><link linkend="gboolean"><type>gboolean</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--shaded-background">shaded-background</link></entry>
- *  <entry>Whether to shade the background under the text area</entry>
- * </row>
- * <row><entry role="property_type"><link linkend="guint"><type>guint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESTileSource--outline-color">outline-color</link></entry>
- *  <entry>Color to use for outline the text (big-endian ARGB).</entry>
- * </row>
- * </tbody>
- * </tgroup>
- * </informaltable>
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -129,7 +68,7 @@ static void ges_title_source_get_property (GObject * object, guint
 static void ges_title_source_set_property (GObject * object, guint
     property_id, const GValue * value, GParamSpec * pspec);
 
-static GstElement *ges_title_source_create_source (GESTrackElement * self);
+static GstElement *ges_title_source_create_source (GESSource * self);
 
 static gboolean
 _lookup_child (GESTimelineElement * object,
@@ -159,7 +98,8 @@ static void
 ges_title_source_class_init (GESTitleSourceClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESVideoSourceClass *source_class = GES_VIDEO_SOURCE_CLASS (klass);
+  GESSourceClass *source_class = GES_SOURCE_CLASS (klass);
+  GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_CLASS (klass);
   GESTimelineElementClass *timeline_element_class =
       GES_TIMELINE_ELEMENT_CLASS (klass);
 
@@ -167,10 +107,11 @@ ges_title_source_class_init (GESTitleSourceClass * klass)
   object_class->set_property = ges_title_source_set_property;
   object_class->dispose = ges_title_source_dispose;
 
-  timeline_element_class->set_inpoint = NULL;
   timeline_element_class->lookup_child = _lookup_child;
-  source_class->ABI.abi.disable_scale_in_compositor = TRUE;
+  vsource_class->ABI.abi.disable_scale_in_compositor = TRUE;
   source_class->create_source = ges_title_source_create_source;
+
+  GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE (klass) = FALSE;
 }
 
 static void
@@ -236,17 +177,17 @@ ges_title_source_set_property (GObject * object,
 }
 
 static GstElement *
-ges_title_source_create_source (GESTrackElement * object)
+ges_title_source_create_source (GESSource * source)
 {
   GstElement *topbin, *background, *text;
   GstPad *src, *pad;
 
-  GESTitleSource *self = GES_TITLE_SOURCE (object);
+  GESTitleSource *self = GES_TITLE_SOURCE (source);
   GESTitleSourcePrivate *priv = self->priv;
   const gchar *bg_props[] = { "pattern", "foreground-color", NULL };
   const gchar *text_props[] = { "text", "font-desc", "valignment", "halignment",
     "color", "xpos", "ypos", "x-absolute", "y-absolute", "outline-color",
-    "shaded-background",
+    "shaded-background", "draw-shadow",
     "text-x", "text-y", "text-width", "text-height", NULL
   };
 
@@ -288,9 +229,10 @@ ges_title_source_create_source (GESTrackElement * object)
   priv->text_el = text;
   priv->background_el = background;
 
-  ges_track_element_add_children_props (object, text, NULL, NULL, text_props);
-  ges_track_element_add_children_props (object, background, NULL, NULL,
-      bg_props);
+  ges_track_element_add_children_props (GES_TRACK_ELEMENT (source), text, NULL,
+      NULL, text_props);
+  ges_track_element_add_children_props (GES_TRACK_ELEMENT (source), background,
+      NULL, NULL, bg_props);
 
   return topbin;
 }
@@ -451,7 +393,7 @@ ges_title_source_set_ypos (GESTitleSource * self, gdouble position)
  * Get the text currently set on the @source.
  *
  * Returns: (transfer full): The text currently set on the @source.
- * 
+ *
  * Deprecated: 1.16: Use ges_timeline_element_get_child_property instead
  * (this actually returns a newly allocated string)
  */
@@ -474,7 +416,7 @@ ges_title_source_get_text (GESTitleSource * source)
  *
  * Returns: (transfer full): The pango font description used by this
  * @source.
- * 
+ *
  * Deprecated: 1.16: Use ges_timeline_element_get_child_property instead
  * (this actually returns a newly allocated string)
  */
@@ -603,8 +545,7 @@ ges_title_source_get_ypos (GESTitleSource * source)
   return ypos;
 }
 
-/**
- * ges_title_source_new:
+/* ges_title_source_new:
  *
  * Creates a new #GESTitleSource.
  *
@@ -614,6 +555,11 @@ ges_title_source_get_ypos (GESTitleSource * source)
 GESTitleSource *
 ges_title_source_new (void)
 {
-  return g_object_new (GES_TYPE_TITLE_SOURCE, "track-type",
-      GES_TRACK_TYPE_VIDEO, NULL);
+  GESTitleSource *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_TITLE_SOURCE, NULL, NULL);
+
+  res = GES_TITLE_SOURCE (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index e2fc05a..8f6ac6d 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TITLE_SOURCE
-#define _GES_TITLE_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TITLE_SOURCE ges_title_source_get_type()
-
-#define GES_TITLE_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TITLE_SOURCE, GESTitleSource))
-
-#define GES_TITLE_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TITLE_SOURCE, GESTitleSourceClass))
-
-#define GES_IS_TITLE_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TITLE_SOURCE))
-
-#define GES_IS_TITLE_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TITLE_SOURCE))
-
-#define GES_TITLE_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TITLE_SOURCE, GESTitleSourceClass))
-
-typedef struct _GESTitleSourcePrivate GESTitleSourcePrivate;
+GES_DECLARE_TYPE(TitleSource, title_source, TITLE_SOURCE);
 
 /**
  * GESTitleSource:
  *
+ * ## Children Properties
+ *
+ *  {{ libs/GESTitleSource-children-props.md }}
  */
 struct _GESTitleSource {
   GESVideoSource parent;
@@ -74,9 +60,6 @@ struct _GESTitleSourceClass {
 };
 
 GES_API
-GType ges_title_source_get_type (void);
-
-GES_API
 void ges_title_source_set_text (GESTitleSource *self,
                                     const gchar *text);
 
@@ -123,6 +106,3 @@ GES_API
 const gdouble ges_title_source_get_ypos (GESTitleSource *source);
 
 G_END_DECLS
-
-#endif /* _GES_TITLE_SOURCE */
-
index d6ed7a2..87a9a19 100644 (file)
@@ -111,10 +111,10 @@ ges_track_element_asset_init (GESTrackElementAsset * self)
 
 /**
  * ges_track_element_asset_set_track_type:
- * @asset: A #GESAssetObject
+ * @asset: A #GESAsset
  * @type: A #GESTrackType
  *
- * Set the #GESAssetTrackType the #GESTrackElement extracted from @self
+ * Set the #GESTrackType the #GESTrackElement extracted from @self
  * should get into
  */
 void
@@ -128,7 +128,7 @@ ges_track_element_asset_set_track_type (GESTrackElementAsset * asset,
 
 /**
  * ges_track_element_asset_get_track_type:
- * @asset: A #GESAssetObject
+ * @asset: A #GESAsset
  *
  * Get the GESAssetTrackType the #GESTrackElement extracted from @self
  * should get into
@@ -143,3 +143,33 @@ ges_track_element_asset_get_track_type (GESTrackElementAsset * asset)
 
   return asset->priv->type;
 }
+
+/**
+ * ges_track_element_asset_get_natural_framerate:
+ * @self: A #GESAsset
+ * @framerate_n: The framerate numerator
+ * @framerate_d: The framerate denominator
+ *
+ * Result: %TRUE if @self has a natural framerate %FALSE otherwise
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_track_element_asset_get_natural_framerate (GESTrackElementAsset * self,
+    gint * framerate_n, gint * framerate_d)
+{
+  GESTrackElementAssetClass *klass;
+
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT_ASSET (self), FALSE);
+  g_return_val_if_fail (framerate_n && framerate_d, FALSE);
+
+  klass = GES_TRACK_ELEMENT_ASSET_GET_CLASS (self);
+
+  *framerate_n = 0;
+  *framerate_d = -1;
+
+  if (klass->get_natural_framerate)
+    return klass->get_natural_framerate (self, framerate_n, framerate_d);
+
+  return FALSE;
+}
index 1cff6c8..14a9f7c 100644 (file)
@@ -17,8 +17,7 @@
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef _GES_TRACK_ELEMENT_ASSET_
-#define _GES_TRACK_ELEMENT_ASSET_
+#pragma once
 
 #include <glib-object.h>
 #include <gio/gio.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TRACK_ELEMENT_ASSET ges_track_element_asset_get_type()
-#define GES_TRACK_ELEMENT_ASSET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TRACK_ELEMENT_ASSET, GESTrackElementAsset))
-#define GES_TRACK_ELEMENT_ASSET_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TRACK_ELEMENT_ASSET, GESTrackElementAssetClass))
-#define GES_IS_TRACK_ELEMENT_ASSET(obj)  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TRACK_ELEMENT_ASSET))
-#define GES_IS_TRACK_ELEMENT_ASSET_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TRACK_ELEMENT_ASSET))
-#define GES_TRACK_ELEMENT_ASSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TRACK_ELEMENT_ASSET, GESTrackElementAssetClass))
-
-typedef struct _GESTrackElementAssetPrivate GESTrackElementAssetPrivate;
-
-GES_API
-GType ges_track_element_asset_get_type (void);
+GES_DECLARE_TYPE(TrackElementAsset, track_element_asset, TRACK_ELEMENT_ASSET);
 
 struct _GESTrackElementAsset
 {
@@ -54,14 +44,28 @@ struct _GESTrackElementAssetClass
 {
   GESAssetClass parent_class;
 
-  gpointer _ges_reserved[GES_PADDING];
+  /**
+   * GESTrackElementAssetClass::get_natural_framerate:
+   * @self: A #GESTrackElementAsset
+   * @framerate_n: The framerate numerator to retrieve
+   * @framerate_d: The framerate denominator to retrieve
+   *
+   * Returns: %TRUE if @self has a natural framerate @FALSE otherwise.
+   *
+   * Since: 1.18
+   */
+  gboolean (*get_natural_framerate)        (GESTrackElementAsset *self, gint *framerate_n, gint *framerate_d);
+
+  gpointer _ges_reserved[GES_PADDING - 1];
 };
 
 GES_API
 const GESTrackType ges_track_element_asset_get_track_type (GESTrackElementAsset *asset);
 GES_API
 void ges_track_element_asset_set_track_type               (GESTrackElementAsset * asset, GESTrackType type);
+GES_API
+gboolean ges_track_element_asset_get_natural_framerate    (GESTrackElementAsset *self,
+                                                           gint *framerate_n,
+                                                           gint *framerate_d);
 
 G_END_DECLS
-#endif /* _GES_TRACK_ELEMENT_ASSET */
-
diff --git a/ges/ges-track-element-deprecated.h b/ges/ges-track-element-deprecated.h
new file mode 100644 (file)
index 0000000..6132fcf
--- /dev/null
@@ -0,0 +1,61 @@
+#pragma once
+
+GES_DEPRECATED_FOR(ges_track_element_get_nleobject)
+GstElement * ges_track_element_get_gnlobject   (GESTrackElement * object);
+
+
+GES_DEPRECATED_FOR(ges_timeline_element_list_children_properties)
+GParamSpec **
+ges_track_element_list_children_properties     (GESTrackElement *object,
+                                               guint *n_properties);
+
+GES_DEPRECATED_FOR(ges_timeline_element_lookup_child)
+gboolean ges_track_element_lookup_child        (GESTrackElement *object,
+                                               const gchar *prop_name,
+                                               GstElement **element,
+                                               GParamSpec **pspec);
+
+GES_DEPRECATED_FOR(ges_timeline_element_get_child_property_valist)
+void
+ges_track_element_get_child_property_valist   (GESTrackElement * object,
+                                              const gchar * first_property_name,
+                                              va_list var_args);
+
+GES_DEPRECATED_FOR(ges_timeline_element_get_child_properties)
+void ges_track_element_get_child_properties   (GESTrackElement *object,
+                                              const gchar * first_property_name,
+                                              ...) G_GNUC_NULL_TERMINATED;
+
+GES_DEPRECATED_FOR(ges_timeline_element_set_child_property_valist)
+void
+ges_track_element_set_child_property_valist   (GESTrackElement * object,
+                                              const gchar * first_property_name,
+                                              va_list var_args);
+
+GES_DEPRECATED_FOR(ges_timeline_element_set_child_property_by_spec)
+void
+ges_track_element_set_child_property_by_pspec (GESTrackElement * object,
+                                              GParamSpec * pspec,
+                                              GValue * value);
+
+GES_DEPRECATED_FOR(ges_timeline_element_set_child_properties)
+void
+ges_track_element_set_child_properties       (GESTrackElement * object,
+                                              const gchar * first_property_name,
+                                              ...) G_GNUC_NULL_TERMINATED;
+
+GES_DEPRECATED_FOR(ges_timeline_element_set_child_property)
+gboolean ges_track_element_set_child_property (GESTrackElement *object,
+                                              const gchar *property_name,
+                                              GValue * value);
+
+GES_DEPRECATED_FOR(ges_timeline_element_get_child_property)
+gboolean ges_track_element_get_child_property (GESTrackElement *object,
+                                              const gchar *property_name,
+                                              GValue * value);
+
+GES_DEPRECATED_FOR(ges_timeline_element_edit)
+gboolean
+ges_track_element_edit                        (GESTrackElement * object,
+                                              GList *layers, GESEditMode mode,
+                                              GESEdge edge, guint64 position);
index d7006b3..eb2e7f1 100644 (file)
 /**
  * SECTION:gestrackelement
  * @title: GESTrackElement
- * @short_description: Base Class for objects contained in a GESTrack
+ * @short_description: Base Class for the elements of a #GESTrack
  *
- * #GESTrackElement is the Base Class for any object that can be contained in a
- * #GESTrack.
+ * A #GESTrackElement is a #GESTimelineElement that specifically belongs
+ * to a single #GESTrack of its #GESTimelineElement:timeline. Its
+ * #GESTimelineElement:start and #GESTimelineElement:duration specify its
+ * temporal extent in the track. Specifically, a track element wraps some
+ * nleobject, such as an #nlesource or #nleoperation, which can be
+ * retrieved with ges_track_element_get_nleobject(), and its
+ * #GESTimelineElement:start, #GESTimelineElement:duration,
+ * #GESTimelineElement:in-point, #GESTimelineElement:priority and
+ * #GESTrackElement:active properties expose the corresponding nleobject
+ * properties. When a track element is added to a track, its nleobject is
+ * added to the corresponding #nlecomposition that the track wraps.
  *
- * It contains the basic information as to the location of the object within
- * its container, like the start position, the inpoint, the duration and the
- * priority.
+ * Most users will not have to work directly with track elements since a
+ * #GESClip will automatically create track elements for its timeline's
+ * tracks and take responsibility for updating them. The only track
+ * elements that are not automatically created by clips, but a user is
+ * likely to want to create, are #GESEffect-s.
+ *
+ * ## Control Bindings for Children Properties
+ *
+ * You can set up control bindings for a track element child property
+ * using ges_track_element_set_control_source(). A
+ * #GstTimedValueControlSource should specify the timed values using the
+ * internal source coordinates (see #GESTimelineElement). By default,
+ * these will be updated to lie between the #GESTimelineElement:in-point
+ * and out-point of the element. This can be switched off by setting
+ * #GESTrackElement:auto-clamp-control-sources to %FALSE.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -48,12 +69,18 @@ struct _GESTrackElementPrivate
   GstElement *element;          /* The element contained in the nleobject (can be NULL) */
 
   GESTrack *track;
+  gboolean has_internal_source_forbidden;
+  gboolean has_internal_source;
 
-  gboolean locked;              /* If TRUE, then moves in sync with its controlling
-                                 * GESClip */
+  gboolean layer_active;
 
   GHashTable *bindings_hashtable;       /* We need this if we want to be able to serialize
                                            and deserialize keyframes */
+  GESAsset *creator_asset;
+
+  GstClockTime outpoint;
+  gboolean freeze_control_sources;
+  gboolean auto_clamp_control_sources;
 };
 
 enum
@@ -62,6 +89,8 @@ enum
   PROP_ACTIVE,
   PROP_TRACK_TYPE,
   PROP_TRACK,
+  PROP_HAS_INTERNAL_SOURCE,
+  PROP_AUTO_CLAMP_CONTROL_SOURCES,
   PROP_LAST
 };
 
@@ -76,8 +105,20 @@ enum
 
 static guint ges_track_element_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESTrackElement, ges_track_element,
-    GES_TYPE_TIMELINE_ELEMENT);
+static void ges_track_element_set_asset (GESExtractable * extractable,
+    GESAsset * asset);
+
+static void
+ges_extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->set_asset = ges_track_element_set_asset;
+  iface->asset_type = GES_TYPE_TRACK_ELEMENT_ASSET;
+}
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTrackElement, ges_track_element,
+    GES_TYPE_TIMELINE_ELEMENT, G_ADD_PRIVATE (GESTrackElement)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
+        ges_extractable_interface_init));
 
 static GstElement *ges_track_element_create_gnl_object_func (GESTrackElement *
     object);
@@ -87,16 +128,11 @@ static gboolean _set_inpoint (GESTimelineElement * element,
     GstClockTime inpoint);
 static gboolean _set_duration (GESTimelineElement * element,
     GstClockTime duration);
+static gboolean _set_max_duration (GESTimelineElement * element,
+    GstClockTime max_duration);
 static gboolean _set_priority (GESTimelineElement * element, guint32 priority);
 GESTrackType _get_track_types (GESTimelineElement * object);
 
-static GParamSpec **default_list_children_properties (GESTrackElement * object,
-    guint * n_properties);
-
-static void
-_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint,
-    GstClockTime duration);
-
 static gboolean
 _lookup_child (GESTrackElement * object,
     const gchar * prop_name, GstElement ** element, GParamSpec ** pspec)
@@ -131,6 +167,25 @@ _get_layer_priority (GESTimelineElement * element)
   return ges_timeline_element_get_layer_priority (element->parent);
 }
 
+static gboolean
+_get_natural_framerate (GESTimelineElement * self, gint * framerate_n,
+    gint * framerate_d)
+{
+  GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
+
+  /* FIXME: asset should **never** be NULL */
+  if (asset &&
+      ges_track_element_asset_get_natural_framerate (GES_TRACK_ELEMENT_ASSET
+          (asset), framerate_n, framerate_d))
+    return TRUE;
+
+  if (self->parent)
+    return ges_timeline_element_get_natural_framerate (self->parent,
+        framerate_n, framerate_d);
+
+  return FALSE;
+}
+
 static void
 ges_track_element_get_property (GObject * object, guint property_id,
     GValue * value, GParamSpec * pspec)
@@ -147,6 +202,14 @@ ges_track_element_get_property (GObject * object, guint property_id,
     case PROP_TRACK:
       g_value_set_object (value, track_element->priv->track);
       break;
+    case PROP_HAS_INTERNAL_SOURCE:
+      g_value_set_boolean (value,
+          ges_track_element_has_internal_source (track_element));
+      break;
+    case PROP_AUTO_CLAMP_CONTROL_SOURCES:
+      g_value_set_boolean (value,
+          ges_track_element_get_auto_clamp_control_sources (track_element));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -163,7 +226,16 @@ ges_track_element_set_property (GObject * object, guint property_id,
       ges_track_element_set_active (track_element, g_value_get_boolean (value));
       break;
     case PROP_TRACK_TYPE:
-      track_element->priv->track_type = g_value_get_flags (value);
+      ges_track_element_set_track_type (track_element,
+          g_value_get_flags (value));
+      break;
+    case PROP_HAS_INTERNAL_SOURCE:
+      ges_track_element_set_has_internal_source (track_element,
+          g_value_get_boolean (value));
+      break;
+    case PROP_AUTO_CLAMP_CONTROL_SOURCES:
+      ges_track_element_set_auto_clamp_control_sources (track_element,
+          g_value_get_boolean (value));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -204,15 +276,18 @@ ges_track_element_dispose (GObject * object)
 }
 
 static void
-ges_track_element_constructed (GObject * gobject)
+ges_track_element_set_asset (GESExtractable * extractable, GESAsset * asset)
 {
   GESTrackElementClass *class;
   GstElement *nleobject;
-  gdouble media_duration_factor;
   gchar *tmp;
-  GESTrackElement *object = GES_TRACK_ELEMENT (gobject);
+  GESTrackElement *object = GES_TRACK_ELEMENT (extractable);
 
-  GST_DEBUG_OBJECT (object, "Creating NleObject");
+  if (ges_track_element_get_track_type (object) == GES_TRACK_TYPE_UNKNOWN) {
+    ges_track_element_set_track_type (object,
+        ges_track_element_asset_get_track_type (GES_TRACK_ELEMENT_ASSET
+            (asset)));
+  }
 
   class = GES_TRACK_ELEMENT_GET_CLASS (object);
   g_assert (class->create_gnl_object);
@@ -229,8 +304,6 @@ ges_track_element_constructed (GObject * gobject)
   gst_object_set_name (GST_OBJECT (nleobject), tmp);
   g_free (tmp);
 
-  GST_DEBUG_OBJECT (object, "Got a valid NleObject, now filling it in");
-
   object->priv->nleobject = gst_object_ref (nleobject);
   g_object_set_qdata (G_OBJECT (nleobject), NLE_OBJECT_TRACK_ELEMENT_QUARK,
       object);
@@ -241,15 +314,24 @@ ges_track_element_constructed (GObject * gobject)
       "inpoint", GES_TIMELINE_ELEMENT_INPOINT (object),
       "duration", GES_TIMELINE_ELEMENT_DURATION (object),
       "priority", GES_TIMELINE_ELEMENT_PRIORITY (object),
-      "active", object->active, NULL);
+      "active", object->active & object->priv->layer_active, NULL);
+}
 
-  media_duration_factor =
-      ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
-      (object));
-  g_object_set (object->priv->nleobject,
-      "media-duration-factor", media_duration_factor, NULL);
+static void
+ges_track_element_constructed (GObject * object)
+{
+  GESTrackElement *self = GES_TRACK_ELEMENT (object);
 
-  G_OBJECT_CLASS (ges_track_element_parent_class)->constructed (gobject);
+  if (self->priv->track_type == GES_TRACK_TYPE_UNKNOWN)
+    ges_track_element_set_track_type (GES_TRACK_ELEMENT (object),
+        GES_TRACK_ELEMENT_GET_CLASS (object)->ABI.abi.default_track_type);
+
+  /* set the default has-internal-source */
+  ges_track_element_set_has_internal_source (self,
+      GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE
+      (GES_TRACK_ELEMENT_GET_CLASS (object)));
+
+  G_OBJECT_CLASS (ges_track_element_parent_class)->constructed (object);
 }
 
 static void
@@ -263,67 +345,155 @@ ges_track_element_class_init (GESTrackElementClass * klass)
   object_class->dispose = ges_track_element_dispose;
   object_class->constructed = ges_track_element_constructed;
 
-
   /**
    * GESTrackElement:active:
    *
-   * Whether the object should be taken into account in the #GESTrack output.
-   * If #FALSE, then its contents will not be used in the resulting track.
+   * Whether the effect of the element should be applied in its
+   * #GESTrackElement:track. If set to %FALSE, it will not be used in
+   * the output of the track.
    */
   properties[PROP_ACTIVE] =
       g_param_spec_boolean ("active", "Active", "Use object in output", TRUE,
-      G_PARAM_READWRITE);
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
   g_object_class_install_property (object_class, PROP_ACTIVE,
       properties[PROP_ACTIVE]);
 
+  /**
+   * GESTrackElement:track-type:
+   *
+   * The track type of the element, which determines the type of track the
+   * element can be added to (see #GESTrack:track-type). This should
+   * correspond to the type of data that the element can produce or
+   * process.
+   */
   properties[PROP_TRACK_TYPE] = g_param_spec_flags ("track-type", "Track Type",
       "The track type of the object", GES_TYPE_TRACK_TYPE,
-      GES_TRACK_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+      GES_TRACK_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+      G_PARAM_EXPLICIT_NOTIFY);
   g_object_class_install_property (object_class, PROP_TRACK_TYPE,
       properties[PROP_TRACK_TYPE]);
 
+  /**
+   * GESTrackElement:track:
+   *
+   * The track that this element belongs to, or %NULL if it does not
+   * belong to a track.
+   */
   properties[PROP_TRACK] = g_param_spec_object ("track", "Track",
       "The track the object is in", GES_TYPE_TRACK, G_PARAM_READABLE);
   g_object_class_install_property (object_class, PROP_TRACK,
       properties[PROP_TRACK]);
 
   /**
+   * GESTrackElement:has-internal-source:
+   *
+   * This property is used to determine whether the 'internal time'
+   * properties of the element have any meaning. In particular, unless
+   * this is set to %TRUE, the #GESTimelineElement:in-point and
+   * #GESTimelineElement:max-duration can not be set to any value other
+   * than the default 0 and #GST_CLOCK_TIME_NONE, respectively.
+   *
+   * If an element has some *internal* *timed* source #GstElement that it
+   * reads stream data from as part of its function in a #GESTrack, then
+   * you'll likely want to set this to %TRUE to allow the
+   * #GESTimelineElement:in-point and #GESTimelineElement:max-duration to
+   * be set.
+   *
+   * The default value is determined by the #GESTrackElementClass
+   * @default_has_internal_source class property. For most
+   * #GESSourceClass-es, this will be %TRUE, with the exception of those
+   * that have a potentially *static* source, such as #GESImageSourceClass
+   * and #GESTitleSourceClass. Otherwise, this will usually be %FALSE.
+   *
+   * For most #GESOperation-s you will likely want to leave this set to
+   * %FALSE. The exception may be for an operation that reads some stream
+   * data from some private internal source as part of manipulating the
+   * input data from the usual linked upstream #GESTrackElement.
+   *
+   * For example, you may want to set this to %TRUE for a
+   * #GES_TRACK_TYPE_VIDEO operation that wraps a #textoverlay that reads
+   * from a subtitle file and places its text on top of the received video
+   * data. The #GESTimelineElement:in-point of the element would be used
+   * to shift the initial seek time on the #textoverlay away from 0, and
+   * the #GESTimelineElement:max-duration could be set to reflect the
+   * time at which the subtitle file runs out of data.
+   *
+   * Note that GES can not support track elements that have both internal
+   * content and manipulate the timing of their data streams (time
+   * effects).
+   *
+   * Since: 1.18
+   */
+  properties[PROP_HAS_INTERNAL_SOURCE] =
+      g_param_spec_boolean ("has-internal-source", "Has Internal Source",
+      "Whether the element has some internal source of stream data", FALSE,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_property (object_class, PROP_HAS_INTERNAL_SOURCE,
+      properties[PROP_HAS_INTERNAL_SOURCE]);
+
+  /**
+   * GESTrackElement:auto-clamp-control-sources:
+   *
+   * Whether the control sources on the element (see
+   * ges_track_element_set_control_source()) will be automatically
+   * updated whenever the #GESTimelineElement:in-point or out-point of the
+   * element change in value.
+   *
+   * See ges_track_element_clamp_control_source() for how this is done
+   * per control source.
+   *
+   * Default value: %TRUE
+   *
+   * Since: 1.18
+   */
+  properties[PROP_AUTO_CLAMP_CONTROL_SOURCES] =
+      g_param_spec_boolean ("auto-clamp-control-sources",
+      "Auto-Clamp Control Sources", "Whether to automatically update the "
+      "control sources with a change in in-point or out-point", TRUE,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_property (object_class,
+      PROP_AUTO_CLAMP_CONTROL_SOURCES,
+      properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]);
+
+  /**
    * GESTrackElement::control-binding-added:
-   * @track_element: a #GESTrackElement
-   * @control_binding: the #GstControlBinding that has been added
+   * @track_element: A #GESTrackElement
+   * @control_binding: The control binding that has been added
    *
-   * The control-binding-added signal is emitted each time a control binding
-   * is added for a child property of @track_element
+   * This is emitted when a control binding is added to a child property
+   * of the track element.
    */
   ges_track_element_signals[CONTROL_BINDING_ADDED] =
       g_signal_new ("control-binding-added", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, GST_TYPE_CONTROL_BINDING);
 
   /**
    * GESTrackElement::control-binding-removed:
-   * @track_element: a #GESTrackElement
-   * @control_binding: the #GstControlBinding that has been removed
+   * @track_element: A #GESTrackElement
+   * @control_binding: The control binding that has been removed
    *
-   * The control-binding-removed signal is emitted each time a control binding
-   * is removed for a child property of @track_element
+   * This is emitted when a control binding is removed from a child
+   * property of the track element.
    */
   ges_track_element_signals[CONTROL_BINDING_REMOVED] =
       g_signal_new ("control-binding-removed", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, GST_TYPE_CONTROL_BINDING);
 
   element_class->set_start = _set_start;
   element_class->set_duration = _set_duration;
   element_class->set_inpoint = _set_inpoint;
+  element_class->set_max_duration = _set_max_duration;
   element_class->set_priority = _set_priority;
   element_class->get_track_types = _get_track_types;
   element_class->deep_copy = ges_track_element_copy_properties;
   element_class->get_layer_priority = _get_layer_priority;
+  element_class->get_natural_framerate = _get_natural_framerate;
 
   klass->create_gnl_object = ges_track_element_create_gnl_object_func;
-  klass->list_children_properties = default_list_children_properties;
   klass->lookup_child = _lookup_child;
+  klass->ABI.abi.default_track_type = GES_TRACK_TYPE_UNKNOWN;
 }
 
 static void
@@ -338,9 +508,27 @@ ges_track_element_init (GESTrackElement * self)
   GES_TIMELINE_ELEMENT_DURATION (self) = GST_SECOND;
   GES_TIMELINE_ELEMENT_PRIORITY (self) = 0;
   self->active = TRUE;
+  self->priv->layer_active = TRUE;
 
   priv->bindings_hashtable = g_hash_table_new_full (g_str_hash, g_str_equal,
       g_free, NULL);
+
+  /* NOTE: make sure we set this flag to TRUE so that
+   *   g_object_new (, "has-internal-source", TRUE, "in-point", 10, NULL);
+   * can succeed. The problem is that "in-point" will always be set before
+   * has-internal-source is set, so we first assume that it is TRUE.
+   * Note that if we call
+   *   g_object_new (, "has-internal-source", FALSE, "in-point", 10, NULL);
+   * then "in-point" will be allowed to be set, but then when
+   * "has-internal-source" is later set to TRUE, this will set the
+   * "in-point" back to 0.
+   * This is particularly needed for the ges_timeline_element_copy method
+   * because it calls g_object_new_with_properties.
+   */
+  self->priv->has_internal_source = TRUE;
+
+  self->priv->outpoint = GST_CLOCK_TIME_NONE;
+  self->priv->auto_clamp_control_sources = TRUE;
 }
 
 static gfloat
@@ -362,6 +550,7 @@ interpolate_values_for_position (GstTimedValue * first_value,
   diff = second_value->value - first_value->value;
   interval = second_value->timestamp - first_value->timestamp;
 
+  /* FIXME: properly support non-linear timed control sources */
   if (position > first_value->timestamp)
     value_at_pos =
         first_value->value + ((float) (position -
@@ -378,98 +567,116 @@ interpolate_values_for_position (GstTimedValue * first_value,
 }
 
 static void
-_update_control_bindings (GESTimelineElement * element, GstClockTime inpoint,
-    GstClockTime duration)
+_update_control_source (GstTimedValueControlSource * source, gboolean absolute,
+    GstClockTime inpoint, GstClockTime outpoint)
 {
-  GParamSpec **specs;
-  guint n, n_specs;
-  GstControlBinding *binding;
-  GstTimedValueControlSource *source;
-  GESTrackElement *self = GES_TRACK_ELEMENT (element);
-
-  specs = ges_track_element_list_children_properties (self, &n_specs);
-
-  for (n = 0; n < n_specs; ++n) {
-    GList *values, *tmp;
-    gboolean absolute;
-    GstTimedValue *last, *first, *prev = NULL, *next = NULL;
-    gfloat value_at_pos;
-
-    binding = ges_track_element_get_control_binding (self, specs[n]->name);
-
-    if (!binding)
-      continue;
-
-    g_object_get (binding, "control_source", &source, NULL);
+  GList *values, *tmp;
+  GstTimedValue *last, *first, *prev = NULL, *next = NULL;
+  gfloat value_at_pos;
 
-    g_object_get (binding, "absolute", &absolute, NULL);
-    if (duration == 0) {
-      gst_timed_value_control_source_unset_all (GST_TIMED_VALUE_CONTROL_SOURCE
-          (source));
-      continue;
-    }
+  if (inpoint == outpoint) {
+    gst_timed_value_control_source_unset_all (source);
+    return;
+  }
 
-    values =
-        gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
-        (source));
+  values = gst_timed_value_control_source_get_all (source);
 
-    if (g_list_length (values) == 0)
-      continue;
+  if (g_list_length (values) == 0)
+    return;
 
-    first = values->data;
+  first = values->data;
 
-    for (tmp = values->next; tmp; tmp = tmp->next) {
-      next = tmp->data;
+  for (tmp = values->next; tmp; tmp = tmp->next) {
+    next = tmp->data;
 
-      if (next->timestamp > inpoint)
-        break;
+    if (next->timestamp == inpoint) {
+      /* just leave this value in place */
+      first = NULL;
+      break;
     }
-    g_list_free (values);
+    if (next->timestamp > inpoint)
+      break;
+  }
+  g_list_free (values);
 
+  if (first) {
     value_at_pos =
         interpolate_values_for_position (first, next, inpoint, absolute);
     gst_timed_value_control_source_unset (source, first->timestamp);
     gst_timed_value_control_source_set (source, inpoint, value_at_pos);
+  }
 
-    values =
-        gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
-        (source));
+  if (GST_CLOCK_TIME_IS_VALID (outpoint)) {
+    values = gst_timed_value_control_source_get_all (source);
 
-    if (duration != GST_CLOCK_TIME_NONE) {
-      last = g_list_last (values)->data;
+    last = g_list_last (values)->data;
 
-      for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) {
-        prev = tmp->data;
+    for (tmp = g_list_last (values)->prev; tmp; tmp = tmp->prev) {
+      prev = tmp->data;
 
-        if (prev->timestamp < duration + inpoint)
-          break;
+      if (prev->timestamp == outpoint) {
+        /* leave this value in place */
+        last = NULL;
+        break;
       }
-      g_list_free (values);
+      if (prev->timestamp < outpoint)
+        break;
+    }
+    g_list_free (values);
 
+    if (last) {
       value_at_pos =
-          interpolate_values_for_position (prev, last, duration + inpoint,
-          absolute);
+          interpolate_values_for_position (prev, last, outpoint, absolute);
 
       gst_timed_value_control_source_unset (source, last->timestamp);
-      gst_timed_value_control_source_set (source, duration + inpoint,
-          value_at_pos);
-      values =
-          gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
-          (source));
+      gst_timed_value_control_source_set (source, outpoint, value_at_pos);
     }
+  }
 
-    for (tmp = values; tmp; tmp = tmp->next) {
-      GstTimedValue *value = tmp->data;
-      if (value->timestamp < inpoint)
-        gst_timed_value_control_source_unset (source, value->timestamp);
-      else if (duration != GST_CLOCK_TIME_NONE
-          && value->timestamp > duration + inpoint)
-        gst_timed_value_control_source_unset (source, value->timestamp);
-    }
-    g_list_free (values);
+  values = gst_timed_value_control_source_get_all (source);
+
+  for (tmp = values; tmp; tmp = tmp->next) {
+    GstTimedValue *value = tmp->data;
+    if (value->timestamp < inpoint)
+      gst_timed_value_control_source_unset (source, value->timestamp);
+    else if (GST_CLOCK_TIME_IS_VALID (outpoint) && value->timestamp > outpoint)
+      gst_timed_value_control_source_unset (source, value->timestamp);
   }
+  g_list_free (values);
+}
 
-  g_free (specs);
+static void
+_update_control_bindings (GESTrackElement * self, GstClockTime inpoint,
+    GstClockTime outpoint)
+{
+  gchar *name;
+  GstControlBinding *binding;
+  GstControlSource *source;
+  gboolean absolute;
+  gpointer value, key;
+  GHashTableIter iter;
+
+  if (self->priv->freeze_control_sources)
+    return;
+
+  g_hash_table_iter_init (&iter, self->priv->bindings_hashtable);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    binding = value;
+    name = key;
+    g_object_get (binding, "control-source", &source, "absolute", &absolute,
+        NULL);
+
+    if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
+      GST_INFO_OBJECT (self, "Not updating %s because it does not have a"
+          " timed value control source", name);
+      gst_object_unref (source);
+      continue;
+    }
+
+    _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute,
+        inpoint, outpoint);
+    gst_object_unref (source);
+  }
 }
 
 static gboolean
@@ -479,27 +686,78 @@ _set_start (GESTimelineElement * element, GstClockTime start)
 
   g_return_val_if_fail (object->priv->nleobject, FALSE);
 
-  if (G_UNLIKELY (start == _START (object)))
-    return FALSE;
-
   g_object_set (object->priv->nleobject, "start", start, NULL);
 
   return TRUE;
 }
 
+static void
+ges_track_element_update_outpoint_full (GESTrackElement * self,
+    GstClockTime inpoint, GstClockTime duration)
+{
+  GstClockTime current_inpoint = _INPOINT (self);
+  gboolean increase = (inpoint > current_inpoint);
+  GstClockTime outpoint = GST_CLOCK_TIME_NONE;
+  GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (self);
+  GESTrackElementPrivate *priv = self->priv;
+
+  if (GES_IS_CLIP (parent) && ges_track_element_get_track (self)
+      && ges_track_element_is_active (self)
+      && GST_CLOCK_TIME_IS_VALID (duration)) {
+    outpoint =
+        ges_clip_get_internal_time_from_timeline_time (GES_CLIP (parent), self,
+        _START (self) + duration, NULL);
+
+    if (!GST_CLOCK_TIME_IS_VALID (outpoint))
+      GST_ERROR_OBJECT (self, "Got an invalid out-point");
+    else if (increase)
+      outpoint += (inpoint - current_inpoint);
+    else
+      outpoint -= (current_inpoint - inpoint);
+  }
+
+  if ((priv->outpoint != outpoint || inpoint != current_inpoint)
+      && self->priv->auto_clamp_control_sources)
+    _update_control_bindings (self, inpoint, outpoint);
+
+  priv->outpoint = outpoint;
+}
+
+void
+ges_track_element_update_outpoint (GESTrackElement * self)
+{
+  GESTimelineElement *el = GES_TIMELINE_ELEMENT (self);
+  ges_track_element_update_outpoint_full (self, el->inpoint, el->duration);
+}
+
 static gboolean
 _set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
 {
   GESTrackElement *object = GES_TRACK_ELEMENT (element);
+  GESTimelineElement *parent = element->parent;
+  GError *error = NULL;
 
   g_return_val_if_fail (object->priv->nleobject, FALSE);
+  if (inpoint && !object->priv->has_internal_source) {
+    GST_WARNING_OBJECT (element, "Cannot set an in-point for a track "
+        "element that is not registered with internal content");
+    return FALSE;
+  }
 
-  if (G_UNLIKELY (inpoint == _INPOINT (object)))
-
+  if (GES_IS_CLIP (parent)
+      && !ges_clip_can_set_inpoint_of_child (GES_CLIP (parent), object,
+          inpoint, &error)) {
+    GST_WARNING_OBJECT (element, "Cannot set an in-point of %"
+        GST_TIME_FORMAT " because the parent clip %" GES_FORMAT
+        " would not allow it%s%s", GST_TIME_ARGS (inpoint),
+        GES_ARGS (parent), error ? ": " : "", error ? error->message : "");
+    g_clear_error (&error);
     return FALSE;
+  }
 
   g_object_set (object->priv->nleobject, "inpoint", inpoint, NULL);
-  _update_control_bindings (element, inpoint, GST_CLOCK_TIME_NONE);
+
+  ges_track_element_update_outpoint_full (object, inpoint, element->duration);
 
   return TRUE;
 }
@@ -510,27 +768,50 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
   GESTrackElement *object = GES_TRACK_ELEMENT (element);
   GESTrackElementPrivate *priv = object->priv;
 
-  g_return_val_if_fail (object->priv->nleobject, FALSE);
+  g_return_val_if_fail (priv->nleobject, FALSE);
 
-  if (GST_CLOCK_TIME_IS_VALID (_MAXDURATION (element)) &&
-      duration > _INPOINT (object) + _MAXDURATION (element))
-    duration = _MAXDURATION (element) - _INPOINT (object);
+  g_object_set (priv->nleobject, "duration", duration, NULL);
 
-  if (G_UNLIKELY (duration == _DURATION (object)))
-    return FALSE;
+  ges_track_element_update_outpoint_full (object, element->inpoint, duration);
 
-  g_object_set (priv->nleobject, "duration", duration, NULL);
+  return TRUE;
+}
+
+static gboolean
+_set_max_duration (GESTimelineElement * element, GstClockTime max_duration)
+{
+  GESTrackElement *object = GES_TRACK_ELEMENT (element);
+  GESTimelineElement *parent = element->parent;
+  GError *error = NULL;
 
-  _update_control_bindings (element, ges_timeline_element_get_inpoint (element),
-      duration);
+  if (GST_CLOCK_TIME_IS_VALID (max_duration)
+      && !object->priv->has_internal_source) {
+    GST_WARNING_OBJECT (element, "Cannot set a max-duration for a track "
+        "element that is not registered with internal content");
+    return FALSE;
+  }
+
+  if (GES_IS_CLIP (parent)
+      && !ges_clip_can_set_max_duration_of_child (GES_CLIP (parent), object,
+          max_duration, &error)) {
+    GST_WARNING_OBJECT (element, "Cannot set a max-duration of %"
+        GST_TIME_FORMAT " because the parent clip %" GES_FORMAT
+        " would not allow it%s%s", GST_TIME_ARGS (max_duration),
+        GES_ARGS (parent), error ? ": " : "", error ? error->message : "");
+    g_clear_error (&error);
+    return FALSE;
+  }
 
   return TRUE;
 }
 
+
 static gboolean
 _set_priority (GESTimelineElement * element, guint32 priority)
 {
   GESTrackElement *object = GES_TRACK_ELEMENT (element);
+  GESTimelineElement *parent = element->parent;
+  GError *error = NULL;
 
   g_return_val_if_fail (object->priv->nleobject, FALSE);
 
@@ -545,6 +826,17 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   if (G_UNLIKELY (priority == _PRIORITY (object)))
     return FALSE;
 
+  if (GES_IS_CLIP (parent)
+      && !ges_clip_can_set_priority_of_child (GES_CLIP (parent), object,
+          priority, &error)) {
+    GST_WARNING_OBJECT (element, "Cannot set a priority of %"
+        G_GUINT32_FORMAT " because the parent clip %" GES_FORMAT
+        " would not allow it%s%s", priority, GES_ARGS (parent),
+        error ? ": " : "", error ? error->message : "");
+    g_clear_error (&error);
+    return FALSE;
+  }
+
   g_object_set (object->priv->nleobject, "priority", priority, NULL);
 
   return TRUE;
@@ -558,17 +850,18 @@ _get_track_types (GESTimelineElement * object)
 
 /**
  * ges_track_element_set_active:
- * @object: a #GESTrackElement
- * @active: visibility
+ * @object: A #GESTrackElement
+ * @active: Whether @object should be active in its track
  *
- * Sets the usage of the @object. If @active is %TRUE, the object will be used for
- * playback and rendering, else it will be ignored.
+ * Sets #GESTrackElement:active for the element.
  *
- * Returns: %TRUE if the property was toggled, else %FALSE
+ * Returns: %TRUE if the property was *toggled*.
  */
 gboolean
 ges_track_element_set_active (GESTrackElement * object, gboolean active)
 {
+  GESTimelineElement *parent;
+  GError *error = NULL;
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
   g_return_val_if_fail (object->priv->nleobject, FALSE);
 
@@ -577,18 +870,95 @@ ges_track_element_set_active (GESTrackElement * object, gboolean active)
   if (G_UNLIKELY (active == object->active))
     return FALSE;
 
-  g_object_set (object->priv->nleobject, "active", active, NULL);
+  parent = GES_TIMELINE_ELEMENT_PARENT (object);
+  if (GES_IS_CLIP (parent)
+      && !ges_clip_can_set_active_of_child (GES_CLIP (parent), object, active,
+          &error)) {
+    GST_WARNING_OBJECT (object,
+        "Cannot set active to %i because the parent clip %" GES_FORMAT
+        " would not allow it%s%s", active, GES_ARGS (parent), error ? ": " : "",
+        error ? error->message : "");
+    g_clear_error (&error);
+    return FALSE;
+  }
+
+  g_object_set (object->priv->nleobject, "active",
+      active & object->priv->layer_active, NULL);
+
+  object->active = active;
+  if (GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed)
+    GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed (object, active);
+
+  g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_ACTIVE]);
+
+  return TRUE;
+}
+
+/**
+ * ges_track_element_set_has_internal_source:
+ * @object: A #GESTrackElement
+ * @has_internal_source: Whether the @object should be allowed to have its
+ * 'internal time' properties set.
+ *
+ * Sets #GESTrackElement:has-internal-source for the element. If this is
+ * set to %FALSE, this method will also set the
+ * #GESTimelineElement:in-point of the element to 0 and its
+ * #GESTimelineElement:max-duration to #GST_CLOCK_TIME_NONE.
+ *
+ * Returns: %FALSE if @has_internal_source is forbidden for @object and
+ * %TRUE in any other case.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_track_element_set_has_internal_source (GESTrackElement * object,
+    gboolean has_internal_source)
+{
+  GESTimelineElement *element;
+
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+
+  GST_DEBUG_OBJECT (object, "object:%p, has-internal-source: %s", object,
+      has_internal_source ? "TRUE" : "FALSE");
+
+  if (has_internal_source && object->priv->has_internal_source_forbidden) {
+    GST_WARNING_OBJECT (object, "Setting an internal source for this "
+        "element is forbidden");
+    return FALSE;
+  }
+
+  if (G_UNLIKELY (has_internal_source == object->priv->has_internal_source))
+    return TRUE;
 
-  if (active != object->active) {
-    object->active = active;
-    if (GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed)
-      GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed (object, active);
+  object->priv->has_internal_source = has_internal_source;
+
+  if (!has_internal_source) {
+    element = GES_TIMELINE_ELEMENT (object);
+    ges_timeline_element_set_inpoint (element, 0);
+    ges_timeline_element_set_max_duration (element, GST_CLOCK_TIME_NONE);
   }
 
+  g_object_notify_by_pspec (G_OBJECT (object),
+      properties[PROP_HAS_INTERNAL_SOURCE]);
+
   return TRUE;
 }
 
 void
+ges_track_element_set_has_internal_source_is_forbidden (GESTrackElement *
+    element)
+{
+  element->priv->has_internal_source_forbidden = TRUE;
+}
+
+/**
+ * ges_track_element_set_track_type:
+ * @object: A #GESTrackElement
+ * @type: The new track-type for @object
+ *
+ * Sets the #GESTrackElement:track-type for the element.
+ */
+void
 ges_track_element_set_track_type (GESTrackElement * object, GESTrackType type)
 {
   g_return_if_fail (GES_IS_TRACK_ELEMENT (object));
@@ -599,6 +969,14 @@ ges_track_element_set_track_type (GESTrackElement * object, GESTrackType type)
   }
 }
 
+/**
+ * ges_track_element_get_track_type:
+ * @object: A #GESTrackElement
+ *
+ * Gets the #GESTrackElement:track-type for the element.
+ *
+ * Returns: The track-type of @object.
+ */
 GESTrackType
 ges_track_element_get_track_type (GESTrackElement * object)
 {
@@ -641,7 +1019,7 @@ ges_track_element_create_gnl_object_func (GESTrackElement * self)
     if (!gst_bin_add (GST_BIN (nleobject), child))
       goto add_failure;
 
-    GST_DEBUG ("Succesfully got the element to put in the nleobject");
+    GST_DEBUG ("Successfully got the element to put in the nleobject");
     self->priv->element = child;
   }
 
@@ -700,6 +1078,7 @@ ges_track_element_add_child_props (GESTrackElement * self,
   guint i;
 
   factory = gst_element_get_factory (child);
+  /* FIXME: handle NULL factory */
   klass = gst_element_factory_get_metadata (factory,
       GST_ELEMENT_METADATA_KLASS);
 
@@ -741,20 +1120,28 @@ ges_track_element_add_child_props (GESTrackElement * self,
 
 /**
  * ges_track_element_add_children_props:
- * @self: The #GESTrackElement to set chidlren props on
- * @element: The GstElement to retrieve properties from
+ * @self: A #GESTrackElement
+ * @element: The child object to retrieve properties from
  * @wanted_categories: (array zero-terminated=1) (transfer none) (allow-none):
- * An array of categories of GstElement to
- * take into account (as defined in the factory meta "klass" field)
+ * An array of element factory "klass" categories to whitelist, or %NULL
+ * to accept all categories
  * @blacklist: (array zero-terminated=1) (transfer none) (allow-none): A
- * blacklist of elements factory names to not take into account
- * @whitelist: (array zero-terminated=1) (transfer none) (allow-none): A list
- * of propery names to add as children properties
+ * blacklist of element factory names, or %NULL to not blacklist any
+ * element factory
+ * @whitelist: (array zero-terminated=1) (transfer none) (allow-none): A
+ * whitelist of element property names, or %NULL to whitelist all
+ * writeable properties
  *
- * Looks for the properties defines with the various parametters and add
- * them to the hashtable of children properties.
+ * Adds all the properties of a #GstElement that match the criteria as
+ * children properties of the track element. If the name of @element's
+ * #GstElementFactory is not in @blacklist, and the factory's
+ * #GST_ELEMENT_METADATA_KLASS contains at least one member of
+ * @wanted_categories (e.g. #GST_ELEMENT_FACTORY_KLASS_DECODER), then
+ * all the properties of @element that are also in @whitelist are added as
+ * child properties of @self using
+ * ges_timeline_element_add_child_property().
  *
- * To be used by subclasses only
+ * This is intended to be used by subclasses when constructing.
  */
 void
 ges_track_element_add_children_props (GESTrackElement * self,
@@ -771,7 +1158,7 @@ ges_track_element_add_children_props (GESTrackElement * self,
     return;
   }
 
-  /*  We go over child elements recursivly, and add writable properties to the
+  /*  We go over child elements recursively, and add writable properties to the
    *  hashtable */
   it = gst_bin_iterate_recurse (GST_BIN (element));
   while (!done) {
@@ -805,14 +1192,24 @@ ges_track_element_add_children_props (GESTrackElement * self,
 
 /* INTERNAL USAGE */
 gboolean
-ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
+ges_track_element_set_track (GESTrackElement * object, GESTrack * track,
+    GError ** error)
 {
-  gboolean ret = TRUE;
+  GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (object);
 
   g_return_val_if_fail (object->priv->nleobject, FALSE);
 
   GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track);
 
+  if (GES_IS_CLIP (parent)
+      && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track,
+          error)) {
+    GST_INFO_OBJECT (object,
+        "The parent clip %" GES_FORMAT " would not allow the track to be "
+        "set to %" GST_PTR_FORMAT, GES_ARGS (parent), track);
+    return FALSE;
+  }
+
   object->priv->track = track;
 
   if (object->priv->track) {
@@ -823,15 +1220,34 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
   }
 
   g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK]);
-  return ret;
+  return TRUE;
+}
+
+void
+ges_track_element_set_layer_active (GESTrackElement * element, gboolean active)
+{
+  if (element->priv->layer_active == active)
+    return;
+
+  element->priv->layer_active = active;
+  g_object_set (element->priv->nleobject, "active", active & element->active,
+      NULL);
 }
 
 /**
  * ges_track_element_get_all_control_bindings
- * @trackelement: The #TrackElement from which to get all set bindings
+ * @trackelement: A #GESTrackElement
+ *
+ * Get all the control bindings that have been created for the children
+ * properties of the track element using
+ * ges_track_element_set_control_source(). The keys used in the returned
+ * hash table are the child property names that were passed to
+ * ges_track_element_set_control_source(), and their values are the
+ * corresponding created #GstControlBinding.
  *
- * Returns: (element-type gchar* GstControlBinding)(transfer none): A
- * #GHashTable containing all property_name: GstControlBinding
+ * Returns: (element-type gchar* GstControlBinding*)(transfer none): A
+ * hash table containing all child-property-name/control-binding pairs
+ * for @trackelement.
  */
 GHashTable *
 ges_track_element_get_all_control_bindings (GESTrackElement * trackelement)
@@ -843,12 +1259,12 @@ ges_track_element_get_all_control_bindings (GESTrackElement * trackelement)
 
 /**
  * ges_track_element_get_track:
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  *
- * Get the #GESTrack to which this object belongs.
+ * Get the #GESTrackElement:track for the element.
  *
- * Returns: (transfer none) (nullable): The #GESTrack to which this object
- * belongs. Can be %NULL if it is not in any track
+ * Returns: (transfer none) (nullable): The track that @object belongs to,
+ * or %NULL if it does not belong to a track.
  */
 GESTrack *
 ges_track_element_get_track (GESTrackElement * object)
@@ -860,11 +1276,11 @@ ges_track_element_get_track (GESTrackElement * object)
 
 /**
  * ges_track_element_get_gnlobject:
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  *
- * Get the NleObject object this object is controlling.
+ * Get the GNonLin object this object is controlling.
  *
- * Returns: (transfer none): the NleObject object this object is controlling.
+ * Returns: (transfer none): The GNonLin object this object is controlling.
  *
  * Deprecated: use #ges_track_element_get_nleobject instead.
  */
@@ -878,11 +1294,11 @@ ges_track_element_get_gnlobject (GESTrackElement * object)
 
 /**
  * ges_track_element_get_nleobject:
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  *
- * Get the GNonLin object this object is controlling.
+ * Get the nleobject that this element wraps.
  *
- * Returns: (transfer none): the GNonLin object this object is controlling.
+ * Returns: (transfer none): The nleobject that @object wraps.
  *
  * Since: 1.6
  */
@@ -896,12 +1312,13 @@ ges_track_element_get_nleobject (GESTrackElement * object)
 
 /**
  * ges_track_element_get_element:
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  *
- * Get the #GstElement this track element is controlling within GNonLin.
+ * Get the #GstElement that the track element's underlying nleobject
+ * controls.
  *
- * Returns: (transfer none): the #GstElement this track element is controlling
- * within GNonLin.
+ * Returns: (transfer none): The #GstElement being controlled by the
+ * nleobject that @object wraps.
  */
 GstElement *
 ges_track_element_get_element (GESTrackElement * object)
@@ -913,12 +1330,11 @@ ges_track_element_get_element (GESTrackElement * object)
 
 /**
  * ges_track_element_is_active:
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  *
- * Lets you know if @object will be used for playback and rendering,
- * or not.
+ * Gets #GESTrackElement:active for the element.
  *
- * Returns: %TRUE if @object is active, %FALSE otherwize
+ * Returns: %TRUE if @object is active in its track.
  */
 gboolean
 ges_track_element_is_active (GESTrackElement * object)
@@ -930,16 +1346,34 @@ ges_track_element_is_active (GESTrackElement * object)
 }
 
 /**
+ * ges_track_element_has_internal_source:
+ * @object: A #GESTrackElement
+ *
+ * Gets #GESTrackElement:has-internal-source for the element.
+ *
+ * Returns: %TRUE if @object can have its 'internal time' properties set.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_track_element_has_internal_source (GESTrackElement * object)
+{
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+
+  return object->priv->has_internal_source;
+}
+
+/**
  * ges_track_element_lookup_child:
- * @object: object to lookup the property in
- * @prop_name: name of the property to look up. You can specify the name of the
+ * @object: Object to lookup the property in
+ * @prop_name: Name of the property to look up. You can specify the name of the
  *     class as such: "ClassName::property-name", to guarantee that you get the
  *     proper GParamSpec in case various GstElement-s contain the same property
  *     name. If you don't do so, you will get the first element found, having
  *     this property and the and the corresponding GParamSpec.
  * @element: (out) (allow-none) (transfer full): pointer to a #GstElement that
  *     takes the real object to set property on
- * @pspec: (out) (allow-none) (transfer full): pointer to take the #GParamSpec
+ * @pspec: (out) (allow-none) (transfer full): pointer to take the specification
  *     describing the property
  *
  * Looks up which @element and @pspec would be effected by the given @name. If various
@@ -962,9 +1396,9 @@ ges_track_element_lookup_child (GESTrackElement * object,
 
 /**
  * ges_track_element_set_child_property_by_pspec: (skip):
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  * @pspec: The #GParamSpec that specifies the property you want to set
- * @value: the value
+ * @value: The value
  *
  * Sets a property of a child of @object.
  *
@@ -986,7 +1420,7 @@ ges_track_element_set_child_property_by_pspec (GESTrackElement * object,
  * ges_track_element_set_child_property_valist: (skip):
  * @object: The #GESTrackElement parent object
  * @first_property_name: The name of the first property to set
- * @var_args: value for the first property, followed optionally by more
+ * @var_args: Value for the first property, followed optionally by more
  * name/return location pairs, followed by NULL
  *
  * Sets a property of a child of @object. If there are various child elements
@@ -1036,7 +1470,7 @@ ges_track_element_set_child_properties (GESTrackElement * object,
  * ges_track_element_get_child_property_valist: (skip):
  * @object: The #GESTrackElement parent object
  * @first_property_name: The name of the first property to get
- * @var_args: value for the first property, followed optionally by more
+ * @var_args: Value for the first property, followed optionally by more
  * name/return location pairs, followed by NULL
  *
  * Gets a property of a child of @object. If there are various child elements
@@ -1062,8 +1496,8 @@ ges_track_element_get_child_property_valist (GESTrackElement * object,
  * Gets an array of #GParamSpec* for all configurable properties of the
  * children of @object.
  *
- * Returns: (transfer full) (array length=n_properties): an array of #GParamSpec* which should be freed after use or
- * %NULL if something went wrong
+ * Returns: (transfer full) (array length=n_properties): An array of #GParamSpec* which should be freed after use or
+ * %NULL if something went wrong.
  *
  * Deprecated: Use #ges_timeline_element_list_children_properties
  */
@@ -1103,7 +1537,7 @@ ges_track_element_get_child_properties (GESTrackElement * object,
 
 /**
  * ges_track_element_get_child_property_by_pspec: (skip):
- * @object: a #GESTrackElement
+ * @object: A #GESTrackElement
  * @pspec: The #GParamSpec that specifies the property you want to get
  * @value: (out): return location for the value
  *
@@ -1123,7 +1557,7 @@ ges_track_element_get_child_property_by_pspec (GESTrackElement * object,
  * ges_track_element_set_child_property: (skip):
  * @object: The origin #GESTrackElement
  * @property_name: The name of the property
- * @value: the value
+ * @value: The value
  *
  * Sets a property of a GstElement contained in @object.
  *
@@ -1131,7 +1565,7 @@ ges_track_element_get_child_property_by_pspec (GESTrackElement * object,
  * intended for language bindings, #ges_track_element_set_child_properties
  * is much more convenient for C programming.
  *
- * Returns: %TRUE if the property was set, %FALSE otherwize
+ * Returns: %TRUE if the property was set, %FALSE otherwise.
  *
  * Deprecated: use #ges_timeline_element_set_child_property instead
  */
@@ -1160,7 +1594,7 @@ ges_track_element_set_child_property (GESTrackElement * object,
  * intended for language bindings, #ges_track_element_get_child_properties
  * is much more convenient for C programming.
  *
- * Returns: %TRUE if the property was found, %FALSE otherwize
+ * Returns: %TRUE if the property was found, %FALSE otherwise.
  *
  * Deprecated: Use #ges_timeline_element_get_child_property
  */
@@ -1172,15 +1606,6 @@ ges_track_element_get_child_property (GESTrackElement * object,
       property_name, value);
 }
 
-static GParamSpec **
-default_list_children_properties (GESTrackElement * object,
-    guint * n_properties)
-{
-  return
-      GES_TIMELINE_ELEMENT_GET_CLASS (object)->list_children_properties
-      (GES_TIMELINE_ELEMENT (object), n_properties);
-}
-
 void
 ges_track_element_copy_properties (GESTimelineElement * element,
     GESTimelineElement * elementcopy)
@@ -1194,7 +1619,9 @@ ges_track_element_copy_properties (GESTimelineElement * element,
       ges_track_element_list_children_properties (GES_TRACK_ELEMENT (element),
       &n_specs);
   for (n = 0; n < n_specs; ++n) {
-    if (!(specs[n]->flags & G_PARAM_WRITABLE))
+    if ((specs[n]->flags & G_PARAM_READWRITE) != G_PARAM_READWRITE)
+      continue;
+    if (specs[n]->flags & G_PARAM_CONSTRUCT_ONLY)
       continue;
     g_value_init (&val, specs[n]->value_type);
     ges_track_element_get_child_property_by_pspec (GES_TRACK_ELEMENT (element),
@@ -1206,6 +1633,19 @@ ges_track_element_copy_properties (GESTimelineElement * element,
   g_free (specs);
 }
 
+void
+ges_track_element_set_creator_asset (GESTrackElement * self,
+    GESAsset * creator_asset)
+{
+  self->priv->creator_asset = creator_asset;
+}
+
+GESAsset *
+ges_track_element_get_creator_asset (GESTrackElement * self)
+{
+  return self->priv->creator_asset;
+}
+
 static void
 _split_binding (GESTrackElement * element, GESTrackElement * new_element,
     guint64 position, GstTimedValueControlSource * source,
@@ -1295,12 +1735,16 @@ ges_track_element_copy_bindings (GESTrackElement * element,
     if (!binding)
       continue;
 
-    /* FIXME : this should work as well with other types of control sources */
-    g_object_get (binding, "control_source", &source, NULL);
-    if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source))
+    g_object_get (binding, "control-source", &source, "absolute", &absolute,
+        NULL);
+    if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
+      GST_FIXME_OBJECT (element,
+          "Implement support for control source type: %s",
+          G_OBJECT_TYPE_NAME (source));
+      gst_object_unref (source);
       continue;
+    }
 
-    g_object_get (binding, "absolute", &absolute, NULL);
     g_object_get (source, "mode", &mode, NULL);
 
     new_source =
@@ -1322,6 +1766,9 @@ ges_track_element_copy_bindings (GESTrackElement * element,
     else
       ges_track_element_set_control_source (new_element,
           GST_CONTROL_SOURCE (new_source), specs[n]->name, "direct");
+
+    gst_object_unref (source);
+    gst_object_unref (new_source);
   }
 
   g_free (specs);
@@ -1329,82 +1776,45 @@ ges_track_element_copy_bindings (GESTrackElement * element,
 
 /**
  * ges_track_element_edit:
- * @object: the #GESTrackElement to edit
- * @layers: (element-type GESLayer): The layers you want the edit to
- *  happen in, %NULL means that the edition is done in all the
- *  #GESLayers contained in the current timeline.
- *      FIXME: This is not implemented yet.
- * @mode: The #GESEditMode in which the edition will happen.
- * @edge: The #GESEdge the edit should happen on.
- * @position: The position at which to edit @object (in nanosecond)
- *
- * Edit @object in the different exisiting #GESEditMode modes. In the case of
- * slide, and roll, you need to specify a #GESEdge
- *
- * Returns: %TRUE if the object as been edited properly, %FALSE if an error
- * occured
+ * @object: The #GESTrackElement to edit
+ * @layers: (element-type GESLayer) (nullable): A whitelist of layers
+ * where the edit can be performed, %NULL allows all layers in the
+ * timeline
+ * @mode: The edit mode
+ * @edge: The edge of @object where the edit should occur
+ * @position: The edit position: a new location for the edge of @object
+ * (in nanoseconds)
+ *
+ * Edits the element within its track.
+ *
+ * Returns: %TRUE if the edit of @object completed, %FALSE on failure.
+ *
+ * Deprecated: 1.18: use #ges_timeline_element_edit instead.
  */
 gboolean
 ges_track_element_edit (GESTrackElement * object,
     GList * layers, GESEditMode mode, GESEdge edge, guint64 position)
 {
-  GESTrack *track = ges_track_element_get_track (object);
-  GESTimeline *timeline;
-
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
 
-  if (G_UNLIKELY (!track)) {
-    GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in "
-        "any Track yet.", mode);
-    return FALSE;
-  }
-
-  timeline = GES_TIMELINE (ges_track_get_timeline (track));
-
-  if (G_UNLIKELY (!timeline)) {
-    GST_WARNING_OBJECT (object, "Trying to edit in %d mode but "
-        "track %p is not in any timeline yet.", mode, track);
-    return FALSE;
-  }
-
-  switch (mode) {
-    case GES_EDIT_MODE_NORMAL:
-      return timeline_move_object (timeline, GES_TIMELINE_ELEMENT (object), -1,
-          layers, edge, position);
-      break;
-    case GES_EDIT_MODE_TRIM:
-      return timeline_trim_object (timeline, GES_TIMELINE_ELEMENT (object), -1,
-          layers, edge, position);
-      break;
-    case GES_EDIT_MODE_RIPPLE:
-      return timeline_ripple_object (timeline, GES_TIMELINE_ELEMENT (object),
-          GES_TIMELINE_ELEMENT_PRIORITY (object) / LAYER_HEIGHT,
-          layers, edge, position);
-      break;
-    case GES_EDIT_MODE_ROLL:
-      return timeline_roll_object (timeline, GES_TIMELINE_ELEMENT (object),
-          layers, edge, position);
-      break;
-    case GES_EDIT_MODE_SLIDE:
-      return timeline_slide_object (timeline, object, layers, edge, position);
-      break;
-    default:
-      GST_ERROR ("Unkown edit mode: %d", mode);
-      return FALSE;
-  }
-
-  return TRUE;
+  return ges_timeline_element_edit (GES_TIMELINE_ELEMENT (object),
+      layers, -1, mode, edge, position);
 }
 
 /**
  * ges_track_element_remove_control_binding:
- * @object: the #GESTrackElement on which to set a control binding
- * @property_name: The name of the property to control.
+ * @object: A #GESTrackElement
+ * @property_name: The name of the child property to remove the control
+ * binding from
  *
- * Removes a #GstControlBinding from @object.
+ * Removes the #GstControlBinding that was created for the specified child
+ * property of the track element using
+ * ges_track_element_set_control_source(). The given @property_name must
+ * be the same name of the child property that was passed to
+ * ges_track_element_set_control_source().
  *
- * Returns: %TRUE if the binding could be removed, %FALSE if an error
- * occured
+ * Returns: %TRUE if the control binding was removed from the specified
+ * child property of @object, or %FALSE if an error occurred.
  */
 gboolean
 ges_track_element_remove_control_binding (GESTrackElement * object,
@@ -1444,28 +1854,33 @@ ges_track_element_remove_control_binding (GESTrackElement * object,
 
 /**
  * ges_track_element_set_control_source:
- * @object: the #GESTrackElement on which to set a control binding
- * @source: the #GstControlSource to set on the binding.
- * @property_name: The name of the property to control.
- * @binding_type: The type of binding to create. Currently the following values are valid:
- *   - "direct": See #gst_direct_control_binding_new
- *   - "direct-absolute": See #gst_direct_control_binding_new_absolute
- *
- * Creates a #GstControlBinding and adds it to the #GstElement concerned by the
- * property. Use the same syntax as #ges_track_element_lookup_child for
- * the property name.
- *
- * Returns: %TRUE if the binding could be created and added, %FALSE if an error
- * occured
+ * @object: A #GESTrackElement
+ * @source: The control source to bind the child property to
+ * @property_name: The name of the child property to control
+ * @binding_type: The type of binding to create ("direct" or
+ * "direct-absolute")
+ *
+ * Creates a #GstControlBinding for the specified child property of the
+ * track element using the given control source. The given @property_name
+ * should refer to an existing child property of the track element, as
+ * used in ges_timeline_element_lookup_child().
+ *
+ * If @binding_type is "direct", then the control binding is created with
+ * gst_direct_control_binding_new() using the given control source. If
+ * @binding_type is "direct-absolute", it is created with
+ * gst_direct_control_binding_new_absolute() instead.
+ *
+ * Returns: %TRUE if the specified child property could be bound to
+ * @source, or %FALSE if an error occurred.
  */
 gboolean
 ges_track_element_set_control_source (GESTrackElement * object,
     GstControlSource * source,
     const gchar * property_name, const gchar * binding_type)
 {
+  gboolean ret = FALSE;
   GESTrackElementPrivate *priv;
   GstElement *element;
-  GParamSpec *pspec;
   GstControlBinding *binding;
   gboolean direct, direct_absolute;
 
@@ -1478,7 +1893,7 @@ ges_track_element_set_control_source (GESTrackElement * object,
     return FALSE;
   }
 
-  if (!ges_track_element_lookup_child (object, property_name, &element, &pspec)) {
+  if (!ges_track_element_lookup_child (object, property_name, &element, NULL)) {
     GST_WARNING ("You need to provide a valid and controllable property name");
     return FALSE;
   }
@@ -1487,44 +1902,72 @@ ges_track_element_set_control_source (GESTrackElement * object,
   direct = !g_strcmp0 (binding_type, "direct");
   direct_absolute = !g_strcmp0 (binding_type, "direct-absolute");
 
-  if (direct || direct_absolute) {
-    /* First remove existing binding */
-    if (ges_track_element_remove_control_binding (object, property_name)) {
-      GST_LOG ("Removed old binding for property %s", property_name);
-    }
+  if (!direct && !direct_absolute) {
+    GST_WARNING_OBJECT (object, "Binding type must be in "
+        "[direct, direct-absolute]");
+    goto done;
+  }
 
-    if (direct_absolute)
-      binding =
-          gst_direct_control_binding_new_absolute (GST_OBJECT (element),
-          property_name, source);
-    else
-      binding =
-          gst_direct_control_binding_new (GST_OBJECT (element), property_name,
-          source);
-
-    gst_object_add_control_binding (GST_OBJECT (element), binding);
-    g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name),
-        binding);
-    g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED],
-        0, binding);
-    return TRUE;
+  /* First remove existing binding */
+  if (ges_track_element_remove_control_binding (object, property_name))
+    GST_LOG_OBJECT (object, "Removed old binding for property %s",
+        property_name);
+
+  if (direct_absolute)
+    binding = gst_direct_control_binding_new_absolute (GST_OBJECT (element),
+        property_name, source);
+  else
+    binding = gst_direct_control_binding_new (GST_OBJECT (element),
+        property_name, source);
+
+  gst_object_add_control_binding (GST_OBJECT (element), binding);
+  /* FIXME: maybe we should force the
+   * "ChildTypeName:property-name"
+   * format convention for child property names in bindings_hashtable.
+   * Currently the table may also contain
+   * "property-name"
+   * as keys.
+   */
+  g_hash_table_insert (priv->bindings_hashtable, g_strdup (property_name),
+      binding);
+
+  if (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)
+      && priv->auto_clamp_control_sources) {
+    /* Make sure we have the control source used by the binding */
+    g_object_get (binding, "control-source", &source, NULL);
+
+    _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source),
+        direct_absolute, _INPOINT (object), priv->outpoint);
+
+    gst_object_unref (source);
   }
 
-  GST_WARNING ("Binding type must be in [direct]");
+  g_signal_emit (object, ges_track_element_signals[CONTROL_BINDING_ADDED],
+      0, binding);
 
-  return FALSE;
+  ret = TRUE;
+
+done:
+  gst_object_unref (element);
+
+  return ret;
 }
 
 /**
  * ges_track_element_get_control_binding:
- * @object: the #GESTrackElement in which to lookup the bindings.
- * @property_name: The property_name to which the binding is associated.
+ * @object: A #GESTrackElement
+ * @property_name: The name of the child property to return the control
+ * binding of
  *
- * Looks up the various controlled properties for that #GESTrackElement,
- * and returns the #GstControlBinding which controls @property_name.
+ * Gets the control binding that was created for the specified child
+ * property of the track element using
+ * ges_track_element_set_control_source(). The given @property_name must
+ * be the same name of the child property that was passed to
+ * ges_track_element_set_control_source().
  *
- * Returns: (transfer none) (nullable): the #GstControlBinding associated with
- * @property_name, or %NULL if that property is not controlled.
+ * Returns: (transfer none) (nullable): The control binding that was
+ * created for the specified child property of @object, or %NULL if
+ * @property_name does not correspond to any control binding.
  */
 GstControlBinding *
 ges_track_element_get_control_binding (GESTrackElement * object,
@@ -1542,3 +1985,138 @@ ges_track_element_get_control_binding (GESTrackElement * object,
       property_name);
   return binding;
 }
+
+/**
+ * ges_track_element_clamp_control_source:
+ * @object: A #GESTrackElement
+ * @property_name: The name of the child property to clamp the control
+ * source of
+ *
+ * Clamp the #GstTimedValueControlSource for the specified child property
+ * to lie between the #GESTimelineElement:in-point and out-point of the
+ * element. The out-point is the #GES_TIMELINE_ELEMENT_END of the element
+ * translated from the timeline coordinates to the internal source
+ * coordinates of the element.
+ *
+ * If the property does not have a #GstTimedValueControlSource set by
+ * ges_track_element_set_control_source(), nothing happens. Otherwise, if
+ * a timed value for the control source lies before the in-point of the
+ * element, or after its out-point, then it will be removed. At the
+ * in-point and out-point times, a new interpolated value will be placed.
+ *
+ * Since: 1.18
+ */
+void
+ges_track_element_clamp_control_source (GESTrackElement * object,
+    const gchar * property_name)
+{
+  GstControlBinding *binding;
+  GstControlSource *source;
+  gboolean absolute;
+
+  g_return_if_fail (GES_IS_TRACK_ELEMENT (object));
+
+  binding = ges_track_element_get_control_binding (object, property_name);
+
+  if (!binding)
+    return;
+
+  g_object_get (binding, "control-source", &source, "absolute", &absolute,
+      NULL);
+
+  if (!GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
+    gst_object_unref (source);
+    return;
+  }
+
+  _update_control_source (GST_TIMED_VALUE_CONTROL_SOURCE (source), absolute,
+      _INPOINT (object), object->priv->outpoint);
+  gst_object_unref (source);
+}
+
+/**
+ * ges_track_element_set_auto_clamp_control_sources:
+ * @object: A #GESTrackElement
+ * @auto_clamp: Whether to automatically clamp the control sources for the
+ * child properties of @object
+ *
+ * Sets #GESTrackElement:auto-clamp-control-sources. If set to %TRUE, this
+ * will immediately clamp all the control sources.
+ *
+ * Since: 1.18
+ */
+void
+ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object,
+    gboolean auto_clamp)
+{
+  g_return_if_fail (GES_IS_TRACK_ELEMENT (object));
+
+  if (auto_clamp == object->priv->auto_clamp_control_sources)
+    return;
+
+  object->priv->auto_clamp_control_sources = auto_clamp;
+  if (auto_clamp)
+    _update_control_bindings (object, _INPOINT (object),
+        object->priv->outpoint);
+
+  g_object_notify_by_pspec (G_OBJECT (object),
+      properties[PROP_AUTO_CLAMP_CONTROL_SOURCES]);
+}
+
+/**
+ * ges_track_element_get_auto_clamp_control_sources:
+ * @object: A #GESTrackElement
+ *
+ * Gets #GESTrackElement:auto-clamp-control-sources.
+ *
+ * Returns: Whether the control sources for the child properties of
+ * @object are automatically clamped.
+ * Since: 1.18
+ */
+gboolean
+ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object)
+{
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+
+  return object->priv->auto_clamp_control_sources;
+}
+
+void
+ges_track_element_freeze_control_sources (GESTrackElement * object,
+    gboolean freeze)
+{
+  object->priv->freeze_control_sources = freeze;
+  if (!freeze && object->priv->auto_clamp_control_sources)
+    _update_control_bindings (object, _INPOINT (object),
+        object->priv->outpoint);
+}
+
+/**
+ * ges_track_element_is_core:
+ * @object: A #GESTrackElement
+ *
+ * Get whether the given track element is a core track element. That is,
+ * it was created by the @create_track_elements #GESClipClass method for
+ * some #GESClip.
+ *
+ * Note that such a track element can only be added to a clip that shares
+ * the same #GESAsset as the clip that created it. For example, you are
+ * allowed to move core children between clips that resulted from
+ * ges_container_ungroup(), but you could not move the core child from a
+ * #GESUriClip to a #GESTitleClip or another #GESUriClip with a different
+ * #GESUriClip:uri.
+ *
+ * Moreover, if a core track element is added to a clip, it will always be
+ * added as a core child. Therefore, if this returns %TRUE, then @element
+ * will be a core child of its parent clip.
+ *
+ * Returns: %TRUE if @element is a core track element.
+ * Since: 1.18
+ */
+gboolean
+ges_track_element_is_core (GESTrackElement * object)
+{
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+
+  return (ges_track_element_get_creator_asset (object) != NULL);
+}
index 7d341e8..507fe87 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TRACK_ELEMENT
-#define _GES_TRACK_ELEMENT
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TRACK_ELEMENT ges_track_element_get_type()
+GES_DECLARE_TYPE(TrackElement, track_element, TRACK_ELEMENT)
 
-#define GES_TRACK_ELEMENT(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TRACK_ELEMENT, GESTrackElement))
-
-#define GES_TRACK_ELEMENT_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TRACK_ELEMENT, GESTrackElementClass))
-
-#define GES_IS_TRACK_ELEMENT(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TRACK_ELEMENT))
-
-#define GES_IS_TRACK_ELEMENT_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TRACK_ELEMENT))
-
-#define GES_TRACK_ELEMENT_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TRACK_ELEMENT, GESTrackElementClass))
-
-typedef struct _GESTrackElementPrivate GESTrackElementPrivate;
+/**
+ * GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE:
+ * @klass: A #GESTrackElementClass
+ *
+ * What the default #GESTrackElement:has-internal-source value should be
+ * for new elements from this class.
+ */
+#define GES_TRACK_ELEMENT_CLASS_DEFAULT_HAS_INTERNAL_SOURCE(klass) \
+  ((GES_TRACK_ELEMENT_CLASS (klass))->ABI.abi.default_has_internal_source)
 
 /**
  * GESTrackElement:
  *
- * The GESTrackElement base class.
+ * The #GESTrackElement base class.
  */
 struct _GESTrackElement {
   GESTimelineElement parent;
@@ -71,64 +64,88 @@ struct _GESTrackElement {
 
 /**
  * GESTrackElementClass:
- * @nleobject_factorytype: name of the GNonLin GStElementFactory type to use.
- * @create_gnl_object: method to create the GNonLin container object.
- * @create_element: method to return the GstElement to put in the nleobject.
- * @active_changed: active property of nleobject has changed
- * @list_children_properties: method to get children properties that user could
- *                            like to configure.
- *                            The default implementation will create an object
- *                            of type @nleobject_factorytype and call
- *                            @create_element.
- *                            DeprecatedUse: GESTimelineElement.list_children_properties instead
- * @lookup_child: method letting subclasses look for a child, overriding the
- *                simple standard behaviour. This vmethod can be used for example
- *                in the case where you want the name of a child property to be
- *                'overriden'. A good example of where it is usefull is the
- *                GESTitleSource where we have a videotestsrc which has a
- *                'foreground-color' property that is used in the TitleSource to
- *                set the background color of the title, in that case, this method
- *                has been overriden so that we tweak the name passed has parametter
- *                to rename "background" to "foreground-backend" making our API
- *                understandable.
- *                Deprecated: use GESTimelineElement.lookup_child instead
- *
- * Subclasses can override the @create_gnl_object method to override what type
- * of GNonLin object will be created.
  */
 struct _GESTrackElementClass {
   /*< private >*/
   GESTimelineElementClass parent_class;
 
   /*< public >*/
-  /* virtual methods for subclasses */
+  /**
+   * GESTrackElementClass::nleobject_factorytype:
+   *
+   * The name of the #GstElementFactory to use to create the underlying
+   *   nleobject of a track element
+   */
   const gchar  *nleobject_factorytype;
+
+  /**
+   * GESTrackElementClass::create_gnl_object:
+   * @object: The #GESTrackElement
+   *
+   * Returns: (transfer floating): the #NLEObject to use in the #nlecomposition
+   */
   GstElement*  (*create_gnl_object)        (GESTrackElement * object);
+
+  /**
+   * GESTrackElementClass::create_element:
+   * @object: The #GESTrackElement
+   *
+   * Returns: (transfer floating): the #GstElement that the underlying nleobject
+   * controls.
+   */
   GstElement*  (*create_element)           (GESTrackElement * object);
 
+  /**
+   * GESTrackElementClass::active_changed:
+   * @object: A #GESTrackElement
+   * @active: Whether the element is active or not inside the #nlecomposition
+   *
+   * Notify when the #GESTrackElement:active property changes
+   */
   void (*active_changed)       (GESTrackElement *object, gboolean active);
 
   /*< private >*/
   /* signals (currently unused) */
+  /**
+   * GESTrackElementClass::changed:
+   *
+   * Deprecated:
+   */
   void  (*changed)  (GESTrackElement * object);
 
-  /*< public >*/
-  /* virtual methods for subclasses */
+  /**
+   * GESTrackElementClass::list_children_properties:
+   *
+   * Listing children properties is handled by
+   * ges_timeline_element_list_children_properties() instead.
+   *
+   * Deprecated: 1.14: Use #GESTimelineElementClass::list_children_properties
+   * instead
+   */
   GParamSpec** (*list_children_properties) (GESTrackElement * object,
               guint *n_properties);
+
+  /**
+   * GESTrackElementClass::lookup_child:
+   *
+   * Deprecated: 1.14: Use #GESTimelineElementClass::lookup_child
+   * instead
+   */
   gboolean (*lookup_child)                 (GESTrackElement *object,
                                             const gchar *prop_name,
                                             GstElement **element,
                                             GParamSpec **pspec);
-  /*< private >*/
-  /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING_LARGE];
+  /*< protected >*/
+  union {
+    gpointer _ges_reserved[GES_PADDING_LARGE];
+    struct {
+      gboolean default_has_internal_source;
+      GESTrackType default_track_type;
+    } abi;
+  } ABI;
 };
 
 GES_API
-GType ges_track_element_get_type               (void);
-
-GES_API
 GESTrack* ges_track_element_get_track          (GESTrackElement * object);
 
 GES_API
@@ -139,80 +156,48 @@ void ges_track_element_set_track_type          (GESTrackElement * object,
 
 GES_API
 GstElement * ges_track_element_get_nleobject   (GESTrackElement * object);
-GES_API
-GstElement * ges_track_element_get_gnlobject   (GESTrackElement * object);
 
 GES_API
 GstElement * ges_track_element_get_element     (GESTrackElement * object);
 
 GES_API
+gboolean ges_track_element_is_core             (GESTrackElement * object);
+
+GES_API
 gboolean ges_track_element_set_active          (GESTrackElement * object,
                                                gboolean active);
 
 GES_API
 gboolean ges_track_element_is_active           (GESTrackElement * object);
 
-GES_API GParamSpec **
-ges_track_element_list_children_properties     (GESTrackElement *object,
-                                               guint *n_properties);
+GES_API gboolean
+ges_track_element_set_has_internal_source      (GESTrackElement * object,
+                                               gboolean has_internal_source);
 
 GES_API
-gboolean ges_track_element_lookup_child        (GESTrackElement *object,
-                                               const gchar *prop_name,
-                                               GstElement **element,
-                                               GParamSpec **pspec);
+gboolean ges_track_element_has_internal_source (GESTrackElement * object);
 
 GES_API void
 ges_track_element_get_child_property_by_pspec (GESTrackElement * object,
                                               GParamSpec * pspec,
                                               GValue * value);
 
-GES_API void
-ges_track_element_get_child_property_valist   (GESTrackElement * object,
-                                              const gchar * first_property_name,
-                                              va_list var_args);
-
-GES_API
-void ges_track_element_get_child_properties   (GESTrackElement *object,
-                                              const gchar * first_property_name,
-                                              ...) G_GNUC_NULL_TERMINATED;
-
-GES_API void
-ges_track_element_set_child_property_valist   (GESTrackElement * object,
-                                              const gchar * first_property_name,
-                                              va_list var_args);
-
-GES_API void
-ges_track_element_set_child_property_by_pspec (GESTrackElement * object,
-                                              GParamSpec * pspec,
-                                              GValue * value);
-
-GES_API
-void ges_track_element_set_child_properties   (GESTrackElement * object,
-                                              const gchar * first_property_name,
-                                              ...) G_GNUC_NULL_TERMINATED;
-
-GES_API
-gboolean ges_track_element_set_child_property (GESTrackElement *object,
-                                              const gchar *property_name,
-                                              GValue * value);
-
-GES_API
-gboolean ges_track_element_get_child_property (GESTrackElement *object,
-                                              const gchar *property_name,
-                                              GValue * value);
-
-GES_API gboolean
-ges_track_element_edit                        (GESTrackElement * object,
-                                              GList *layers, GESEditMode mode,
-                                              GESEdge edge, guint64 position);
-
 GES_API gboolean
 ges_track_element_set_control_source          (GESTrackElement *object,
                                                GstControlSource *source,
                                                const gchar *property_name,
                                                const gchar *binding_type);
 
+GES_API void
+ges_track_element_clamp_control_source        (GESTrackElement * object,
+                                               const gchar * property_name);
+
+GES_API void
+ges_track_element_set_auto_clamp_control_sources (GESTrackElement * object,
+                                                  gboolean auto_clamp);
+GES_API gboolean
+ges_track_element_get_auto_clamp_control_sources (GESTrackElement * object);
+
 GES_API GstControlBinding *
 ges_track_element_get_control_binding         (GESTrackElement *object,
                                                const gchar *property_name);
@@ -227,5 +212,7 @@ ges_track_element_get_all_control_bindings    (GESTrackElement * trackelement);
 GES_API gboolean
 ges_track_element_remove_control_binding      (GESTrackElement * object,
                                                const gchar * property_name);
+
+#include "ges-track-element-deprecated.h"
+
 G_END_DECLS
-#endif /* _GES_TRACK_ELEMENT */
index 3cb3859..d3bd2ef 100644 (file)
 /**
  * SECTION:gestrack
  * @title: GESTrack
- * @short_description: Composition of objects
+ * @short_description: The output source of a #GESTimeline
  *
- * Corresponds to one output format (i.e. audio OR video).
+ * A #GESTrack acts an output source for a #GESTimeline. Each one
+ * essentially provides an additional #GstPad for the timeline, with
+ * #GESTrack:restriction-caps capabilities. Internally, a track
+ * wraps an #nlecomposition filtered by a #capsfilter.
  *
- * Contains the compatible TrackElement(s).
+ * A track will contain a number of #GESTrackElement-s, and its role is
+ * to select and activate these elements according to their timings when
+ * the timeline in played. For example, a track would activate a
+ * #GESSource when its #GESTimelineElement:start is reached by outputting
+ * its data for its #GESTimelineElement:duration. Similarly, a
+ * #GESOperation would be activated by applying its effect to the source
+ * data, starting from its #GESTimelineElement:start time and lasting for
+ * its #GESTimelineElement:duration.
+ *
+ * For most users, it will usually be sufficient to add newly created
+ * tracks to a timeline, but never directly add an element to a track.
+ * Whenever a #GESClip is added to a timeline, the clip adds its
+ * elements to the timeline's tracks and assumes responsibility for
+ * updating them.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -94,6 +110,7 @@ enum
   ARG_TYPE,
   ARG_DURATION,
   ARG_MIXING,
+  ARG_ID,
   ARG_LAST,
   TRACK_ELEMENT_ADDED,
   TRACK_ELEMENT_REMOVED,
@@ -112,6 +129,7 @@ G_DEFINE_TYPE_WITH_CODE (GESTrack, ges_track, GST_TYPE_BIN,
 
 static void composition_duration_cb (GstElement * composition, GParamSpec * arg
     G_GNUC_UNUSED, GESTrack * obj);
+static void ges_track_set_caps (GESTrack * track, const GstCaps * caps);
 
 /* Private methods/functions/callbacks */
 static void
@@ -215,6 +233,17 @@ update_gaps (GESTrack * track)
     if (!ges_track_element_is_active (trackelement))
       continue;
 
+    if (priv->timeline) {
+      guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement);
+
+      if (layer_prio != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+        GESLayer *layer = g_list_nth_data (priv->timeline->layers, layer_prio);
+
+        if (!ges_layer_get_active_for_track (layer, track))
+          continue;
+      }
+    }
+
     start = _START (trackelement);
     end = start + _DURATION (trackelement);
 
@@ -240,6 +269,9 @@ update_gaps (GESTrack * track)
         priv->gaps = g_list_prepend (priv->gaps, gap);
       }
 
+      /* FIXME: here the duration is set to the duration of the timeline,
+       * but elsewhere it is set to the duration of the composition. Are
+       * these always the same? */
       priv->duration = timeline_duration;
     }
   }
@@ -320,6 +352,9 @@ composition_duration_cb (GstElement * composition,
         GST_TIME_FORMAT, GST_TIME_ARGS (duration),
         GST_TIME_ARGS (track->priv->duration));
 
+    /* FIXME: here the duration is set to the duration of the composition,
+     * but elsewhere it is set to the duration of the timeline. Are these
+     * always the same? */
     track->priv->duration = duration;
 
     g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_DURATION]);
@@ -340,6 +375,15 @@ ges_track_get_composition (GESTrack * track)
   return track->priv->composition;
 }
 
+void
+ges_track_set_smart_rendering (GESTrack * track, gboolean rendering_smartly)
+{
+  GESTrackPrivate *priv = track->priv;
+
+  g_object_set (priv->capsfilter, "caps",
+      rendering_smartly ? NULL : priv->restriction_caps, NULL);
+}
+
 /* FIXME: Find out how to avoid doing this "hack" using the GDestroyNotify
  * function pointer in the trackelements_by_start GSequence
  *
@@ -348,7 +392,8 @@ ges_track_get_composition (GESTrack * track)
  * accessing it
  */
 static gboolean
-remove_object_internal (GESTrack * track, GESTrackElement * object)
+remove_object_internal (GESTrack * track, GESTrackElement * object,
+    gboolean emit, GError ** error)
 {
   GESTrackPrivate *priv;
   GstElement *nleobject;
@@ -358,25 +403,30 @@ remove_object_internal (GESTrack * track, GESTrackElement * object)
   priv = track->priv;
 
   if (G_UNLIKELY (ges_track_element_get_track (object) != track)) {
-    GST_WARNING ("Object belongs to another track");
+    GST_WARNING_OBJECT (track, "Object belongs to another track");
+    return FALSE;
+  }
+
+  if (!ges_track_element_set_track (object, NULL, error)) {
+    GST_INFO_OBJECT (track, "Failed to unset the track for %" GES_FORMAT,
+        GES_ARGS (object));
     return FALSE;
   }
+  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
 
   if ((nleobject = ges_track_element_get_nleobject (object))) {
     GST_DEBUG ("Removing NleObject '%s' from composition '%s'",
         GST_ELEMENT_NAME (nleobject), GST_ELEMENT_NAME (priv->composition));
 
     if (!ges_nle_composition_remove_object (priv->composition, nleobject)) {
-      GST_WARNING ("Failed to remove nleobject from composition");
+      GST_WARNING_OBJECT (track, "Failed to remove nleobject from composition");
       return FALSE;
     }
   }
 
-  ges_track_element_set_track (object, NULL);
-  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
-
-  g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
-      GES_TRACK_ELEMENT (object));
+  if (emit)
+    g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
+        GES_TRACK_ELEMENT (object));
 
   gst_object_unref (object);
 
@@ -386,7 +436,7 @@ remove_object_internal (GESTrack * track, GESTrackElement * object)
 static void
 dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
 {
-  remove_object_internal (track, trackelement);
+  remove_object_internal (track, trackelement, TRUE, NULL);
 }
 
 /* GstElement virtual methods */
@@ -394,13 +444,53 @@ dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
 static GstStateChangeReturn
 ges_track_change_state (GstElement * element, GstStateChange transition)
 {
-  if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
+  GESTrack *track = GES_TRACK (element);
+
+  if (transition == GST_STATE_CHANGE_READY_TO_PAUSED &&
+      track->priv->valid_thread == g_thread_self ())
     track_resort_and_fill_gaps (GES_TRACK (element));
 
   return GST_ELEMENT_CLASS (ges_track_parent_class)->change_state (element,
       transition);
 }
 
+static void
+ges_track_handle_message (GstBin * bin, GstMessage * message)
+{
+  GESTrack *track = GES_TRACK (bin);
+
+  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STREAM_COLLECTION) {
+    gint i;
+    GList *selected_streams = NULL;
+    GstStreamCollection *collection;
+
+    gst_message_parse_stream_collection (message, &collection);
+
+    for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
+      GstStream *stream = gst_stream_collection_get_stream (collection, i);
+      GstStreamType stype = gst_stream_get_stream_type (stream);
+
+      if ((track->type == GES_TRACK_TYPE_VIDEO
+              && stype == GST_STREAM_TYPE_VIDEO)
+          || (track->type == GES_TRACK_TYPE_AUDIO
+              && stype == GST_STREAM_TYPE_AUDIO)
+          || (stype == GST_STREAM_TYPE_UNKNOWN)) {
+
+        selected_streams =
+            g_list_append (selected_streams,
+            (gchar *) gst_stream_get_stream_id (stream));
+      }
+    }
+
+    if (selected_streams) {
+      gst_element_send_event (GST_ELEMENT (GST_MESSAGE_SRC (message)),
+          gst_event_new_select_streams (selected_streams));
+      g_list_free (selected_streams);
+    }
+  }
+  gst_element_post_message (GST_ELEMENT_CAST (bin), message);
+}
+
 /* GObject virtual methods */
 static void
 ges_track_get_property (GObject * object, guint property_id,
@@ -424,6 +514,9 @@ ges_track_get_property (GObject * object, guint property_id,
     case ARG_MIXING:
       g_value_set_boolean (value, track->priv->mixing);
       break;
+    case ARG_ID:
+      g_object_get_property (G_OBJECT (track->priv->composition), "id", value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -448,6 +541,9 @@ ges_track_set_property (GObject * object, guint property_id,
     case ARG_MIXING:
       ges_track_set_mixing (track, g_value_get_boolean (value));
       break;
+    case ARG_ID:
+      g_object_set_property (G_OBJECT (track->priv->composition), "id", value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -467,6 +563,7 @@ ges_track_dispose (GObject * object)
   g_list_free_full (priv->gaps, (GDestroyNotify) free_gap);
   ges_nle_object_commit (track->priv->composition, TRUE);
 
+  gst_clear_object (&track->priv->mixing_operation);
   if (priv->composition) {
     gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad);
     gst_bin_remove (GST_BIN (object), priv->composition);
@@ -497,19 +594,28 @@ ges_track_constructed (GObject * object)
 {
   GESTrack *self = GES_TRACK (object);
   gchar *componame = NULL;
+  gchar *capsfiltername = NULL;
 
   if (self->type == GES_TRACK_TYPE_VIDEO) {
     componame =
         g_strdup_printf ("video_%s", GST_OBJECT_NAME (self->priv->composition));
+    capsfiltername =
+        g_strdup_printf ("video_restriction_%s",
+        GST_OBJECT_NAME (self->priv->capsfilter));
   } else if (self->type == GES_TRACK_TYPE_AUDIO) {
     componame =
         g_strdup_printf ("audio_%s", GST_OBJECT_NAME (self->priv->composition));
+    capsfiltername =
+        g_strdup_printf ("audio_restriction_%s",
+        GST_OBJECT_NAME (self->priv->capsfilter));
   }
 
   if (componame) {
     gst_object_set_name (GST_OBJECT (self->priv->composition), componame);
+    gst_object_set_name (GST_OBJECT (self->priv->capsfilter), capsfiltername);
 
     g_free (componame);
+    g_free (capsfiltername);
   }
 
   if (!gst_bin_add (GST_BIN (self), self->priv->composition))
@@ -548,7 +654,7 @@ ges_track_constructed (GObject * object)
       }
     }
 
-    self->priv->mixing_operation = nleobject;
+    self->priv->mixing_operation = gst_object_ref (nleobject);
 
   } else {
     GST_INFO_OBJECT (self, "No way to create a main mixer");
@@ -560,9 +666,12 @@ ges_track_class_init (GESTrackClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GstElementClass *gstelement_class = (GstElementClass *) klass;
+  GstBinClass *bin_class = GST_BIN_CLASS (klass);
 
   gstelement_class->change_state = GST_DEBUG_FUNCPTR (ges_track_change_state);
 
+  bin_class->handle_message = GST_DEBUG_FUNCPTR (ges_track_handle_message);
+
   object_class->get_property = ges_track_get_property;
   object_class->set_property = ges_track_set_property;
   object_class->dispose = ges_track_dispose;
@@ -572,13 +681,26 @@ ges_track_class_init (GESTrackClass * klass)
   /**
    * GESTrack:caps:
    *
-   * Caps used to filter/choose the output stream. This is generally set to
-   * a generic set of caps like 'video/x-raw' for raw video.
+   * The capabilities used to choose the output of the #GESTrack's
+   * elements. Internally, this is used to select output streams when
+   * several may be available, by determining whether its #GstPad is
+   * compatible (see #NleObject:caps for #nlecomposition). As such,
+   * this is used as a weaker indication of the desired output type of the
+   * track, **before** the #GESTrack:restriction-caps is applied.
+   * Therefore, this should be set to a *generic* superset of the
+   * #GESTrack:restriction-caps, such as "video/x-raw(ANY)". In addition,
+   * it should match with the track's #GESTrack:track-type.
+   *
+   * Note that when you set this property, the #GstCapsFeatures of all its
+   * #GstStructure-s will be automatically set to #GST_CAPS_FEATURES_ANY.
+   *
+   * Once a track has been added to a #GESTimeline, you should not change
+   * this.
    *
    * Default value: #GST_CAPS_ANY.
    */
   properties[ARG_CAPS] = g_param_spec_boxed ("caps", "Caps",
-      "Caps used to filter/choose the output stream",
+      "Caps used to choose the output stream",
       GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
   g_object_class_install_property (object_class, ARG_CAPS,
       properties[ARG_CAPS]);
@@ -586,13 +708,19 @@ ges_track_class_init (GESTrackClass * klass)
   /**
    * GESTrack:restriction-caps:
    *
-   * Caps used to filter/choose the output stream.
+   * The capabilities that specifies the final output format of the
+   * #GESTrack. For example, for a video track, it would specify the
+   * height, width, framerate and other properties of the stream.
+   *
+   * You may change this property after the track has been added to a
+   * #GESTimeline, but it must remain compatible with the track's
+   * #GESTrack:caps.
    *
    * Default value: #GST_CAPS_ANY.
    */
   properties[ARG_RESTRICTION_CAPS] =
       g_param_spec_boxed ("restriction-caps", "Restriction caps",
-      "Caps used to filter/choose the output stream", GST_TYPE_CAPS,
+      "Caps used as a final filter on the output stream", GST_TYPE_CAPS,
       G_PARAM_READWRITE);
   g_object_class_install_property (object_class, ARG_RESTRICTION_CAPS,
       properties[ARG_RESTRICTION_CAPS]);
@@ -604,6 +732,8 @@ ges_track_class_init (GESTrackClass * klass)
    *
    * Default value: O
    */
+  /* FIXME: is duration the duration of the timeline or the duration of
+   * the underlying composition? */
   properties[ARG_DURATION] = g_param_spec_uint64 ("duration", "Duration",
       "The current duration of the track", 0, G_MAXUINT64, GST_SECOND,
       G_PARAM_READABLE);
@@ -613,12 +743,12 @@ ges_track_class_init (GESTrackClass * klass)
   /**
    * GESTrack:track-type:
    *
-   * Type of stream the track outputs. This is used when creating the #GESTrack
-   * to specify in generic terms what type of content will be outputted.
+   * The track type of the track. This controls the type of
+   * #GESTrackElement-s that can be added to the track. This should
+   * match with the track's #GESTrack:caps.
    *
-   * It also serves as a 'fast' way to check what type of data will be outputted
-   * from the #GESTrack without having to actually check the #GESTrack's caps
-   * property.
+   * Once a track has been added to a #GESTimeline, you should not change
+   * this.
    */
   properties[ARG_TYPE] = g_param_spec_flags ("track-type", "TrackType",
       "Type of stream the track outputs",
@@ -630,44 +760,66 @@ ges_track_class_init (GESTrackClass * klass)
   /**
    * GESTrack:mixing:
    *
-   * Whether layer mixing is activated or not on the track.
+   * Whether the track should support the mixing of #GESLayer data, such
+   * as composing the video data of each layer (when part of the video
+   * data is transparent, the next layer will become visible) or adding
+   * together the audio data. As such, for audio and video tracks, you'll
+   * likely want to keep this set to %TRUE.
    */
   properties[ARG_MIXING] = g_param_spec_boolean ("mixing", "Mixing",
       "Whether layer mixing is activated on the track or not",
-      TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+      TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
   g_object_class_install_property (object_class, ARG_MIXING,
       properties[ARG_MIXING]);
 
+  /**
+   * GESTrack:id:
+   *
+   * The #nlecomposition:id of the underlying #nlecomposition.
+   *
+   * Since: 1.18
+   */
+  properties[ARG_ID] =
+      g_param_spec_string ("id", "Id", "The stream-id of the composition",
+      NULL,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_DOC_SHOW_DEFAULT);
+  g_object_class_install_property (object_class, ARG_ID, properties[ARG_ID]);
+
   gst_element_class_add_static_pad_template (gstelement_class,
       &ges_track_src_pad_template);
 
   /**
    * GESTrack::track-element-added:
-   * @object: the #GESTrack
-   * @effect: the #GESTrackElement that was added.
+   * @object: The #GESTrack
+   * @effect: The element that was added
    *
-   * Will be emitted after a track element was added to the track.
+   * Will be emitted after a track element is added to the track.
    */
   ges_track_signals[TRACK_ELEMENT_ADDED] =
       g_signal_new ("track-element-added", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, GES_TYPE_TRACK_ELEMENT);
 
   /**
    * GESTrack::track-element-removed:
-   * @object: the #GESTrack
-   * @effect: the #GESTrackElement that was removed.
+   * @object: The #GESTrack
+   * @effect: The element that was removed
    *
-   * Will be emitted after a track element was removed from the track.
+   * Will be emitted after a track element is removed from the track.
    */
   ges_track_signals[TRACK_ELEMENT_REMOVED] =
       g_signal_new ("track-element-removed", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic,
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, GES_TYPE_TRACK_ELEMENT);
 
   /**
    * GESTrack::commited:
-   * @track: the #GESTrack
+   * @track: The #GESTrack
+   *
+   * This signal will be emitted once the changes initiated by
+   * ges_track_commit() have been executed in the backend. In particular,
+   * this will be emitted whenever the underlying #nlecomposition has been
+   * committed (see #nlecomposition::commited).
    */
   ges_track_signals[COMMITED] =
       g_signal_new ("commited", G_TYPE_FROM_CLASS (klass),
@@ -692,6 +844,7 @@ ges_track_init (GESTrack * self)
   self->priv->gaps = NULL;
   self->priv->mixing = TRUE;
   self->priv->restriction_caps = NULL;
+  self->priv->last_gap_disabled = TRUE;
 
   g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration",
       G_CALLBACK (composition_duration_cb), self);
@@ -701,15 +854,29 @@ ges_track_init (GESTrack * self)
 
 /**
  * ges_track_new:
- * @type: The type of track
- * @caps: (transfer full): The caps to restrict the output of the track to.
+ * @type: The #GESTrack:track-type for the track
+ * @caps: (transfer full): The #GESTrack:caps for the track
+ *
+ * Creates a new track with the given track-type and caps.
  *
- * Creates a new #GESTrack with the given @type and @caps.
+ * If @type is #GES_TRACK_TYPE_VIDEO, and @caps is a subset of
+ * "video/x-raw(ANY)", then a #GESVideoTrack is created. This will
+ * automatically choose a gap creation method suitable for video data. You
+ * will likely want to set #GESTrack:restriction-caps separately. You may
+ * prefer to use the ges_video_track_new() method instead.
  *
- * The newly created track will steal a reference to the caps. If you wish to
- * use those caps elsewhere, you will have to take an extra reference.
+ * If @type is #GES_TRACK_TYPE_AUDIO, and @caps is a subset of
+ * "audio/x-raw(ANY)", then a #GESAudioTrack is created. This will
+ * automatically choose a gap creation method suitable for audio data, and
+ * will set the #GESTrack:restriction-caps to the default for
+ * #GESAudioTrack. You may prefer to use the ges_audio_track_new() method
+ * instead.
  *
- * Returns: (transfer floating): A new #GESTrack.
+ * Otherwise, a plain #GESTrack is returned. You will likely want to set
+ * the #GESTrack:restriction-caps and call
+ * ges_track_set_create_element_for_gap_func() on the returned track.
+ *
+ * Returns: (transfer floating): A new track.
  */
 GESTrack *
 ges_track_new (GESTrackType type, GstCaps * caps)
@@ -727,6 +894,8 @@ ges_track_new (GESTrackType type, GstCaps * caps)
       ges_track_set_caps (track, caps);
 
       gst_caps_unref (tmpcaps);
+      /* FIXME: ges_track_set_caps does not take ownership of caps,
+       * so we also need to unref caps */
       return track;
     }
     gst_caps_unref (tmpcaps);
@@ -739,6 +908,8 @@ ges_track_new (GESTrackType type, GstCaps * caps)
       ges_track_set_caps (track, caps);
 
       gst_caps_unref (tmpcaps);
+      /* FIXME: ges_track_set_caps does not take ownership of caps,
+       * so we also need to unref caps */
       return track;
     }
 
@@ -754,29 +925,43 @@ ges_track_new (GESTrackType type, GstCaps * caps)
 
 /**
  * ges_track_set_timeline:
- * @track: a #GESTrack
- * @timeline: a #GESTimeline
+ * @track: A #GESTrack
+ * @timeline (nullable): A #GESTimeline
  *
- * Sets @timeline as the timeline controlling @track.
+ * Informs the track that it belongs to the given timeline. Calling this
+ * does not actually add the track to the timeline. For that, you should
+ * use ges_timeline_add_track(), which will also take care of informing
+ * the track that it belongs to the timeline. As such, there is no need
+ * for you to call this method.
  */
+/* FIXME: this should probably be deprecated and only used internally */
 void
 ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
 {
+  GSequenceIter *it;
+  g_return_if_fail (GES_IS_TRACK (track));
+  g_return_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline));
   GST_DEBUG ("track:%p, timeline:%p", track, timeline);
 
   track->priv->timeline = timeline;
+
+  for (it = g_sequence_get_begin_iter (track->priv->trackelements_by_start);
+      g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) {
+    GESTimelineElement *trackelement =
+        GES_TIMELINE_ELEMENT (g_sequence_get (it));
+    ges_timeline_element_set_timeline (trackelement, timeline);
+  }
   track_resort_and_fill_gaps (track);
 }
 
 /**
  * ges_track_set_caps:
- * @track: a #GESTrack
- * @caps: the #GstCaps to set
+ * @track: A #GESTrack
+ * @caps: The new caps for @track
  *
- * Sets the given @caps on the track.
- * Note that the capsfeatures of @caps will always be set
- * to ANY. If you want to restrict them, you should
- * do it in #ges_track_set_restriction_caps.
+ * Sets the #GESTrack:caps for the track. The new #GESTrack:caps of the
+ * track will be a copy of @caps, except its #GstCapsFeatures will be
+ * automatically set to #GST_CAPS_FEATURES_ANY.
  */
 void
 ges_track_set_caps (GESTrack * track, const GstCaps * caps)
@@ -805,10 +990,13 @@ ges_track_set_caps (GESTrack * track, const GstCaps * caps)
 
 /**
  * ges_track_set_restriction_caps:
- * @track: a #GESTrack
- * @caps: the #GstCaps to set
+ * @track: A #GESTrack
+ * @caps: The new restriction-caps for @track
+ *
+ * Sets the #GESTrack:restriction-caps for the track.
  *
- * Sets the given @caps as the caps the track has to output.
+ * > **NOTE**: Restriction caps are **not** taken into account when
+ * > using #GESPipeline:mode=#GES_PIPELINE_MODE_SMART_RENDER.
  */
 void
 ges_track_set_restriction_caps (GESTrack * track, const GstCaps * caps)
@@ -827,23 +1015,34 @@ ges_track_set_restriction_caps (GESTrack * track, const GstCaps * caps)
     gst_caps_unref (priv->restriction_caps);
   priv->restriction_caps = gst_caps_copy (caps);
 
-  g_object_set (priv->capsfilter, "caps", caps, NULL);
+  if (!track->priv->timeline ||
+      !ges_timeline_get_smart_rendering (track->priv->timeline))
+    g_object_set (priv->capsfilter, "caps", caps, NULL);
 
   g_object_notify (G_OBJECT (track), "restriction-caps");
 }
 
 /**
  * ges_track_update_restriction_caps:
- * @track: a #GESTrack
- * @caps: the #GstCaps to update with
+ * @track: A #GESTrack
+ * @caps: The caps to update the restriction-caps with
  *
- * Updates the restriction caps by modifying all the fields present in @caps
- * in the original restriction caps. If for example the current restriction caps
- * are video/x-raw, format=I420, width=360 and @caps is video/x-raw, format=RGB,
- * the restriction caps will be updated to video/x-raw, format=RGB, width=360.
+ * Updates the #GESTrack:restriction-caps of the track using the fields
+ * found in the given caps. Each of the #GstStructure-s in @caps is
+ * compared against the existing structure with the same index in the
+ * current #GESTrack:restriction-caps. If there is no corresponding
+ * existing structure at that index, then the new structure is simply
+ * copied to that index. Otherwise, any fields in the new structure are
+ * copied into the existing structure. This will replace existing values,
+ * and may introduce new ones, but any fields 'missing' in the new
+ * structure are left unchanged in the existing structure.
  *
- * Modification happens for each structure in the new caps, and
- * one can add new fields or structures through that function.
+ * For example, if the existing #GESTrack:restriction-caps are
+ * "video/x-raw, width=480, height=360", and the updating caps is
+ * "video/x-raw, format=I420, width=500; video/x-bayer, width=400", then
+ * the new #GESTrack:restriction-caps after calling this will be
+ * "video/x-raw, width=500, height=360, format=I420; video/x-bayer,
+ * width=400".
  */
 void
 ges_track_update_restriction_caps (GESTrack * self, const GstCaps * caps)
@@ -870,6 +1069,8 @@ ges_track_update_restriction_caps (GESTrack * self, const GstCaps * caps)
     } else
       gst_caps_append_structure (new_restriction_caps,
           gst_structure_copy (new));
+    /* FIXME: maybe appended structure should also have its CapsFeatures
+     * copied over? */
   }
 
   ges_track_set_restriction_caps (self, new_restriction_caps);
@@ -878,10 +1079,10 @@ ges_track_update_restriction_caps (GESTrack * self, const GstCaps * caps)
 
 /**
  * ges_track_set_mixing:
- * @track: a #GESTrack
- * @mixing: TRUE if the track should be mixing, FALSE otherwise.
+ * @track: A #GESTrack
+ * @mixing: Whether @track should be mixing
  *
- * Sets if the #GESTrack should be mixing.
+ * Sets the #GESTrack:mixing for the track.
  */
 void
 ges_track_set_mixing (GESTrack * track, gboolean mixing)
@@ -889,14 +1090,15 @@ ges_track_set_mixing (GESTrack * track, gboolean mixing)
   g_return_if_fail (GES_IS_TRACK (track));
   CHECK_THREAD (track);
 
-  if (!track->priv->mixing_operation) {
-    GST_DEBUG_OBJECT (track, "Track will be set to mixing = %d", mixing);
-    track->priv->mixing = mixing;
+  if (mixing == track->priv->mixing) {
+    GST_DEBUG_OBJECT (track, "Mixing is already set to the same value");
+
     return;
   }
 
-  if (mixing == track->priv->mixing) {
-    GST_DEBUG_OBJECT (track, "Mixing is already set to the same value");
+  if (!track->priv->mixing_operation) {
+    GST_DEBUG_OBJECT (track, "Track will be set to mixing = %d", mixing);
+    goto notify;
   }
 
   if (mixing) {
@@ -914,29 +1116,72 @@ ges_track_set_mixing (GESTrack * track, gboolean mixing)
     }
   }
 
+notify:
   track->priv->mixing = mixing;
 
+  if (track->priv->timeline)
+    ges_timeline_set_smart_rendering (track->priv->timeline,
+        ges_timeline_get_smart_rendering (track->priv->timeline));
+  g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_MIXING]);
+
   GST_DEBUG_OBJECT (track, "The track has been set to mixing = %d", mixing);
 }
 
+static gboolean
+remove_element_internal (GESTrack * track, GESTrackElement * object,
+    gboolean emit, GError ** error)
+{
+  GSequenceIter *it;
+  GESTrackPrivate *priv = track->priv;
+
+  GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
+
+  it = g_hash_table_lookup (priv->trackelements_iter, object);
+  g_sequence_remove (it);
+
+  if (remove_object_internal (track, object, emit, error) == TRUE) {
+    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
+
+    return TRUE;
+  }
+
+  g_hash_table_insert (track->priv->trackelements_iter, object,
+      g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
+          (GCompareDataFunc) element_start_compare, NULL));
+
+  return FALSE;
+}
+
 /**
- * ges_track_add_element:
- * @track: a #GESTrack
- * @object: (transfer floating): the #GESTrackElement to add
+ * ges_track_add_element_full:
+ * @track: A #GESTrack
+ * @object: (transfer floating): The element to add
+ * @error: (nullable): Return location for an error
+ *
+ * Adds the given track element to the track, which takes ownership of the
+ * element.
  *
- * Adds the given object to the track. Sets the object's controlling track,
- * and thus takes ownership of the @object.
+ * Note that this can fail if it would break a configuration rule of the
+ * track's #GESTimeline.
  *
- * An object can only be added to one track.
+ * Note that a #GESTrackElement can only be added to one track.
  *
- * Returns: #TRUE if the object was properly added. #FALSE if the track does not
- * want to accept the object.
+ * Returns: %TRUE if @object was successfully added to @track.
+ * Since: 1.18
  */
 gboolean
-ges_track_add_element (GESTrack * track, GESTrackElement * object)
+ges_track_add_element_full (GESTrack * track, GESTrackElement * object,
+    GError ** error)
 {
+  GESTimeline *timeline;
+  GESTimelineElement *el;
+
   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+  g_return_val_if_fail (!error || !*error, FALSE);
+
+  el = GES_TIMELINE_ELEMENT (object);
+
   CHECK_THREAD (track);
 
   GST_DEBUG ("track:%p, object:%p", track, object);
@@ -948,12 +1193,14 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
     return FALSE;
   }
 
-  if (G_UNLIKELY (!ges_track_element_set_track (object, track))) {
-    GST_ERROR ("Couldn't properly add the object to the Track");
+  if (!ges_track_element_set_track (object, track, error)) {
+    GST_INFO_OBJECT (track, "Failed to set the track for %" GES_FORMAT,
+        GES_ARGS (object));
     gst_object_ref_sink (object);
     gst_object_unref (object);
     return FALSE;
   }
+  ges_timeline_element_set_timeline (el, NULL);
 
   GST_DEBUG ("Adding object %s to ourself %s",
       GST_OBJECT_NAME (ges_track_element_get_nleobject (object)),
@@ -962,6 +1209,9 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
   if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition,
               ges_track_element_get_nleobject (object)))) {
     GST_WARNING ("Couldn't add object to the NleComposition");
+    if (!ges_track_element_set_track (object, NULL, NULL))
+      GST_ERROR_OBJECT (track, "Failed to unset track of element %"
+          GES_FORMAT, GES_ARGS (object));
     gst_object_ref_sink (object);
     gst_object_unref (object);
     return FALSE;
@@ -972,8 +1222,22 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
       g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
           (GCompareDataFunc) element_start_compare, NULL));
 
-  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object),
-      track->priv->timeline);
+  timeline = track->priv->timeline;
+  ges_timeline_element_set_timeline (el, timeline);
+  /* check that we haven't broken the timeline configuration by adding this
+   * element to the track */
+  if (timeline
+      && !timeline_tree_can_move_element (timeline_get_tree (timeline), el,
+          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration,
+          error)) {
+    GST_INFO_OBJECT (track,
+        "Could not add the track element %" GES_FORMAT
+        " to the track because it breaks the timeline " "configuration rules",
+        GES_ARGS (el));
+    remove_element_internal (track, object, FALSE, NULL);
+    return FALSE;
+  }
+
   g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_ADDED], 0,
       GES_TRACK_ELEMENT (object));
 
@@ -981,13 +1245,30 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
 }
 
 /**
+ * ges_track_add_element:
+ * @track: A #GESTrack
+ * @object: (transfer floating): The element to add
+ *
+ * See ges_track_add_element(), which also gives an error.
+ *
+ * Returns: %TRUE if @object was successfully added to @track.
+ */
+gboolean
+ges_track_add_element (GESTrack * track, GESTrackElement * object)
+{
+  return ges_track_add_element_full (track, object, NULL);
+}
+
+/**
  * ges_track_get_elements:
- * @track: a #GESTrack
+ * @track: A #GESTrack
  *
- * Gets the #GESTrackElement contained in @track
+ * Gets the track elements contained in the track. The returned list is
+ * sorted by the element's #GESTimelineElement:priority and
+ * #GESTimelineElement:start.
  *
- * Returns: (transfer full) (element-type GESTrackElement): the list of
- * #GESTrackElement present in the Track sorted by priority and start.
+ * Returns: (transfer full) (element-type GESTrackElement): A list of
+ * all the #GESTrackElement-s in @track.
  */
 GList *
 ges_track_get_elements (GESTrack * track)
@@ -1005,55 +1286,54 @@ ges_track_get_elements (GESTrack * track)
 }
 
 /**
- * ges_track_remove_element:
- * @track: a #GESTrack
- * @object: the #GESTrackElement to remove
+ * ges_track_remove_element_full:
+ * @track: A #GESTrack
+ * @object: The element to remove
+ * @error: (nullable): Return location for an error
  *
- * Removes the object from the track and unparents it.
- * Unparenting it means the reference owned by @track on the @object will be
- * removed. If you wish to use the @object after this function, make sure you
- * call gst_object_ref() before removing it from the @track.
+ * Removes the given track element from the track, which revokes
+ * ownership of the element.
  *
- * Returns: #TRUE if the object was removed, else #FALSE if the track
- * could not remove the object (like if it didn't belong to the track).
+ * Returns: %TRUE if @object was successfully removed from @track.
+ * Since: 1.18
  */
 gboolean
-ges_track_remove_element (GESTrack * track, GESTrackElement * object)
+ges_track_remove_element_full (GESTrack * track, GESTrackElement * object,
+    GError ** error)
 {
-  GSequenceIter *it;
-  GESTrackPrivate *priv;
-
   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
-  CHECK_THREAD (track);
-
-  priv = track->priv;
-
-  GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
-
-  it = g_hash_table_lookup (priv->trackelements_iter, object);
-  g_sequence_remove (it);
+  g_return_val_if_fail (!error || !*error, FALSE);
 
-  if (remove_object_internal (track, object) == TRUE) {
-    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
-
-    return TRUE;
-  }
+  if (!track->priv->timeline
+      || !ges_timeline_is_disposed (track->priv->timeline))
+    CHECK_THREAD (track);
 
-  g_hash_table_insert (track->priv->trackelements_iter, object,
-      g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
-          (GCompareDataFunc) element_start_compare, NULL));
+  return remove_element_internal (track, object, TRUE, error);
+}
 
-  return FALSE;
+/**
+ * ges_track_remove_element:
+ * @track: A #GESTrack
+ * @object: The element to remove
+ *
+ * See ges_track_remove_element_full(), which also returns an error.
+ *
+ * Returns: %TRUE if @object was successfully removed from @track.
+ */
+gboolean
+ges_track_remove_element (GESTrack * track, GESTrackElement * object)
+{
+  return ges_track_remove_element_full (track, object, NULL);
 }
 
 /**
  * ges_track_get_caps:
- * @track: a #GESTrack
+ * @track: A #GESTrack
  *
- * Get the #GstCaps this track is configured to output.
+ * Get the #GESTrack:caps of the track.
  *
- * Returns: The #GstCaps this track is configured to output.
+ * Returns: The caps of @track.
  */
 const GstCaps *
 ges_track_get_caps (GESTrack * track)
@@ -1066,11 +1346,12 @@ ges_track_get_caps (GESTrack * track)
 
 /**
  * ges_track_get_timeline:
- * @track: a #GESTrack
+ * @track: A #GESTrack
  *
- * Get the #GESTimeline this track belongs to. Can be %NULL.
+ * Get the timeline this track belongs to.
  *
- * Returns: (nullable): The #GESTimeline this track belongs to. Can be %NULL.
+ * Returns: (nullable): The timeline that @track belongs to, or %NULL if
+ * it does not belong to a timeline.
  */
 const GESTimeline *
 ges_track_get_timeline (GESTrack * track)
@@ -1083,11 +1364,11 @@ ges_track_get_timeline (GESTrack * track)
 
 /**
  * ges_track_get_mixing:
- * @track: a #GESTrack
+ * @track: A #GESTrack
  *
- *  Gets if the underlying #NleComposition contains an expandable mixer.
+ * Gets the #GESTrack:mixing of the track.
  *
- * Returns: #True if there is a mixer, #False otherwise.
+ * Returns: Whether @track is mixing.
  */
 gboolean
 ges_track_get_mixing (GESTrack * track)
@@ -1099,18 +1380,24 @@ ges_track_get_mixing (GESTrack * track)
 
 /**
  * ges_track_commit:
- * @track: a #GESTrack
+ * @track: A #GESTrack
  *
- * Commits all the pending changes of the TrackElement contained in the
+ * Commits all the pending changes for the elements contained in the
  * track.
  *
- * When timing changes happen in a timeline, the changes are not
- * directly done inside NLE. This method needs to be called so any changes
- * on a clip contained in the timeline actually happen at the media
- * processing level.
+ * When changes are made to the timing or priority of elements within a
+ * track, they are not directly executed for the underlying
+ * #nlecomposition and its children. This method will finally execute
+ * these changes so they are reflected in the data output of the track.
+ *
+ * Any pending changes will be executed in the backend. The
+ * #GESTimeline::commited signal will be emitted once this has completed.
  *
- * Returns: %TRUE if something as been commited %FALSE if nothing needed
- * to be commited
+ * Note that ges_timeline_commit() will call this method on all of its
+ * tracks, so you are unlikely to need to use this directly.
+ *
+ * Returns: %TRUE if pending changes were committed, or %FALSE if nothing
+ * needed to be committed.
  */
 gboolean
 ges_track_commit (GESTrack * track)
@@ -1126,13 +1413,19 @@ ges_track_commit (GESTrack * track)
 
 /**
  * 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
+ * @track: A #GESTrack
+ * @func: (scope notified): The function to be used to create a source
+ * #GstElement that can fill gaps in @track
+ *
+ * Sets the function that will be used to create a #GstElement that can be
+ * used as a source to fill the gaps of the track. A gap is a timeline
+ * region where the track has no #GESTrackElement sources. Therefore, you
+ * are likely to want the #GstElement returned by the function to always
+ * produce 'empty' content, defined relative to the stream type, such as
+ * transparent frames for a video, or mute samples for audio.
  *
- * 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_audio_track_new and #ges_video_track_new constructor when possible.
+ * #GESAudioTrack and #GESVideoTrack objects are created with such a
+ * function already set appropriately.
  */
 void
 ges_track_set_create_element_for_gap_func (GESTrack * track,
@@ -1143,3 +1436,29 @@ ges_track_set_create_element_for_gap_func (GESTrack * track,
 
   track->priv->create_element_for_gaps = func;
 }
+
+/**
+ * ges_track_get_restriction_caps:
+ * @track: A #GESTrack
+ *
+ * Gets the #GESTrack:restriction-caps of the track.
+ *
+ * Returns: (transfer full): The restriction-caps of @track.
+ *
+ * Since: 1.18
+ */
+GstCaps *
+ges_track_get_restriction_caps (GESTrack * track)
+{
+  GESTrackPrivate *priv;
+
+  g_return_val_if_fail (GES_IS_TRACK (track), NULL);
+  CHECK_THREAD (track);
+
+  priv = track->priv;
+
+  if (priv->restriction_caps)
+    return gst_caps_ref (priv->restriction_caps);
+
+  return NULL;
+}
index e60b2fe..46da9c8 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TRACK
-#define _GES_TRACK
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TRACK            ges_track_get_type()
-#define GES_TRACK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TRACK, GESTrack))
-#define GES_TRACK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TRACK, GESTrackClass))
-#define GES_IS_TRACK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TRACK))
-#define GES_IS_TRACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TRACK))
-#define GES_TRACK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TRACK, GESTrackClass))
-
-typedef struct _GESTrackPrivate GESTrackPrivate;
+GES_DECLARE_TYPE(Track, track, TRACK);
 
 /**
  * GESCreateElementForGapFunc:
- * @track: the #GESTrack
+ * @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.
+ * A function that creates a #GstElement that can be used as a source to
+ * fill the gaps of the track. A gap is a timeline region where the track
+ * has no #GESTrackElement sources.
  *
- * Returns: A #GstElement (must be a source) that will be used to
- * fill the gaps (periods of time in @track that containes no source).
+ * Returns: A source #GstElement to fill gaps in @track.
  */
 typedef GstElement* (*GESCreateElementForGapFunc) (GESTrack *track);
 
 /**
  * GESTrack:
- * @type: a #GESTrackType indicting the basic type of the track.
+ * @type: The #GESTrack:track-type of the track
  */
 struct _GESTrack
 {
@@ -90,28 +83,41 @@ const GESTimeline* ges_track_get_timeline                    (GESTrack *track);
 GES_API
 gboolean           ges_track_commit                          (GESTrack *track);
 GES_API
-void               ges_track_set_timeline                    (GESTrack *track, GESTimeline *timeline);
+void               ges_track_set_timeline                    (GESTrack *track,
+                                                              GESTimeline *timeline);
+GES_API
+gboolean           ges_track_add_element                     (GESTrack *track,
+                                                              GESTrackElement *object);
+GES_API
+gboolean           ges_track_add_element_full                (GESTrack *track,
+                                                              GESTrackElement *object,
+                                                              GError ** error);
 GES_API
-gboolean           ges_track_add_element                     (GESTrack *track, GESTrackElement *object);
+gboolean           ges_track_remove_element                  (GESTrack *track,
+                                                              GESTrackElement *object);
 GES_API
-gboolean           ges_track_remove_element                  (GESTrack *track, GESTrackElement *object);
+gboolean           ges_track_remove_element_full             (GESTrack *track,
+                                                              GESTrackElement *object,
+                                                              GError ** error);
 GES_API
-void               ges_track_set_create_element_for_gap_func (GESTrack *track, GESCreateElementForGapFunc func);
+void               ges_track_set_create_element_for_gap_func (GESTrack *track,
+                                                              GESCreateElementForGapFunc func);
 GES_API
-void               ges_track_set_mixing                      (GESTrack *track, gboolean mixing);
+void               ges_track_set_mixing                      (GESTrack *track,
+                                                              gboolean mixing);
 GES_API
 gboolean           ges_track_get_mixing                      (GESTrack *track);
 GES_API
-void               ges_track_set_restriction_caps            (GESTrack *track, const GstCaps *caps);
+void               ges_track_set_restriction_caps            (GESTrack *track,
+                                                              const GstCaps *caps);
 GES_API
-void               ges_track_update_restriction_caps         (GESTrack *track, const GstCaps *caps);
-
-/* standard methods */
+void               ges_track_update_restriction_caps         (GESTrack *track,
+                                                              const GstCaps *caps);
 GES_API
-GType              ges_track_get_type                        (void);
+GstCaps *          ges_track_get_restriction_caps            (GESTrack * track);
+
 GES_API
-GESTrack*          ges_track_new                             (GESTrackType type, GstCaps * caps);
+GESTrack*          ges_track_new                             (GESTrackType type,
+                                                              GstCaps * caps);
 
 G_END_DECLS
-
-#endif /* _GES_TRACK */
index c405cc3..0ffc60f 100644 (file)
@@ -108,12 +108,14 @@ ges_transition_clip_update_vtype_internal (GESClip *
 }
 
 /* GESExtractable interface overrides */
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
 static GParameter *
 extractable_get_parameters_from_id (const gchar * id, guint * n_params)
 {
   GEnumClass *enum_class =
       g_type_class_peek (GES_VIDEO_STANDARD_TRANSITION_TYPE_TYPE);
   GParameter *params = g_new0 (GParameter, 1);
+
   GEnumValue *value = g_enum_get_value_by_nick (enum_class, id);
 
   params[0].name = "vtype";
@@ -124,6 +126,7 @@ extractable_get_parameters_from_id (const gchar * id, guint * n_params)
   return params;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
 static gchar *
 extractable_check_id (GType type, const gchar * id)
 {
@@ -162,6 +165,8 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
   GESVideoStandardTransitionType value;
   GESTransitionClip *trans = GES_TRANSITION_CLIP (self);
   const gchar *vtype = ges_asset_get_id (asset);
+  GESAsset *prev_asset = ges_extractable_get_asset (self);
+  GList *tmp;
 
   if (!(ges_clip_get_supported_formats (GES_CLIP (self)) &
           GES_TRACK_TYPE_VIDEO)) {
@@ -185,6 +190,14 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
     ges_transition_clip_update_vtype_internal (GES_CLIP (self), value, FALSE);
   }
 
+  if (!prev_asset)
+    return TRUE;
+
+  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
+    if (ges_track_element_get_creator_asset (tmp->data) == prev_asset)
+      ges_track_element_set_creator_asset (tmp->data, asset);
+  }
+
   return TRUE;
 }
 
@@ -300,6 +313,9 @@ _child_removed (GESContainer * container, GESTimelineElement * element)
     priv->video_transitions = g_slist_remove (priv->video_transitions, element);
     gst_object_unref (element);
   }
+  /* call parent method */
+  GES_CONTAINER_CLASS (ges_transition_clip_parent_class)->child_removed
+      (container, element);
 }
 
 static void
@@ -319,6 +335,9 @@ _child_added (GESContainer * container, GESTimelineElement * element)
     ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container),
         g_object_class_find_property (eklass, "border"), G_OBJECT (element));
   }
+  /* call parent method */
+  GES_CONTAINER_CLASS (ges_transition_clip_parent_class)->child_added
+      (container, element);
 }
 
 static GESTrackElement *
index 1887004..86c31b9 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TRANSITION_CLIP
-#define _GES_TRANSITION_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TRANSITION_CLIP ges_transition_clip_get_type()
-
-#define GES_TRANSITION_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TRANSITION_CLIP, GESTransitionClip))
-
-#define GES_TRANSITION_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TRANSITION_CLIP, GESTransitionClipClass))
-
-#define GES_IS_TRANSITION_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TRANSITION_CLIP))
-
-#define GES_IS_TRANSITION_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TRANSITION_CLIP))
-
-#define GES_TRANSITION_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TRANSITION_CLIP, GESTransitionClipClass))
-
-typedef struct _GESTransitionClipPrivate GESTransitionClipPrivate;
+GES_DECLARE_TYPE(TransitionClip, transition_clip, TRANSITION_CLIP);
 
 /**
  * GESTransitionClip:
  * @vtype: a #GESVideoStandardTransitionType indicating the type of video transition
  * to apply.
+ *
+ * ### Children Properties
+ *
+ *  {{ libs/GESTransitionClip-children-props.md }}
  */
 struct _GESTransitionClip {
   /*< private >*/
@@ -79,13 +66,8 @@ struct _GESTransitionClipClass {
 };
 
 GES_API
-GType ges_transition_clip_get_type (void);
-
-GES_API
 GESTransitionClip *ges_transition_clip_new (GESVideoStandardTransitionType vtype);
 GES_API
 GESTransitionClip *ges_transition_clip_new_for_nick (char *nick);
 
 G_END_DECLS
-
-#endif /* _GES_TRANSITION_CLIP */
index 65c07e6..f1de5a9 100644 (file)
 #include <ges/ges.h>
 #include "ges-internal.h"
 
-struct _GESTransitionPrivate
-{
-  /*  Dummy variable */
-  void *nothing;
-};
-
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESTransition, ges_transition,
-    GES_TYPE_OPERATION);
-
+G_DEFINE_ABSTRACT_TYPE (GESTransition, ges_transition, GES_TYPE_OPERATION);
 
 static void
 ges_transition_class_init (GESTransitionClass * klass)
@@ -49,5 +41,4 @@ ges_transition_class_init (GESTransitionClass * klass)
 static void
 ges_transition_init (GESTransition * self)
 {
-  self->priv = ges_transition_get_instance_private (self);
 }
index 406140a..e08b030 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_TRANSITION
-#define _GES_TRANSITION
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_TRANSITION ges_transition_get_type()
-
-#define GES_TRANSITION(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_TRANSITION,\
-        GESTransition))
-
-#define GES_TRANSITION_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_TRANSITION,\
-        GESTransitionClass))
-
-#define GES_IS_TRANSITION(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_TRANSITION))
-
-#define GES_IS_TRANSITION_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_TRANSITION))
-
-#define GES_TRANSITION_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_TRANSITION,\
-        GESTransitionClass))
-
-typedef struct _GESTransitionPrivate GESTransitionPrivate;
+GES_DECLARE_TYPE(Transition, transition, TRANSITION);
 
 /**
  * GESTransition:
@@ -75,15 +55,10 @@ struct _GESTransition
 struct _GESTransitionClass {
   /*< private >*/
   GESOperationClass parent_class;
-  
+
   /*< private >*/
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_transition_get_type (void);
-
 G_END_DECLS
-
-#endif
index 4a66d1f..7b26af6 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GES_TYPES_H__
-#define __GES_TYPES_H__
+/**
+ * SECTION:gestypes
+ * @title: GES Types
+ * @short_description: GStreamer Editing Services data types
+ *
+ * GStreamer Editing Services data types
+ */
+
+#pragma once
 
 #include <glib.h>
 #include <ges/ges-prelude.h>
 
 G_BEGIN_DECLS
 
-/* Padding */
+/**
+ * GES_PADDING: (attributes doc.skip=true)
+ */
 #define GES_PADDING         4
 
-/* padding for very extensible base classes */
+/**
+ * GES_PADDING_LARGE: (attributes doc.skip=true)
+ */
 #define GES_PADDING_LARGE   20
 
+/**
+ * GESFrameNumber:
+ *
+ * A datatype to hold a frame number.
+ *
+ * Since: 1.18
+ */
+typedef gint64 GESFrameNumber;
+
+/**
+ * GES_FRAME_NUMBER_NONE: (value 9223372036854775807) (type GESFrameNumber)
+ *
+ * Constant to define an undefined frame number
+ */
+#define GES_FRAME_NUMBER_NONE             ((gint64) 9223372036854775807)
+
+/**
+ * GES_FRAME_NUMBER_IS_VALID:
+ * Tests if a given GESFrameNumber represents a valid frame
+ */
+#define GES_FRAME_NUMBER_IS_VALID(frames) (((GESFrameNumber) frames) != GES_FRAME_NUMBER_NONE)
+
+/**
+ * GES_TYPE_FRAME_NUMBER:
+ *
+ * The #GType of a #GESFrameNumber.
+ */
+#define GES_TYPE_FRAME_NUMBER G_TYPE_UINT64
+
 /* Type definitions */
 
 typedef struct _GESTimeline GESTimeline;
@@ -184,6 +224,44 @@ typedef struct _GESVideoTrack GESVideoTrack;
 typedef struct _GESAudioTrackClass GESAudioTrackClass;
 typedef struct _GESAudioTrack GESAudioTrack;
 
-G_END_DECLS
+typedef struct _GESMarkerList GESMarkerList;
+typedef struct _GESMarker GESMarker;
 
-#endif /* __GES_TYPES_H__ */
+typedef struct _GESEffectAssetClass GESEffectAssetClass;
+typedef struct _GESEffectAsset GESEffectAsset;
+
+typedef struct _GESXmlFormatterClass GESXmlFormatterClass;
+typedef struct _GESXmlFormatter GESXmlFormatter;
+
+/**
+ * GES_DECLARE_TYPE: (attributes doc.skip=true)
+ */
+#define GES_DECLARE_TYPE(ObjName, obj_name, OBJ_NAME)    \
+  GES_API GType ges_##obj_name##_get_type(void);                               \
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS                                             \
+  typedef struct _GES##ObjName##Private GES##ObjName##Private;                 \
+                                                                               \
+  G_DEFINE_AUTOPTR_CLEANUP_FUNC(GES##ObjName, gst_object_unref)                \
+                                                                               \
+  static G_GNUC_UNUSED inline GES##ObjName *GES_##OBJ_NAME(gpointer ptr) {                   \
+    return G_TYPE_CHECK_INSTANCE_CAST(ptr, ges_##obj_name##_get_type(),        \
+                                      GES##ObjName);                           \
+  }                                                                            \
+  static G_GNUC_UNUSED inline GES##ObjName##Class *GES_##OBJ_NAME##_CLASS(gpointer ptr) {    \
+    return G_TYPE_CHECK_CLASS_CAST(ptr, ges_##obj_name##_get_type(),           \
+                                   GES##ObjName##Class);                       \
+  }                                                                            \
+  static G_GNUC_UNUSED inline gboolean GES_IS_##OBJ_NAME(gpointer ptr) {                     \
+    return G_TYPE_CHECK_INSTANCE_TYPE(ptr, ges_##obj_name##_get_type());       \
+  }                                                                            \
+  static G_GNUC_UNUSED inline gboolean GES_IS_##OBJ_NAME##_CLASS(gpointer ptr) {             \
+    return G_TYPE_CHECK_CLASS_TYPE(ptr, ges_##obj_name##_get_type());          \
+  }                                                                            \
+  static G_GNUC_UNUSED inline GES##ObjName##Class *GES_##OBJ_NAME##_GET_CLASS(               \
+      gpointer ptr) {                                                          \
+    return G_TYPE_INSTANCE_GET_CLASS(ptr, ges_##obj_name##_get_type(),         \
+                                     GES##ObjName##Class);                     \
+  }                                                                            \
+  G_GNUC_END_IGNORE_DEPRECATIONS
+
+G_END_DECLS
index 99308fe..f33d8e9 100644 (file)
@@ -19,7 +19,7 @@
  * Boston, MA 02111-1307, USA.
  */
 /**
- * SECTION: gesuriclipasset
+ * SECTION: gesuriasset
  * @title: GESUriClipAsset
  * @short_description: A GESAsset subclass specialized in GESUriClip extraction
  *
 
 static GHashTable *parent_newparent_table = NULL;
 
-static GstDiscoverer *discoverer = NULL;
+G_LOCK_DEFINE_STATIC (discoverers_lock);
+static GstClockTime discovering_timeout = DEFAULT_DISCOVERY_TIMEOUT;
+static GHashTable *discoverers = NULL;  /* Thread ID -> GstDiscoverer */
+static void discoverer_discovered_cb (GstDiscoverer * discoverer,
+    GstDiscovererInfo * info, GError * err, gpointer user_data);
+
+/* WITH discoverers_lock */
+static GstDiscoverer *
+create_discoverer (void)
+{
+  GstDiscoverer *disco = gst_discoverer_new (discovering_timeout, NULL);
+
+  g_signal_connect (disco, "discovered", G_CALLBACK (discoverer_discovered_cb),
+      NULL);
+  GST_INFO_OBJECT (disco, "Creating new discoverer");
+  g_hash_table_insert (discoverers, g_thread_self (), disco);
+  gst_discoverer_start (disco);
+
+  return disco;
+}
+
+static GstDiscoverer *
+get_discoverer (void)
+{
+  GstDiscoverer *disco;
+
+  G_LOCK (discoverers_lock);
+  g_assert (discoverers);
+  disco = g_hash_table_lookup (discoverers, g_thread_self ());
+  if (!disco) {
+    disco = create_discoverer ();
+  }
+  disco = gst_object_ref (disco);
+  G_UNLOCK (discoverers_lock);
+
+  return disco;
+}
 
 static void
 initable_iface_init (GInitableIface * initable_iface)
@@ -58,18 +94,18 @@ enum
 {
   PROP_0,
   PROP_DURATION,
+  PROP_IS_NESTED_TIMELINE,
   PROP_LAST
 };
 static GParamSpec *properties[PROP_LAST];
 
-static void discoverer_discovered_cb (GstDiscoverer * discoverer,
-    GstDiscovererInfo * info, GError * err, gpointer user_data);
-
 struct _GESUriClipAssetPrivate
 {
   GstDiscovererInfo *info;
   GstClockTime duration;
+  GstClockTime max_duration;
   gboolean is_image;
+  gboolean is_nested_timeline;
 
   GList *asset_trackfilesources;
 };
@@ -84,13 +120,13 @@ typedef struct
 struct _GESUriSourceAssetPrivate
 {
   GstDiscovererStreamInfo *sinfo;
-  GESUriClipAsset *parent_asset;
+  GESUriClipAsset *creator_asset;
 
   const gchar *uri;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GESUriClipAsset, ges_uri_clip_asset,
-    GES_TYPE_CLIP_ASSET, G_ADD_PRIVATE (GESUriClipAsset)
+    GES_TYPE_SOURCE_CLIP_ASSET, G_ADD_PRIVATE (GESUriClipAsset)
     G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
 
 static void
@@ -103,6 +139,9 @@ ges_uri_clip_asset_get_property (GObject * object, guint property_id,
     case PROP_DURATION:
       g_value_set_uint64 (value, priv->duration);
       break;
+    case PROP_IS_NESTED_TIMELINE:
+      g_value_set_boolean (value, priv->is_nested_timeline);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
   }
@@ -128,13 +167,14 @@ _start_loading (GESAsset * asset, GError ** error)
 {
   gboolean ret;
   const gchar *uri;
-  GESUriClipAssetClass *class = GES_URI_CLIP_ASSET_GET_CLASS (asset);
-
-  GST_DEBUG ("Started loading %p", asset);
+  GstDiscoverer *discoverer = get_discoverer ();
 
   uri = ges_asset_get_id (asset);
+  GST_DEBUG_OBJECT (discoverer, "Started loading %s", uri);
+
+  ret = gst_discoverer_discover_uri_async (discoverer, uri);
+  gst_object_unref (discoverer);
 
-  ret = gst_discoverer_discover_uri_async (class->discoverer, uri);
   if (ret)
     return GES_ASSET_LOADING_ASYNC;
 
@@ -180,6 +220,52 @@ _request_id_update (GESAsset * self, gchar ** proposed_new_id, GError * error)
   return FALSE;
 }
 
+static gboolean
+ges_uri_source_asset_get_natural_framerate (GESTrackElementAsset * asset,
+    gint * framerate_n, gint * framerate_d)
+{
+  GESUriSourceAssetPrivate *priv = GES_URI_SOURCE_ASSET (asset)->priv;
+
+  if (!GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo))
+    return FALSE;
+
+  *framerate_d =
+      gst_discoverer_video_info_get_framerate_denom (GST_DISCOVERER_VIDEO_INFO
+      (priv->sinfo));
+  *framerate_n =
+      gst_discoverer_video_info_get_framerate_num (GST_DISCOVERER_VIDEO_INFO
+      (priv->sinfo));
+
+  if ((*framerate_n == 0 && *framerate_d == 1) || *framerate_d == 0
+      || *framerate_d == G_MAXINT) {
+    GST_INFO_OBJECT (asset, "No framerate information about the file.");
+
+    *framerate_n = 0;
+    *framerate_d = -1;
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+_get_natural_framerate (GESClipAsset * self, gint * framerate_n,
+    gint * framerate_d)
+{
+  GList *tmp;
+
+  for (tmp = (GList *)
+      ges_uri_clip_asset_get_stream_assets (GES_URI_CLIP_ASSET (self)); tmp;
+      tmp = tmp->next) {
+
+    if (ges_track_element_asset_get_natural_framerate (tmp->data, framerate_n,
+            framerate_d))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
 static void
 _asset_proxied (GESAsset * self, const gchar * new_uri)
 {
@@ -224,6 +310,8 @@ ges_uri_clip_asset_class_init (GESUriClipAssetClass * klass)
   GES_ASSET_CLASS (klass)->request_id_update = _request_id_update;
   GES_ASSET_CLASS (klass)->inform_proxy = _asset_proxied;
 
+  GES_CLIP_ASSET_CLASS (klass)->get_natural_framerate = _get_natural_framerate;
+
   klass->discovered = discoverer_discovered_cb;
 
 
@@ -238,6 +326,19 @@ ges_uri_clip_asset_class_init (GESUriClipAssetClass * klass)
   g_object_class_install_property (object_class, PROP_DURATION,
       properties[PROP_DURATION]);
 
+  /**
+   * GESUriClipAsset:is-nested-timeline:
+   *
+   * The duration (in nanoseconds) of the media file
+   *
+   * Since: 1.18
+   */
+  properties[PROP_IS_NESTED_TIMELINE] =
+      g_param_spec_boolean ("is-nested-timeline", "Is nested timeline",
+      "Whether this is a nested timeline", FALSE, G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_IS_NESTED_TIMELINE,
+      properties[PROP_IS_NESTED_TIMELINE]);
+
   _ges_uri_asset_ensure_setup (klass);
 }
 
@@ -249,7 +350,7 @@ ges_uri_clip_asset_init (GESUriClipAsset * self)
   priv = self->priv = ges_uri_clip_asset_get_instance_private (self);
 
   priv->info = NULL;
-  priv->duration = GST_CLOCK_TIME_NONE;
+  priv->max_duration = priv->duration = GST_CLOCK_TIME_NONE;
   priv->is_image = FALSE;
 }
 
@@ -257,8 +358,8 @@ static void
 _create_uri_source_asset (GESUriClipAsset * asset,
     GstDiscovererStreamInfo * sinfo, GESTrackType type)
 {
-  GESAsset *tck_filesource_asset;
-  GESUriSourceAssetPrivate *priv_tckasset;
+  GESAsset *src_asset;
+  GESUriSourceAssetPrivate *src_priv;
   GESUriClipAssetPrivate *priv = asset->priv;
   gchar *stream_id =
       g_strdup (gst_discoverer_stream_info_get_stream_id (sinfo));
@@ -270,22 +371,22 @@ _create_uri_source_asset (GESUriClipAsset * asset,
   }
 
   if (type == GES_TRACK_TYPE_VIDEO)
-    tck_filesource_asset = ges_asset_request (GES_TYPE_VIDEO_URI_SOURCE,
-        stream_id, NULL);
+    src_asset = ges_asset_request (GES_TYPE_VIDEO_URI_SOURCE, stream_id, NULL);
   else
-    tck_filesource_asset = ges_asset_request (GES_TYPE_AUDIO_URI_SOURCE,
-        stream_id, NULL);
+    src_asset = ges_asset_request (GES_TYPE_AUDIO_URI_SOURCE, stream_id, NULL);
   g_free (stream_id);
 
-  priv_tckasset = GES_URI_SOURCE_ASSET (tck_filesource_asset)->priv;
-  priv_tckasset->uri = ges_asset_get_id (GES_ASSET (asset));
-  priv_tckasset->sinfo = gst_object_ref (sinfo);
-  priv_tckasset->parent_asset = asset;
+  src_priv = GES_URI_SOURCE_ASSET (src_asset)->priv;
+  src_priv->uri = ges_asset_get_id (GES_ASSET (asset));
+  src_priv->sinfo = gst_object_ref (sinfo);
+  src_priv->creator_asset = asset;
   ges_track_element_asset_set_track_type (GES_TRACK_ELEMENT_ASSET
-      (tck_filesource_asset), type);
+      (src_asset), type);
 
-  priv->asset_trackfilesources = g_list_append (priv->asset_trackfilesources,
-      tck_filesource_asset);
+  priv->is_image |=
+      ges_uri_source_asset_is_image (GES_URI_SOURCE_ASSET (src_asset));
+  priv->asset_trackfilesources =
+      g_list_append (priv->asset_trackfilesources, src_asset);
 }
 
 static void
@@ -295,6 +396,7 @@ ges_uri_clip_asset_set_info (GESUriClipAsset * self, GstDiscovererInfo * info)
 
   GESTrackType supportedformats = GES_TRACK_TYPE_UNKNOWN;
   GESUriClipAssetPrivate *priv = GES_URI_CLIP_ASSET (self)->priv;
+  const GstTagList *tlist = gst_discoverer_info_get_tags (info);
 
   /* Extract infos from the GstDiscovererInfo */
   stream_list = gst_discoverer_info_get_stream_list (info);
@@ -314,9 +416,6 @@ ges_uri_clip_asset_set_info (GESUriClipAsset * self, GstDiscovererInfo * info)
         supportedformats = GES_TRACK_TYPE_VIDEO;
       else
         supportedformats |= GES_TRACK_TYPE_VIDEO;
-      if (gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *)
-              sinf))
-        priv->is_image = TRUE;
       type = GES_TRACK_TYPE_VIDEO;
     }
 
@@ -330,8 +429,16 @@ ges_uri_clip_asset_set_info (GESUriClipAsset * self, GstDiscovererInfo * info)
   if (stream_list)
     gst_discoverer_stream_info_list_free (stream_list);
 
-  if (priv->is_image == FALSE)
-    priv->duration = gst_discoverer_info_get_duration (info);
+  if (tlist)
+    gst_tag_list_get_boolean (tlist, "is-ges-timeline",
+        &priv->is_nested_timeline);
+
+  if (priv->is_image == FALSE) {
+    priv->max_duration = priv->duration =
+        gst_discoverer_info_get_duration (info);
+    if (priv->is_nested_timeline)
+      priv->max_duration = GST_CLOCK_TIME_NONE;
+  }
   /* else we keep #GST_CLOCK_TIME_NONE */
 
   priv->info = gst_object_ref (info);
@@ -421,6 +528,7 @@ asset_ready_cb (GESAsset * source, GAsyncResult * res, RequestSyncData * data)
     gchar *possible_uri = ges_uri_asset_try_update_id (data->error, source);
 
     if (possible_uri) {
+      ges_asset_try_proxy (source, possible_uri);
       g_clear_error (&data->error);
       ges_asset_request_async (GES_TYPE_URI_CLIP, possible_uri, NULL,
           (GAsyncReadyCallback) asset_ready_cb, data);
@@ -444,7 +552,7 @@ asset_ready_cb (GESAsset * source, GAsyncResult * res, RequestSyncData * data)
 GstDiscovererInfo *
 ges_uri_clip_asset_get_info (const GESUriClipAsset * self)
 {
-  g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), NULL);
+  g_return_val_if_fail (GES_IS_URI_CLIP_ASSET ((GESUriClipAsset *) self), NULL);
 
   return self->priv->info;
 }
@@ -465,14 +573,37 @@ ges_uri_clip_asset_get_duration (GESUriClipAsset * self)
   return self->priv->duration;
 }
 
+
+/**
+ * ges_uri_clip_asset_get_max_duration:
+ * @self: a #GESUriClipAsset
+ *
+ * Gets maximum duration of the file represented by @self,
+ * it is usually the same as GESUriClipAsset::duration,
+ * but in the case of nested timelines, for example, they
+ * are different as those can be extended 'infinitely'.
+ *
+ * Returns: The maximum duration of @self
+ *
+ * Since: 1.18
+ */
+GstClockTime
+ges_uri_clip_asset_get_max_duration (GESUriClipAsset * self)
+{
+  g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (self), GST_CLOCK_TIME_NONE);
+
+  return self->priv->max_duration;
+}
+
 /**
  * ges_uri_clip_asset_is_image:
- * @self: a #indent: Standard input:311: Error:Unexpected end of file
-GESUriClipAsset
+ * @self: a #GESUriClipAsset
  *
  * Gets Whether the file represented by @self is an image or not
  *
  * Returns: Whether the file represented by @self is an image or not
+ *
+ * Since: 1.18
  */
 gboolean
 ges_uri_clip_asset_is_image (GESUriClipAsset * self)
@@ -502,12 +633,12 @@ ges_uri_clip_asset_is_image (GESUriClipAsset * self)
  *
  *   filesource_asset = ges_uri_clip_asset_finish (res, &error);
  *   if (filesource_asset) {
- *    g_print ("The file: %s is usable as a FileSource, it is%s an image and lasts %" GST_TIME_FORMAT,
+ *    gst_print ("The file: %s is usable as a FileSource, it is%s an image and lasts %" GST_TIME_FORMAT,
  *        ges_asset_get_id (GES_ASSET (filesource_asset))
  *        ges_uri_clip_asset_is_image (filesource_asset) ? "" : " not",
  *        GST_TIME_ARGS (ges_uri_clip_asset_get_duration (filesource_asset));
  *   } else {
- *    g_print ("The file: %s is *not* usable as a FileSource because: %s",
+ *    gst_print ("The file: %s is *not* usable as a FileSource because: %s",
  *        ges_asset_get_id (source), error->message);
  *   }
  *
@@ -570,9 +701,7 @@ ges_uri_clip_asset_request_sync (const gchar * uri, GError ** error)
   GError *lerror = NULL;
   GESUriClipAsset *asset;
   RequestSyncData data = { 0, };
-  GESUriClipAssetClass *klass = g_type_class_peek (GES_TYPE_URI_CLIP_ASSET);
-  GstClockTime timeout;
-  GstDiscoverer *previous_discoverer = klass->discoverer;
+  GstDiscoverer *previous_discoverer;
 
   asset = GES_URI_CLIP_ASSET (ges_asset_request (GES_TYPE_URI_CLIP, uri,
           &lerror));
@@ -581,24 +710,17 @@ ges_uri_clip_asset_request_sync (const gchar * uri, GError ** error)
     return asset;
 
   data.ml = g_main_loop_new (NULL, TRUE);
-  g_object_get (previous_discoverer, "timeout", &timeout, NULL);
-  klass->discoverer = gst_discoverer_new (timeout, error);
-  if (!klass->discoverer) {
-    klass->discoverer = previous_discoverer;
+  previous_discoverer = get_discoverer ();
+  create_discoverer ();
 
-    return NULL;
-  }
-
-  g_signal_connect (klass->discoverer, "discovered",
-      G_CALLBACK (klass->discovered), NULL);
-  gst_discoverer_start (klass->discoverer);
   ges_asset_request_async (GES_TYPE_URI_CLIP, uri, NULL,
       (GAsyncReadyCallback) asset_ready_cb, &data);
   g_main_loop_run (data.ml);
   g_main_loop_unref (data.ml);
 
-  gst_object_unref (klass->discoverer);
-  klass->discoverer = previous_discoverer;
+  G_LOCK (discoverers_lock);
+  g_hash_table_insert (discoverers, g_thread_self (), previous_discoverer);
+  G_UNLOCK (discoverers_lock);
 
   if (data.error) {
     GST_ERROR ("Got an error requesting asset: %s", data.error->message);
@@ -622,9 +744,18 @@ void
 ges_uri_clip_asset_class_set_timeout (GESUriClipAssetClass * klass,
     GstClockTime timeout)
 {
+  GHashTableIter iter;
+  gpointer value;
+
   g_return_if_fail (GES_IS_URI_CLIP_ASSET_CLASS (klass));
 
-  g_object_set (klass->discoverer, "timeout", timeout, NULL);
+  discovering_timeout = timeout;
+
+  G_LOCK (discoverers_lock);
+  g_hash_table_iter_init (&iter, discoverers);
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    g_object_set (value, "timeout", timeout, NULL);
+  G_UNLOCK (discoverers_lock);
 }
 
 /**
@@ -647,15 +778,6 @@ ges_uri_clip_asset_get_stream_assets (GESUriClipAsset * self)
 /*****************************************************************
  *            GESUriSourceAsset implementation             *
  *****************************************************************/
-/**
- * SECTION: gesurisourceasset
- * @title: GESUriClipAsset
- * @short_description: A GESAsset subclass specialized in GESUriSource extraction
- *
- * NOTE: You should never request such a #GESAsset as they will be created automatically
- * by #GESUriClipAsset-s.
- */
-
 G_DEFINE_TYPE_WITH_PRIVATE (GESUriSourceAsset, ges_uri_source_asset,
     GES_TYPE_TRACK_ELEMENT_ASSET);
 
@@ -680,12 +802,8 @@ _extract (GESAsset * asset, GError ** error)
 
   uri = g_strdup (priv->uri);
 
-  if (g_str_has_prefix (priv->uri, GES_MULTI_FILE_URI_PREFIX)) {
+  if (g_str_has_prefix (priv->uri, GES_MULTI_FILE_URI_PREFIX))
     trackelement = GES_TRACK_ELEMENT (ges_multi_file_source_new (uri));
-  } else if (GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo)
-      && gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *)
-          priv->sinfo))
-    trackelement = GES_TRACK_ELEMENT (ges_image_source_new (uri));
   else if (GST_IS_DISCOVERER_VIDEO_INFO (priv->sinfo))
     trackelement = GES_TRACK_ELEMENT (ges_video_uri_source_new (uri));
   else
@@ -718,6 +836,8 @@ ges_uri_source_asset_class_init (GESUriSourceAssetClass * klass)
   object_class->dispose = ges_uri_source_asset_dispose;
 
   GES_ASSET_CLASS (klass)->extract = _extract;
+  GES_TRACK_ELEMENT_ASSET_CLASS (klass)->get_natural_framerate =
+      ges_uri_source_asset_get_natural_framerate;
 }
 
 static void
@@ -728,7 +848,7 @@ ges_uri_source_asset_init (GESUriSourceAsset * self)
   priv = self->priv = ges_uri_source_asset_get_instance_private (self);
 
   priv->sinfo = NULL;
-  priv->parent_asset = NULL;
+  priv->creator_asset = NULL;
   priv->uri = NULL;
 }
 
@@ -769,19 +889,48 @@ ges_uri_source_asset_get_filesource_asset (GESUriSourceAsset * asset)
 {
   g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), NULL);
 
-  return asset->priv->parent_asset;
+  return asset->priv->creator_asset;
+}
+
+/**
+ * ges_uri_source_asset_is_image:
+ * @asset: A #GESUriClipAsset
+ *
+ * Check if @asset contains a single image
+ *
+ * Returns: %TRUE if the video stream corresponds to an image (i.e. only
+ * contains one frame)
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_uri_source_asset_is_image (GESUriSourceAsset * asset)
+{
+  g_return_val_if_fail (GES_IS_URI_SOURCE_ASSET (asset), FALSE);
+
+  if (!GST_IS_DISCOVERER_VIDEO_INFO (asset->priv->sinfo))
+    return FALSE;
+
+  return gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *)
+      asset->priv->sinfo);
 }
 
 void
 _ges_uri_asset_cleanup (void)
 {
-  if (discoverer)
-    gst_discoverer_stop (discoverer);
-  g_clear_object (&discoverer);
   if (parent_newparent_table) {
     g_hash_table_destroy (parent_newparent_table);
     parent_newparent_table = NULL;
   }
+
+  G_LOCK (discoverers_lock);
+  if (discoverers) {
+    g_hash_table_destroy (discoverers);
+    discoverers = NULL;
+  }
+  gst_clear_object (&GES_URI_CLIP_ASSET_CLASS (g_type_class_peek
+          (GES_TYPE_URI_CLIP_ASSET))->discoverer);
+  G_UNLOCK (discoverers_lock);
 }
 
 gboolean
@@ -791,6 +940,7 @@ _ges_uri_asset_ensure_setup (gpointer uriasset_class)
   GError *err;
   GstClockTime timeout;
   const gchar *timeout_str;
+  GstDiscoverer *discoverer = NULL;
 
   g_return_val_if_fail (GES_IS_URI_CLIP_ASSET_CLASS (uriasset_class), FALSE);
 
@@ -807,7 +957,7 @@ _ges_uri_asset_ensure_setup (gpointer uriasset_class)
   if (errno)
     timeout = DEFAULT_DISCOVERY_TIMEOUT;
 
-  if (!discoverer) {
+  if (!klass->discoverer) {
     discoverer = gst_discoverer_new (timeout, &err);
     if (!discoverer) {
       GST_ERROR ("Could not create discoverer: %s", err->message);
@@ -827,10 +977,17 @@ _ges_uri_asset_ensure_setup (gpointer uriasset_class)
 
     g_signal_connect (klass->discoverer, "discovered",
         G_CALLBACK (klass->discovered), NULL);
+    gst_discoverer_start (klass->discoverer);
+  }
+
+  G_LOCK (discoverers_lock);
+  if (discoverers == NULL) {
+    discoverers = g_hash_table_new_full (g_direct_hash,
+        (GEqualFunc) g_direct_equal, NULL, g_object_unref);
   }
+  G_UNLOCK (discoverers_lock);
 
   /* We just start the discoverer and let it live */
-  gst_discoverer_start (klass->discoverer);
   if (parent_newparent_table == NULL) {
     parent_newparent_table = g_hash_table_new_full (g_file_hash,
         (GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
index 5bb5587..53fa544 100644 (file)
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef _GES_URI_CLIP_ASSET_
-#define _GES_URI_CLIP_ASSET_
+
+#pragma once
 
 #include <glib-object.h>
 #include <gio/gio.h>
 #include <ges/ges-types.h>
 #include <ges/ges-asset.h>
-#include <ges/ges-clip-asset.h>
+#include <ges/ges-source-clip-asset.h>
 #include <ges/ges-track-element-asset.h>
 
 G_BEGIN_DECLS
 #define GES_TYPE_URI_CLIP_ASSET ges_uri_clip_asset_get_type()
-#define GES_URI_CLIP_ASSET(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_URI_CLIP_ASSET, GESUriClipAsset))
-#define GES_URI_CLIP_ASSET_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_URI_CLIP_ASSET, GESUriClipAssetClass))
-#define GES_IS_URI_CLIP_ASSET(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_URI_CLIP_ASSET))
-#define GES_IS_URI_CLIP_ASSET_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_URI_CLIP_ASSET))
-#define GES_URI_CLIP_ASSET_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_URI_CLIP_ASSET, GESUriClipAssetClass))
-
-typedef struct _GESUriClipAssetPrivate GESUriClipAssetPrivate;
-
-GES_API
-GType ges_uri_clip_asset_get_type (void);
+GES_DECLARE_TYPE(UriClipAsset, uri_clip_asset, URI_CLIP_ASSET);
 
 struct _GESUriClipAsset
 {
-  GESClipAsset parent;
+  GESSourceClipAsset parent;
 
   /* <private> */
   GESUriClipAssetPrivate *priv;
@@ -59,14 +45,16 @@ struct _GESUriClipAsset
 
 struct _GESUriClipAssetClass
 {
-  GESClipAssetClass parent_class;
+  GESSourceClipAssetClass parent_class;
 
   /* <private> */
-  GstDiscoverer *discoverer;
-  GstDiscoverer *sync_discoverer;
+  GstDiscoverer *discoverer; /* Unused */
+  GstDiscoverer *sync_discoverer; /* Unused */
 
-  void (*discovered) (GstDiscoverer * discoverer, GstDiscovererInfo * info,
-                     GError * err, gpointer user_data);
+  void (*discovered) (GstDiscoverer * discoverer, /* Unused */
+                      GstDiscovererInfo * info,
+                      GError * err,
+                      gpointer user_data);
 
   gpointer _ges_reserved[GES_PADDING -1];
 };
@@ -76,6 +64,8 @@ GstDiscovererInfo *ges_uri_clip_asset_get_info      (const GESUriClipAsset * sel
 GES_API
 GstClockTime ges_uri_clip_asset_get_duration        (GESUriClipAsset *self);
 GES_API
+GstClockTime ges_uri_clip_asset_get_max_duration    (GESUriClipAsset *self);
+GES_API
 gboolean ges_uri_clip_asset_is_image                (GESUriClipAsset *self);
 GES_API
 void ges_uri_clip_asset_new                         (const gchar *uri,
@@ -93,22 +83,16 @@ GES_API
 const GList * ges_uri_clip_asset_get_stream_assets  (GESUriClipAsset *self);
 
 #define GES_TYPE_URI_SOURCE_ASSET ges_uri_source_asset_get_type()
-#define GES_URI_SOURCE_ASSET(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_URI_SOURCE_ASSET, GESUriSourceAsset))
-#define GES_URI_SOURCE_ASSET_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_URI_SOURCE_ASSET, GESUriSourceAssetClass))
-#define GES_IS_URI_SOURCE_ASSET(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_URI_SOURCE_ASSET))
-#define GES_IS_URI_SOURCE_ASSET_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_URI_SOURCE_ASSET))
-#define GES_URI_SOURCE_ASSET_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_URI_SOURCE_ASSET, GESUriSourceAssetClass))
-
-typedef struct _GESUriSourceAssetPrivate GESUriSourceAssetPrivate;
-
-GES_API
-GType ges_uri_source_asset_get_type (void);
+GES_DECLARE_TYPE(UriSourceAsset, uri_source_asset, URI_SOURCE_ASSET);
 
+/**
+ * GESUriSourceAsset:
+ *
+ * Asset to create a stream specific #GESSource for a media file.
+ *
+ * NOTE: You should never request such a #GESAsset as they will be created automatically
+ * by #GESUriClipAsset-s.
+ */
 struct _GESUriSourceAsset
 {
   GESTrackElementAsset parent;
@@ -132,6 +116,7 @@ GES_API
 const gchar * ges_uri_source_asset_get_stream_uri                  (GESUriSourceAsset *asset);
 GES_API
 const GESUriClipAsset *ges_uri_source_asset_get_filesource_asset   (GESUriSourceAsset *asset);
+GES_API
+gboolean ges_uri_source_asset_is_image                             (GESUriSourceAsset *asset);
 
-G_END_DECLS
-#endif /* _GES_URI_CLIP_ASSET */
+G_END_DECLS
\ No newline at end of file
index 91ef527..fccd384 100644 (file)
@@ -47,8 +47,6 @@ static void ges_extractable_interface_init (GESExtractableInterface * iface);
 
 #define parent_class ges_uri_clip_parent_class
 
-GESExtractableInterface *parent_extractable_iface;
-
 struct _GESUriClipPrivate
 {
   gchar *uri;
@@ -73,8 +71,6 @@ G_DEFINE_TYPE_WITH_CODE (GESUriClip, ges_uri_clip,
 
 static GList *ges_uri_clip_create_track_elements (GESClip *
     clip, GESTrackType type);
-static GESTrackElement
-    * ges_uri_clip_create_track_element (GESClip * clip, GESTrackType type);
 static void ges_uri_clip_set_uri (GESUriClip * self, gchar * uri);
 
 gboolean
@@ -145,7 +141,7 @@ static void
 ges_uri_clip_class_init (GESUriClipClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESClipClass *timobj_class = GES_CLIP_CLASS (klass);
+  GESClipClass *clip_class = GES_CLIP_CLASS (klass);
   GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
 
   object_class->get_property = ges_uri_clip_get_property;
@@ -192,56 +188,20 @@ ges_uri_clip_class_init (GESUriClipClass * klass)
 
   element_class->set_max_duration = uri_clip_set_max_duration;
 
-  timobj_class->create_track_elements = ges_uri_clip_create_track_elements;
-  timobj_class->create_track_element = ges_uri_clip_create_track_element;
+  clip_class->create_track_elements = ges_uri_clip_create_track_elements;
 }
 
 static gchar *
 extractable_check_id (GType type, const gchar * id)
 {
-  const gchar *testing_directory;
-
-  testing_directory = g_getenv ("GES_TESTING_ASSETS_DIRECTORY");
-
-  /* Testing purposes, user can specify a directory to look up for script */
-  if (testing_directory != NULL) {
-    gchar **tokens;
-    gchar *location = NULL;
-    guint i;
-
-    GST_DEBUG ("Checking if the testing directory contains needed media");
-
-    tokens = g_strsplit (id, "media", 2);
-    for (i = 0; tokens[i]; i++)
-      if (i == 1)
-        location = tokens[1];
-
-    if (location == NULL)
-      GST_WARNING ("The provided id doesn't have a media subdirectory");
-    else {
-      gchar *actual_id =
-          g_strconcat ("file://", testing_directory, "/media/", location, NULL);
-
-      if (gst_uri_is_valid (actual_id)) {
-        GST_DEBUG ("Returning new id %s instead of id %s", actual_id, id);
-        g_strfreev (tokens);
-        return (actual_id);
-      } else
-        GST_WARNING ("The constructed id %s was not valid, trying %s anyway",
-            actual_id, id);
-
-      g_free (actual_id);
-    }
-
-    g_strfreev (tokens);
-  }
-
   if (gst_uri_is_valid (id))
     return g_strdup (id);
 
   return NULL;
 }
 
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
+
 static GParameter *
 extractable_get_parameters_from_id (const gchar * id, guint * n_params)
 {
@@ -256,50 +216,77 @@ extractable_get_parameters_from_id (const gchar * id, guint * n_params)
   return params;
 }
 
+G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */
+
 static gchar *
 extractable_get_id (GESExtractable * self)
 {
   return g_strdup (GES_URI_CLIP (self)->priv->uri);
 }
 
+static GList *
+get_auto_transitions_around_source (GESTrackElement * child)
+{
+  GList *transitions = NULL;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (child);
+  gint i;
+  GESEdge edges[] = { GES_EDGE_START, GES_EDGE_END };
+
+  if (!timeline)
+    return NULL;
+
+  for (i = 0; i < G_N_ELEMENTS (edges); i++) {
+    GESAutoTransition *transition =
+        ges_timeline_get_auto_transition_at_edge (timeline, child, edges[i]);
+    if (transition)
+      transitions = g_list_prepend (transitions, transition);
+  }
+
+  return transitions;
+}
+
 static gboolean
 extractable_set_asset (GESExtractable * self, GESAsset * asset)
 {
-  gboolean res = TRUE;
+  gboolean res = TRUE, contains_core;
   GESUriClip *uriclip = GES_URI_CLIP (self);
   GESUriClipAsset *uri_clip_asset;
   GESClip *clip = GES_CLIP (self);
+  GESContainer *container = GES_CONTAINER (clip);
+  GESTimelineElement *element = GES_TIMELINE_ELEMENT (self);
   GESLayer *layer = ges_clip_get_layer (clip);
-  GList *tmp;
-  GESTimelineElement *audio_source = NULL, *video_source = NULL;
+  GList *tmp, *children;
+  GHashTable *source_by_track, *auto_transitions_on_sources;
+  GstClockTime max_duration;
+  GESAsset *prev_asset;
+  GList *transitions = NULL;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
 
   g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (asset), FALSE);
 
   uri_clip_asset = GES_URI_CLIP_ASSET (asset);
-  if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_DURATION (self)) &&
-      ges_uri_clip_asset_get_duration (uri_clip_asset) <
-      GES_TIMELINE_ELEMENT_INPOINT (self) +
-      GES_TIMELINE_ELEMENT_DURATION (self)) {
-    GST_INFO_OBJECT (self,
-        "Can not set asset to %p as its duration is %" GST_TIME_FORMAT
-        " < to inpoint %" GST_TIME_FORMAT " + %" GST_TIME_FORMAT " = %"
-        GST_TIME_FORMAT, asset,
-        GST_TIME_ARGS (ges_uri_clip_asset_get_duration (uri_clip_asset)),
-        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self)),
-        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)),
-        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self) +
-            GES_TIMELINE_ELEMENT_DURATION (self)));
 
+  /* new sources elements will have their max-duration set to
+   * max_duration. Check that this is possible with the new uri
+   * NOTE: we are assuming that all the new core children will end up
+   * in the same tracks as the previous core children */
+  max_duration = ges_uri_clip_asset_get_max_duration (uri_clip_asset);
+  if (!ges_clip_can_set_max_duration_of_all_core (clip, max_duration, NULL)) {
+    GST_INFO_OBJECT (self, "Can not set asset to %p as its max-duration %"
+        GST_TIME_FORMAT " is too low", asset, GST_TIME_ARGS (max_duration));
 
     return FALSE;
   }
 
-  if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_DURATION (clip)) == FALSE)
-    _set_duration0 (GES_TIMELINE_ELEMENT (uriclip),
-        ges_uri_clip_asset_get_duration (uri_clip_asset));
+  if (!container->children && !GST_CLOCK_TIME_IS_VALID (element->duration)) {
+    if (!ges_timeline_element_set_duration (element,
+            ges_uri_clip_asset_get_duration (uri_clip_asset))) {
+      GST_ERROR_OBJECT (self, "Failed to set the duration using a new "
+          "uri asset when we have no children. This should not happen");
+      return FALSE;
+    }
+  }
 
-  ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (uriclip),
-      ges_uri_clip_asset_get_duration (uri_clip_asset));
   ges_uri_clip_set_is_image (uriclip,
       ges_uri_clip_asset_is_image (uri_clip_asset));
 
@@ -308,58 +295,146 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
         ges_clip_asset_get_supported_formats (GES_CLIP_ASSET (uri_clip_asset)));
   }
 
-  GES_TIMELINE_ELEMENT (uriclip)->asset = asset;
-
-  if (layer) {
-    GList *children = ges_container_get_children (GES_CONTAINER (self), TRUE);
+  prev_asset = element->asset;
+  element->asset = asset;
 
-    for (tmp = children; tmp; tmp = tmp->next) {
-      if (GES_IS_SOURCE (tmp->data)) {
-        GESTrack *track = ges_track_element_get_track (tmp->data);
-
-        if (track->type == GES_TRACK_TYPE_AUDIO)
-          audio_source = gst_object_ref (tmp->data);
-        else if (track->type == GES_TRACK_TYPE_VIDEO)
-          video_source = gst_object_ref (tmp->data);
-
-        ges_track_remove_element (track, tmp->data);
-        ges_container_remove (GES_CONTAINER (self), tmp->data);
-      }
-    }
-    g_list_free_full (children, g_object_unref);
-
-    gst_object_ref (clip);
+  /* FIXME: it would be much better if we could have a way to replace
+   * each source one-to-one with a new source in the same track, e.g.
+   * a user supplied
+   * GESSource * ges_uri_clip_swap_source (
+   *   GESClip * clip, GESSource * replace, GList * new_sources,
+   *   gpointer user_data)
+   *
+   * and they select a new source from new_sources to replace @replace, or
+   * %NULL to remove it without a replacement. The default would swap
+   * one video for another video, etc.
+   *
+   * Then we could use this information with
+   * ges_clip_can_update_duration_limit, using the new max-duration and
+   * replacing each source in the same track, to test that the operation
+   * can succeed (basically extending
+   * ges_clip_can_set_max_duration_of_all_core, but with the added
+   * information that sources without a replacement will not contribute
+   * to the duration-limit, and all of the siblings in the same track will
+   * also be removed from the track).
+   *
+   * Then we can perform the replacement, whilst avoiding track-selection
+   * (similar to GESClip's _transfer_child). */
+
+  source_by_track = g_hash_table_new_full (NULL, NULL,
+      gst_object_unref, gst_object_unref);
+  auto_transitions_on_sources = g_hash_table_new_full (NULL, NULL,
+      gst_object_unref, (GDestroyNotify) g_list_free);
+
+  if (timeline)
+    ges_timeline_freeze_auto_transitions (timeline, TRUE);
+
+  children = ges_container_get_children (container, FALSE);
+  for (tmp = children; tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    GESTrack *track;
+
+    /* remove our core children */
+    if (!ges_track_element_is_core (child))
+      continue;
+
+    track = ges_track_element_get_track (child);
+    if (track)
+      g_hash_table_insert (source_by_track, gst_object_ref (track),
+          gst_object_ref (child));
+
+    transitions = get_auto_transitions_around_source (child);
+    if (transitions)
+      g_hash_table_insert (auto_transitions_on_sources, gst_object_ref (child),
+          transitions);
+
+    /* removing the track element from its clip whilst it is in a
+     * timeline will remove it from its track */
+    /* removing the core element will also empty its non-core siblings
+     * from the same track */
+    ges_container_remove (container, GES_TIMELINE_ELEMENT (child));
+  }
+  g_list_free_full (children, g_object_unref);
 
-    ges_layer_remove_clip (layer, clip);
-    res = ges_layer_add_clip (layer, clip);
+  contains_core = FALSE;
 
-    for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
-      if (GES_IS_SOURCE (tmp->data)) {
-        GESTrack *track = ges_track_element_get_track (tmp->data);
+  /* keep alive */
+  gst_object_ref (self);
+  if (layer) {
 
-        if (track->type == GES_TRACK_TYPE_AUDIO && audio_source) {
-          ges_track_element_copy_properties (audio_source, tmp->data);
-          ges_track_element_copy_bindings (GES_TRACK_ELEMENT (audio_source),
-              tmp->data, GST_CLOCK_TIME_NONE);
-        } else if (track->type == GES_TRACK_TYPE_VIDEO && video_source) {
-          ges_track_element_copy_properties (video_source, tmp->data);
-          ges_track_element_copy_bindings (GES_TRACK_ELEMENT (video_source),
-              tmp->data, GST_CLOCK_TIME_NONE);
+    res = ges_layer_remove_clip (layer, clip);
+
+    if (res) {
+      /* adding back to the layer will trigger the re-creation of the core
+       * children */
+      res = ges_layer_add_clip (layer, clip);
+
+      if (!res)
+        GST_ERROR_OBJECT (self, "Failed to add the uri clip %s back into "
+            "its layer. This is likely caused by track-selection for the "
+            "core sources and effects failing because the core sources "
+            "were not replaced in the same tracks", element->name);
+
+      /* NOTE: assume that core children in the same tracks correspond to
+       * the same source! */
+      for (tmp = container->children; tmp; tmp = tmp->next) {
+        GESTrackElement *child = tmp->data;
+        GESTrackElement *orig_source;
+
+        if (!ges_track_element_is_core (child))
+          continue;
+
+        contains_core = TRUE;
+        orig_source = g_hash_table_lookup (source_by_track,
+            ges_track_element_get_track (child));
+
+        if (!orig_source)
+          continue;
+
+        ges_track_element_copy_properties (GES_TIMELINE_ELEMENT
+            (orig_source), GES_TIMELINE_ELEMENT (child));
+        ges_track_element_copy_bindings (orig_source, child,
+            GST_CLOCK_TIME_NONE);
+
+        transitions =
+            g_hash_table_lookup (auto_transitions_on_sources, orig_source);
+        for (; transitions; transitions = transitions->next) {
+          GESAutoTransition *transition = transitions->data;
+
+          if (transition->previous_source == orig_source)
+            ges_auto_transition_set_source (transition, child, GES_EDGE_START);
+          else if (transition->next_source == orig_source)
+            ges_auto_transition_set_source (transition, child, GES_EDGE_END);
         }
       }
+    } else {
+      GST_ERROR_OBJECT (self, "Failed to remove from the layer. This "
+          "should not happen");
     }
-
-    g_clear_object (&audio_source);
-    g_clear_object (&video_source);
-    gst_object_unref (clip);
     gst_object_unref (layer);
   }
+  g_hash_table_unref (source_by_track);
+  g_hash_table_unref (auto_transitions_on_sources);
+
+  if (timeline)
+    ges_timeline_freeze_auto_transitions (timeline, FALSE);
 
   if (res) {
     g_free (uriclip->priv->uri);
     uriclip->priv->uri = g_strdup (ges_asset_get_id (asset));
+
+    if (!contains_core) {
+      if (!ges_timeline_element_set_max_duration (element, max_duration))
+        GST_ERROR_OBJECT (self, "Failed to set the max-duration on the uri "
+            "clip when it has no children. This should not happen");
+    }
+  } else {
+    element->asset = prev_asset;
   }
 
+  /* if re-adding failed, clip may be destroyed */
+  gst_object_unref (self);
+
   return res;
 }
 
@@ -404,9 +479,9 @@ ges_uri_clip_set_mute (GESUriClip * self, gboolean mute)
   /* Go over tracked objects, and update 'active' status on all audio objects */
   for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = g_list_next (tmp)) {
     GESTrackElement *trackelement = (GESTrackElement *) tmp->data;
+    GESTrack *track = ges_track_element_get_track (trackelement);
 
-    if (ges_track_element_get_track (trackelement)->type ==
-        GES_TRACK_TYPE_AUDIO)
+    if (track && track->type == GES_TRACK_TYPE_AUDIO)
       ges_track_element_set_active (trackelement, !mute);
   }
 }
@@ -415,13 +490,17 @@ gboolean
 uri_clip_set_max_duration (GESTimelineElement * element,
     GstClockTime maxduration)
 {
-  if (_DURATION (element) == GST_CLOCK_TIME_NONE || _DURATION (element) == 0)
-    /* If we don't have a valid duration, use the max duration */
-    _set_duration0 (element, maxduration - _INPOINT (element));
-
-  return
+  gboolean ret =
       GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_max_duration (element,
       maxduration);
+
+  if (ret) {
+    GstClockTime limit = ges_clip_get_duration_limit (GES_CLIP (element));
+    if (GST_CLOCK_TIME_IS_VALID (limit) && (element->duration == 0))
+      _set_duration0 (element, limit);
+  }
+
+  return ret;
 }
 
 /**
@@ -484,57 +563,30 @@ ges_uri_clip_create_track_elements (GESClip * clip, GESTrackType type)
 {
   GList *res = NULL;
   const GList *tmp, *stream_assets;
+  GESAsset *asset = GES_TIMELINE_ELEMENT (clip)->asset;
+  GESUriClipAsset *uri_asset;
+  GstClockTime max_duration;
 
-  g_return_val_if_fail (GES_TIMELINE_ELEMENT (clip)->asset, NULL);
+  g_return_val_if_fail (asset, NULL);
 
-  stream_assets =
-      ges_uri_clip_asset_get_stream_assets (GES_URI_CLIP_ASSET
-      (GES_TIMELINE_ELEMENT (clip)->asset));
-  for (tmp = stream_assets; tmp; tmp = tmp->next) {
-    GESTrackElementAsset *asset = GES_TRACK_ELEMENT_ASSET (tmp->data);
+  uri_asset = GES_URI_CLIP_ASSET (asset);
 
-    if (ges_track_element_asset_get_track_type (asset) == type)
-      res = g_list_prepend (res, ges_asset_extract (GES_ASSET (asset), NULL));
-  }
+  max_duration = ges_uri_clip_asset_get_max_duration (uri_asset);
+  stream_assets = ges_uri_clip_asset_get_stream_assets (uri_asset);
 
-  return res;
-}
-
-static GESTrackElement *
-ges_uri_clip_create_track_element (GESClip * clip, GESTrackType type)
-{
-  GESUriClipPrivate *priv = GES_URI_CLIP (clip)->priv;
-  GESTrackElement *res = NULL;
-
-  if (g_str_has_prefix (priv->uri, GES_MULTI_FILE_URI_PREFIX)) {
-    GST_DEBUG ("Creating a GESMultiFileSource for %s", priv->uri);
-    res = (GESTrackElement *) ges_multi_file_source_new (priv->uri);
-  } else if (priv->is_image) {
-    if (type != GES_TRACK_TYPE_VIDEO) {
-      GST_DEBUG ("Object is still image, not adding any audio source");
-      return NULL;
-    } else {
-      GST_DEBUG ("Creating a GESImageSource");
-      res = (GESTrackElement *) ges_image_source_new (priv->uri);
+  for (tmp = stream_assets; tmp; tmp = tmp->next) {
+    GESTrackElementAsset *element_asset = GES_TRACK_ELEMENT_ASSET (tmp->data);
+
+    if (ges_track_element_asset_get_track_type (element_asset) == type) {
+      GESTrackElement *element =
+          GES_TRACK_ELEMENT (ges_asset_extract (GES_ASSET (element_asset),
+              NULL));
+      ges_timeline_element_set_max_duration (GES_TIMELINE_ELEMENT (element),
+          max_duration);
+      res = g_list_prepend (res, element);
     }
-
-  } else {
-    GST_DEBUG ("Creating a GESUriSource");
-
-    /* FIXME : Implement properly ! */
-    if (type == GES_TRACK_TYPE_VIDEO)
-      res = (GESTrackElement *) ges_video_uri_source_new (priv->uri);
-    else if (type == GES_TRACK_TYPE_AUDIO)
-      res = (GESTrackElement *) ges_audio_uri_source_new (priv->uri);
-
-    /* If mute and track is audio, deactivate the track element */
-    if (type == GES_TRACK_TYPE_AUDIO && priv->mute)
-      ges_track_element_set_active (res, FALSE);
   }
 
-  if (res)
-    ges_track_element_set_track_type (res, type);
-
   return res;
 }
 
@@ -544,17 +596,26 @@ ges_uri_clip_create_track_element (GESClip * clip, GESTrackType type)
  *
  * Creates a new #GESUriClip for the provided @uri.
  *
+ * > **WARNING**: This function might 'discover` @uri **synchrounously**, it is
+ * > an IO and processing intensive task that you probably don't want to run in
+ * > an application mainloop. Have a look at #ges_asset_request_async to see how
+ * > to make that operation happen **asynchronously**.
+ *
  * Returns: (transfer floating) (nullable): The newly created #GESUriClip, or
  * %NULL if there was an error.
  */
 GESUriClip *
 ges_uri_clip_new (const gchar * uri)
 {
-  GESAsset *asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, NULL));
+  GError *err = NULL;
   GESUriClip *res = NULL;
+  GESAsset *asset = GES_ASSET (ges_uri_clip_asset_request_sync (uri, &err));
 
   if (asset) {
-    res = GES_URI_CLIP (ges_asset_extract (asset, NULL));
+    res = GES_URI_CLIP (ges_asset_extract (asset, &err));
+    if (!res && err)
+      GST_ERROR ("Could not analyze %s: %s", uri, err->message);
+
     gst_object_unref (asset);
   } else
     GST_ERROR ("Could not create asset for uri: %s", uri);
index 66f0d0e..b4137cd 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_URI_CLIP
-#define _GES_URI_CLIP
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_URI_CLIP ges_uri_clip_get_type()
-
-#define GES_URI_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_URI_CLIP, GESUriClip))
-
-#define GES_URI_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_URI_CLIP, GESUriClipClass))
-
-#define GES_IS_URI_CLIP(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_URI_CLIP))
-
-#define GES_IS_URI_CLIP_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_URI_CLIP))
-
-#define GES_URI_CLIP_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_URI_CLIP, GESUriClipClass))
-
-typedef struct _GESUriClipPrivate GESUriClipPrivate;
+GES_DECLARE_TYPE(UriClip, uri_clip, URI_CLIP);
 
 struct _GESUriClip {
   GESSourceClip parent;
@@ -69,9 +52,6 @@ struct _GESUriClipClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_uri_clip_get_type (void);
-
 GES_API void
 ges_uri_clip_set_mute (GESUriClip * self, gboolean mute);
 
@@ -90,6 +70,3 @@ GES_API
 GESUriClip* ges_uri_clip_new (const gchar *uri);
 
 G_END_DECLS
-
-#endif /* _GES_URI_CLIP */
-
diff --git a/ges/ges-uri-source.c b/ges/ges-uri-source.c
new file mode 100644 (file)
index 0000000..e98ce10
--- /dev/null
@@ -0,0 +1,223 @@
+/* GStreamer Editing Services
+ * Copyright (C) 2020 Ubicast S.A
+ *     Author: Thibault Saunier <tsaunier@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ges-internal.h"
+#include "ges-uri-source.h"
+
+GST_DEBUG_CATEGORY_STATIC (uri_source_debug);
+#undef GST_CAT_DEFAULT
+#define GST_CAT_DEFAULT uri_source_debug
+
+#define DEFAULT_RAW_CAPS                       \
+  "video/x-raw; "                              \
+  "audio/x-raw; "                              \
+  "text/x-raw; "                               \
+  "subpicture/x-dvd; "                 \
+  "subpicture/x-pgs"
+
+static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS);
+
+static inline gboolean
+are_raw_caps (const GstCaps * caps)
+{
+  GstCaps *raw = gst_static_caps_get (&default_raw_caps);
+  gboolean res = gst_caps_can_intersect (caps, raw);
+
+  gst_caps_unref (raw);
+  return res;
+}
+
+typedef enum
+{
+  GST_AUTOPLUG_SELECT_TRY,
+  GST_AUTOPLUG_SELECT_EXPOSE,
+  GST_AUTOPLUG_SELECT_SKIP,
+} GstAutoplugSelectResult;
+
+static gint
+autoplug_select_cb (GstElement * bin, GstPad * pad, GstCaps * caps,
+    GstElementFactory * factory, GESUriSource * self)
+{
+  GstElement *nlesrc;
+  GstCaps *downstream_caps;
+  GstQuery *segment_query = NULL;
+  GstFormat segment_format;
+  GstAutoplugSelectResult res = GST_AUTOPLUG_SELECT_TRY;
+  gchar *stream_id = gst_pad_get_stream_id (pad);
+  const gchar *wanted_id =
+      gst_discoverer_stream_info_get_stream_id
+      (ges_uri_source_asset_get_stream_info (GES_URI_SOURCE_ASSET
+          (ges_extractable_get_asset (GES_EXTRACTABLE (self->element)))));
+  gboolean wanted = !g_strcmp0 (stream_id, wanted_id);
+
+  if (!ges_source_get_rendering_smartly (GES_SOURCE (self->element))) {
+    if (!are_raw_caps (caps))
+      goto done;
+
+    if (!wanted) {
+      GST_INFO_OBJECT (self->element, "Not matching stream id: %s -> SKIPPING",
+          stream_id);
+      res = GST_AUTOPLUG_SELECT_SKIP;
+    } else {
+      GST_INFO_OBJECT (self->element, "Using stream %s", stream_id);
+    }
+    goto done;
+  }
+
+  segment_query = gst_query_new_segment (GST_FORMAT_TIME);
+  if (!gst_pad_query (pad, segment_query)) {
+    GST_DEBUG_OBJECT (pad, "Could not query segment");
+
+    goto done;
+  }
+
+  gst_query_parse_segment (segment_query, NULL, &segment_format, NULL, NULL);
+  if (segment_format != GST_FORMAT_TIME) {
+    GST_DEBUG_OBJECT (pad,
+        "Segment not in %s != time for %" GST_PTR_FORMAT
+        "... continue plugin elements", gst_format_get_name (segment_format),
+        caps);
+
+    goto done;
+  }
+
+  nlesrc = ges_track_element_get_nleobject (self->element);
+  downstream_caps = gst_pad_peer_query_caps (nlesrc->srcpads->data, NULL);
+  if (downstream_caps && gst_caps_can_intersect (downstream_caps, caps)) {
+    if (wanted) {
+      res = GST_AUTOPLUG_SELECT_EXPOSE;
+      GST_INFO_OBJECT (self->element,
+          "Exposing %" GST_PTR_FORMAT " with stream id: %s", caps, stream_id);
+    } else {
+      res = GST_AUTOPLUG_SELECT_SKIP;
+      GST_DEBUG_OBJECT (self->element, "Totally skipping %s", stream_id);
+    }
+  }
+  gst_clear_caps (&downstream_caps);
+
+done:
+  g_free (stream_id);
+  gst_clear_query (&segment_query);
+
+  return res;
+}
+
+GstElement *
+ges_uri_source_create_source (GESUriSource * self)
+{
+  GESTrack *track;
+  GstElement *decodebin;
+  const GstCaps *caps = NULL;
+
+  track = ges_track_element_get_track (self->element);
+
+  self->decodebin = decodebin = gst_element_factory_make ("uridecodebin", NULL);
+  GST_DEBUG_OBJECT (self->element,
+      "%" GST_PTR_FORMAT " - Track! %" GST_PTR_FORMAT, self->decodebin, track);
+
+  if (track)
+    caps = ges_track_get_caps (track);
+
+  g_object_set (decodebin, "caps", caps,
+      "expose-all-streams", FALSE, "uri", self->uri, NULL);
+  g_signal_connect (decodebin, "autoplug-select",
+      G_CALLBACK (autoplug_select_cb), self);
+
+  return decodebin;
+
+}
+
+static void
+ges_uri_source_track_set_cb (GESTrackElement * element,
+    GParamSpec * arg G_GNUC_UNUSED, GESUriSource * self)
+{
+  GESTrack *track;
+  const GstCaps *caps = NULL;
+
+  if (!self->decodebin)
+    return;
+
+  track = ges_track_element_get_track (GES_TRACK_ELEMENT (element));
+  if (!track)
+    return;
+
+  caps = ges_track_get_caps (track);
+
+  GST_INFO_OBJECT (element,
+      "Setting %" GST_PTR_FORMAT "caps to: %" GST_PTR_FORMAT, self->decodebin,
+      caps);
+  g_object_set (self->decodebin, "caps", caps, NULL);
+}
+
+
+
+void
+ges_uri_source_init (GESTrackElement * element, GESUriSource * self)
+{
+  static gsize once = 0;
+
+  if (g_once_init_enter (&once)) {
+    GST_DEBUG_CATEGORY_INIT (uri_source_debug, "gesurisource", 0,
+        "GES uri source");
+    g_once_init_leave (&once, 1);
+  }
+
+  self->element = element;
+  g_signal_connect (element, "notify::track",
+      G_CALLBACK (ges_uri_source_track_set_cb), self);
+}
+
+gboolean
+ges_uri_source_select_pad (GESSource * self, GstPad * pad)
+{
+  gboolean res = TRUE;
+  gboolean is_nested_timeline;
+  GESUriSourceAsset *asset =
+      GES_URI_SOURCE_ASSET (ges_extractable_get_asset (GES_EXTRACTABLE (self)));
+  const GESUriClipAsset *clip_asset =
+      ges_uri_source_asset_get_filesource_asset (asset);
+  const gchar *wanted_stream_id = ges_asset_get_id (GES_ASSET (asset));
+  gchar *stream_id;
+
+  if (clip_asset) {
+    g_object_get (G_OBJECT (clip_asset), "is-nested-timeline",
+        &is_nested_timeline, NULL);
+
+    if (is_nested_timeline) {
+      GST_DEBUG_OBJECT (self, "Nested timeline track selection is handled"
+          " by the timeline SELECT_STREAM events handling.");
+
+      return TRUE;
+    }
+  }
+
+  stream_id = gst_pad_get_stream_id (pad);
+  res = !g_strcmp0 (stream_id, wanted_stream_id);
+
+  GST_INFO_OBJECT (self, "%s pad with stream id: %s as %s wanted",
+      res ? "Using" : "Ignoring", stream_id, wanted_stream_id);
+  g_free (stream_id);
+
+  return res;
+}
diff --git a/ges/ges-uri-source.h b/ges/ges-uri-source.h
new file mode 100644 (file)
index 0000000..86b35ad
--- /dev/null
@@ -0,0 +1,42 @@
+/* GStreamer Editing Services
+ * Copyright (C) 2020 Ubicast SAS
+ *               Author: Thibault Saunier <tsaunier@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <ges/ges.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GESUriSource GESUriSource;
+
+struct _GESUriSource
+{
+  GstElement *decodebin;        /* Reference owned by parent class */
+  gchar *uri;
+
+  GESTrackElement *element;
+};
+
+G_GNUC_INTERNAL gboolean      ges_uri_source_select_pad   (GESSource *self, GstPad *pad);
+G_GNUC_INTERNAL GstElement *ges_uri_source_create_source  (GESUriSource *self);
+G_GNUC_INTERNAL void         ges_uri_source_init          (GESTrackElement *element, GESUriSource *self);
+
+G_END_DECLS
index adb0d0c..3694068 100644 (file)
 #include "ges-track.h"
 #include "ges-layer.h"
 #include "ges.h"
+#include <gst/base/base.h>
 
 static GstElementFactory *compositor_factory = NULL;
 
 /**
  * ges_timeline_new_audio_video:
  *
- * Creates a new #GESTimeline containing a raw audio and a
- * raw video track.
+ * Creates a new timeline containing a single #GESAudioTrack and a
+ * single #GESVideoTrack.
  *
- * Returns: (transfer floating): The newly created #GESTimeline.
+ * Returns: (transfer floating): The new timeline, or %NULL if the tracks
+ * could not be created and added.
  */
 
 GESTimeline *
@@ -130,9 +132,12 @@ ges_pspec_hash (gconstpointer key_spec)
 }
 
 static gboolean
-find_compositor (GstPluginFeatureFilter * feature, gpointer udata)
+find_compositor (GstPluginFeature * feature, gpointer udata)
 {
+  gboolean res = FALSE;
   const gchar *klass;
+  GstPluginFeature *loaded_feature = NULL;
+  GstElement *elem = NULL;
 
   if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature)))
     return FALSE;
@@ -140,9 +145,120 @@ find_compositor (GstPluginFeatureFilter * feature, gpointer udata)
   klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY_CAST (feature),
       GST_ELEMENT_METADATA_KLASS);
 
-  return (strstr (klass, "Compositor") != NULL);
+  if (strstr (klass, "Compositor") == NULL)
+    return FALSE;
+
+  loaded_feature = gst_plugin_feature_load (feature);
+  if (!loaded_feature) {
+    GST_ERROR ("Could not load feature: %" GST_PTR_FORMAT, feature);
+    return FALSE;
+  }
+
+  /* Some hardware compositor elements (d3d11compositor for example) consist of
+   * bin with internal mixer elements */
+  if (g_type_is_a (gst_element_factory_get_element_type (GST_ELEMENT_FACTORY
+              (loaded_feature)), GST_TYPE_BIN)) {
+    GParamSpec *pspec;
+    GstElement *mixer = NULL;
+
+    elem =
+        gst_element_factory_create (GST_ELEMENT_FACTORY_CAST (loaded_feature),
+        NULL);
+
+    /* Checks whether this element has mixer property and the internal element
+     * is aggregator subclass */
+    if (!elem) {
+      GST_ERROR ("Could not create element from factory %" GST_PTR_FORMAT,
+          feature);
+      goto done;
+    }
+
+    pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (elem), "mixer");
+    if (!pspec)
+      goto done;
+
+    if (!g_type_is_a (pspec->value_type, GST_TYPE_ELEMENT))
+      goto done;
+
+    g_object_get (elem, "mixer", &mixer, NULL);
+    if (!mixer)
+      goto done;
+
+    if (GST_IS_AGGREGATOR (mixer))
+      res = TRUE;
+
+    gst_object_unref (mixer);
+  } else {
+    res =
+        g_type_is_a (gst_element_factory_get_element_type (GST_ELEMENT_FACTORY
+            (loaded_feature)), GST_TYPE_AGGREGATOR);
+  }
+
+done:
+  gst_clear_object (&elem);
+  gst_object_unref (loaded_feature);
+  return res;
 }
 
+gboolean
+ges_util_structure_get_clocktime (GstStructure * structure, const gchar * name,
+    GstClockTime * val, GESFrameNumber * frames)
+{
+  gboolean found = FALSE;
+
+  const GValue *gvalue;
+
+  if (!val && !frames)
+    return FALSE;
+
+  gvalue = gst_structure_get_value (structure, name);
+  if (!gvalue)
+    return FALSE;
+
+  if (frames)
+    *frames = GES_FRAME_NUMBER_NONE;
+
+  found = TRUE;
+  if (val && G_VALUE_TYPE (gvalue) == GST_TYPE_CLOCK_TIME) {
+    *val = (GstClockTime) g_value_get_uint64 (gvalue);
+  } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_UINT64) {
+    *val = (GstClockTime) g_value_get_uint64 (gvalue);
+  } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_UINT) {
+    *val = (GstClockTime) g_value_get_uint (gvalue);
+  } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_INT) {
+    *val = (GstClockTime) g_value_get_int (gvalue);
+  } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_INT64) {
+    *val = (GstClockTime) g_value_get_int64 (gvalue);
+  } else if (val && G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE) {
+    gdouble d = g_value_get_double (gvalue);
+
+    if (d == -1.0)
+      *val = GST_CLOCK_TIME_NONE;
+    else
+      *val = d * GST_SECOND;
+  } else if (frames && G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
+    const gchar *str = g_value_get_string (gvalue);
+
+    found = FALSE;
+    if (str && str[0] == 'f') {
+      GValue v = G_VALUE_INIT;
+
+      g_value_init (&v, G_TYPE_UINT64);
+      if (gst_value_deserialize (&v, &str[1])) {
+        *frames = g_value_get_uint64 (&v);
+        if (val)
+          *val = GST_CLOCK_TIME_NONE;
+        found = TRUE;
+      }
+      g_value_reset (&v);
+    }
+  } else {
+    found = FALSE;
+
+  }
+
+  return found;
+}
 
 
 GstElementFactory *
@@ -166,6 +282,19 @@ ges_get_compositor_factory (void)
   return compositor_factory;
 }
 
+void
+ges_idle_add (GSourceFunc func, gpointer udata, GDestroyNotify notify)
+{
+  GMainContext *context = g_main_context_get_thread_default ();
+  GSource *source = g_idle_source_new ();
+  if (!context)
+    context = g_main_context_default ();
+
+  g_source_set_callback (source, func, udata, notify);
+  g_source_attach (source, context);
+
+}
+
 gboolean
 ges_nle_composition_add_object (GstElement * comp, GstElement * object)
 {
index ef150f9..84252b6 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_UTILS
-#define _GES_UTILS
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
@@ -35,6 +34,3 @@ guint ges_pspec_hash (gconstpointer key_spec);
 
 
 G_END_DECLS
-
-#endif /* _GES_UTILS */
-
index 4d17354..3b666ed 100644 (file)
 #define MONITOR_ON_PIPELINE "validate-monitor"
 #define RUNNER_ON_PIPELINE "runner-monitor"
 
-#define DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action) \
-  GESTimeline *timeline; \
-  GstElement * pipeline = gst_validate_scenario_get_pipeline (scenario); \
-  if (pipeline == NULL) { \
-    GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,\
-            "Can't execute a '%s' action after the pipeline "\
-            "has been destroyed.", action->type);\
-    return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;\
-  }\
-  g_object_get (pipeline, "timeline", &timeline, NULL);
-
-#define DECLARE_AND_GET_TIMELINE(scenario, action) \
-  DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action);\
-  gst_object_unref(pipeline);
+typedef struct
+{
+  GMainLoop *ml;
+  GError *error;
+} LoadTimelineData;
 
 static gboolean
-_serialize_project (GstValidateScenario * scenario, GstValidateAction * action)
+_get_clocktime (GstStructure * structure, const gchar * name,
+    GstClockTime * val, GESFrameNumber * frames)
+{
+  const GValue *gvalue = gst_structure_get_value (structure, name);
+
+  if (!gvalue)
+    return FALSE;
+
+  if (frames && G_VALUE_TYPE (gvalue) == G_TYPE_STRING) {
+    const gchar *str = g_value_get_string (gvalue);
+
+    if (str && str[0] == 'f') {
+      GValue v = G_VALUE_INIT;
+
+      g_value_init (&v, G_TYPE_UINT64);
+      if (gst_value_deserialize (&v, &str[1])) {
+        *frames = g_value_get_uint64 (&v);
+        if (val)
+          *val = GST_CLOCK_TIME_NONE;
+        g_value_reset (&v);
+
+        return TRUE;
+      }
+      g_value_reset (&v);
+    }
+  }
+
+  if (!val)
+    return FALSE;
+
+  return gst_validate_utils_get_clocktime (structure, name, val);
+}
+
+static void
+project_loaded_cb (GESProject * project, GESTimeline * timeline,
+    LoadTimelineData * data)
+{
+  g_main_loop_quit (data->ml);
+}
+
+static void
+error_loading_asset_cb (GESProject * project, GError * err,
+    const gchar * unused_id, GType extractable_type, LoadTimelineData * data)
+{
+  data->error = g_error_copy (err);
+  g_main_loop_quit (data->ml);
+}
+
+static GESTimeline *
+_ges_load_timeline (GstValidateScenario * scenario, GstValidateAction * action,
+    const gchar * project_uri)
+{
+  GESProject *project = ges_project_new (project_uri);
+  GESTimeline *timeline;
+  LoadTimelineData data = { 0 };
+
+  data.ml = g_main_loop_new (NULL, TRUE);
+  timeline =
+      GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data.error));
+  if (!timeline)
+    goto done;
+
+  g_signal_connect (project, "loaded", (GCallback) project_loaded_cb, &data);
+  g_signal_connect (project, "error-loading-asset",
+      (GCallback) error_loading_asset_cb, &data);
+  g_main_loop_run (data.ml);
+  g_signal_handlers_disconnect_by_func (project, project_loaded_cb, &data);
+  g_signal_handlers_disconnect_by_func (project, error_loading_asset_cb, &data);
+  GST_INFO_OBJECT (scenario, "Loaded timeline from %s", project_uri);
+
+done:
+  if (data.error) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR, "Can not load timeline from: %s (%s)",
+        project_uri, data.error->message);
+    g_clear_error (&data.error);
+    gst_clear_object (&timeline);
+  }
+
+  g_main_loop_unref (data.ml);
+  gst_object_unref (project);
+  return timeline;
+}
+
+#ifdef G_HAVE_ISO_VARARGS
+#define REPORT_UNLESS(condition, errpoint, ...)                                \
+  G_STMT_START {                                                               \
+    if (!(condition)) {                                                        \
+      res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;                        \
+      gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action,      \
+                                 SCENARIO_ACTION_EXECUTION_ERROR,              \
+                                 __VA_ARGS__);                                 \
+      goto errpoint;                                                           \
+    }                                                                          \
+  }                                                                            \
+  G_STMT_END
+#else /* G_HAVE_GNUC_VARARGS */
+#ifdef G_HAVE_GNUC_VARARGS
+#define REPORT_UNLESS(condition, errpoint, args...)                            \
+  G_STMT_START {                                                               \
+    if (!(condition)) {                                                        \
+      res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;                        \
+      gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action,      \
+                                 SCENARIO_ACTION_EXECUTION_ERROR, ##args);     \
+      goto errpoint;                                                           \
+    }                                                                          \
+  }                                                                            \
+  G_STMT_END
+#endif /* G_HAVE_ISO_VARARGS */
+#endif /* G_HAVE_GNUC_VARARGS */
+
+#define DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action)                \
+  GESTimeline *timeline = NULL;                                                \
+  GstElement *pipeline = NULL;                                                 \
+  const gchar *project_uri =                                                   \
+      gst_structure_get_string(action->structure, "project-uri");              \
+  if (!project_uri) {                                                          \
+    pipeline = gst_validate_scenario_get_pipeline(scenario);                   \
+    REPORT_UNLESS(GES_IS_PIPELINE(pipeline), done,                     \
+                  "Can't execute a '%s' action after the pipeline "            \
+                  "has been destroyed.",                                       \
+                  action->type);                                               \
+    g_object_get(pipeline, "timeline", &timeline, NULL);                       \
+  } else {                                                                     \
+    timeline = _ges_load_timeline(scenario, action, project_uri);              \
+    if (!timeline)                                                             \
+      return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;                       \
+  }
+
+#define DECLARE_AND_GET_TIMELINE(scenario, action)         \
+  DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action); \
+  if (pipeline)                                            \
+    gst_object_unref(pipeline);
+
+#define GES_START_VALIDATE_ACTION(funcname)                                    \
+static gint                                                                    \
+funcname(GstValidateScenario *scenario, GstValidateAction *action) {           \
+  GstValidateActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;                \
+  DECLARE_AND_GET_TIMELINE_AND_PIPELINE(scenario, action);
+
+#define GST_END_VALIDATE_ACTION                                                \
+done:                                                                          \
+  if (res == GST_VALIDATE_EXECUTE_ACTION_OK) {                                        \
+    REPORT_UNLESS(                                                             \
+        _ges_save_timeline_if_needed(timeline, action->structure, NULL),       \
+        done_no_save, "Could not save timeline to %s",                         \
+        gst_structure_get_string(action->structure, "project-id"));            \
+  }                                                                            \
+                                                                               \
+done_no_save:                                                                  \
+  gst_clear_object(&pipeline);                                                 \
+  gst_clear_object(&timeline);                                                 \
+  return res;                                                                  \
+}
+
+#define TRY_GET(name,type,var,def) G_STMT_START {\
+  if  (!gst_structure_get (action->structure, name, type, var, NULL)) {\
+    *var = def; \
+  } \
+} G_STMT_END
+
+GES_START_VALIDATE_ACTION (_serialize_project)
 {
   const gchar *uri = gst_structure_get_string (action->structure, "uri");
-  gboolean res;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
+  gchar *location = gst_uri_get_location (uri),
+      *dir = g_path_get_dirname (location);
 
   gst_validate_printf (action, "Saving project to %s", uri);
 
-  res = ges_timeline_save_to_uri (timeline, uri, NULL, TRUE, NULL);
+  g_mkdir_with_parents (dir, 0755);
+  g_free (location);
+  g_free (dir);
 
-  g_object_unref (timeline);
-  return res;
+  res = ges_timeline_save_to_uri (timeline, uri, NULL, TRUE, NULL);
 }
 
-static gboolean
-_remove_asset (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_remove_asset)
 {
   const gchar *id = NULL;
   const gchar *type_string = NULL;
   GType type;
   GESAsset *asset;
-  gboolean res = FALSE;
   GESProject *project;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
 
   project = ges_timeline_get_project (timeline);
 
   id = gst_structure_get_string (action->structure, "id");
   type_string = gst_structure_get_string (action->structure, "type");
 
-  if (!type_string || !id) {
-    GST_ERROR ("Missing parameters, we got type %s and id %s", type_string, id);
-    goto beach;
-  }
-
-  if (!(type = g_type_from_name (type_string))) {
-    GST_ERROR ("This type doesn't exist : %s", type_string);
-    goto beach;
-  }
+  REPORT_UNLESS (type_string && id, done,
+      "Missing parameters, we got type %s and id %s", type_string, id);
+  REPORT_UNLESS ((type = g_type_from_name (type_string)), done,
+      "This type doesn't exist : %s", type_string);
 
   asset = ges_project_get_asset (project, id, type);
-
-  if (!asset) {
-    GST_ERROR ("No asset with id %s and type %s", id, type_string);
-    goto beach;
-  }
-
+  REPORT_UNLESS (asset, done, "No asset with id %s and type %s", id,
+      type_string);
   res = ges_project_remove_asset (project, asset);
-
-beach:
-  g_object_unref (timeline);
-  return res;
+  gst_object_unref (asset);
 }
 
-static gboolean
-_add_asset (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_add_asset)
 {
   const gchar *id = NULL;
   const gchar *type_string = NULL;
   GType type;
-  GESAsset *asset;
-  gboolean res = FALSE;
+  GESAsset *asset = NULL;
   GESProject *project;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
 
   project = ges_timeline_get_project (timeline);
 
@@ -123,259 +262,499 @@ _add_asset (GstValidateScenario * scenario, GstValidateAction * action)
   gst_validate_printf (action, "Adding asset of type %s with ID %s\n",
       id, type_string);
 
-  if (!type_string || !id) {
-    GST_ERROR ("Missing parameters, we got type %s and id %s", type_string, id);
-    goto beach;
-  }
-
-  if (!(type = g_type_from_name (type_string))) {
-    GST_ERROR ("This type doesn't exist : %s", type_string);
-    goto beach;
-  }
+  REPORT_UNLESS (type_string
+      && id, beach, "Missing parameters, we got type %s and id %s", type_string,
+      id);
+  REPORT_UNLESS ((type =
+          g_type_from_name (type_string)), beach,
+      "This type doesn't exist : %s", type_string);
 
   asset = _ges_get_asset_from_timeline (timeline, type, id, NULL);
 
-  if (!asset) {
-    res = FALSE;
-
-    goto beach;
-  }
-
+  REPORT_UNLESS (asset, beach, "Could not get asset for %s id: %s", type_string,
+      id);
   res = ges_project_add_asset (project, asset);
 
 beach:
-  g_object_unref (timeline);
-  return res;
+  gst_clear_object (&asset);
 }
 
-static gboolean
-_add_layer (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_add_layer)
 {
   GESLayer *layer;
   gint priority;
-  gboolean res = TRUE, auto_transition = FALSE;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
-  if (!gst_structure_get_int (action->structure, "priority", &priority)) {
-    GST_ERROR ("priority is needed when adding a layer");
-    goto failed;
-  }
+  gboolean auto_transition = FALSE;
 
-  gst_validate_printf (action, "Adding layer with priority %d\n", priority);
-  layer = _ges_get_layer_by_priority (timeline, priority);
+  REPORT_UNLESS (gst_structure_get_int (action->structure, "priority",
+          &priority), done, "priority is needed when adding a layer");
+  REPORT_UNLESS ((layer =
+          _ges_get_layer_by_priority (timeline, priority)), done,
+      "No layer with priority: %d", priority);
 
   gst_structure_get_boolean (action->structure, "auto-transition",
       &auto_transition);
   g_object_set (layer, "priority", priority, "auto-transition", auto_transition,
       NULL);
-
-
-beach:
-  g_object_unref (timeline);
-  return res;
-
-failed:
-  goto beach;
+  gst_object_unref (layer);
 }
 
-static gboolean
-_remove_layer (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_remove_layer)
 {
-  GESLayer *layer;
+  GESLayer *layer = NULL;
   gint priority;
-  gboolean res = FALSE;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
-  if (!gst_structure_get_int (action->structure, "priority", &priority)) {
-    GST_ERROR ("priority is needed when removing a layer");
-    goto beach;
-  }
 
+  REPORT_UNLESS (gst_structure_get_int (action->structure, "priority",
+          &priority), done, "'priority' is required when removing a layer");
   layer = _ges_get_layer_by_priority (timeline, priority);
+  REPORT_UNLESS (layer, beach, "No layer with priority %d", priority);
 
-  if (layer) {
-    res = ges_timeline_remove_layer (timeline, layer);
-    gst_object_unref (layer);
-  } else {
-    GST_ERROR ("No layer with priority %d", priority);
-  }
+  res = ges_timeline_remove_layer (timeline, layer);
 
 beach:
-  g_object_unref (timeline);
-  return res;
+  gst_clear_object (&layer);
 }
 
-static gboolean
-_remove_clip (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_remove_clip)
 {
   GESTimelineElement *clip;
-  GESLayer *layer;
+  GESLayer *layer = NULL;
   const gchar *name;
-  gboolean res = FALSE;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
 
   name = gst_structure_get_string (action->structure, "name");
   clip = ges_timeline_get_element (timeline, name);
-  g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
+  REPORT_UNLESS (GES_IS_CLIP (clip), beach, "Couldn't find clip: %s", name);
 
-  gst_validate_printf (action, "removing clip with ID %s\n", name);
   layer = ges_clip_get_layer (GES_CLIP (clip));
+  REPORT_UNLESS (layer, beach, "Clip %s not in a layer", name);
 
-  if (layer) {
-    res = ges_layer_remove_clip (layer, GES_CLIP (clip));
-    gst_object_unref (layer);
-  } else {
-    GST_ERROR ("No layer for clip %s", ges_timeline_element_get_name (clip));
-  }
+  res = ges_layer_remove_clip (layer, GES_CLIP (clip));
 
-  g_object_unref (timeline);
-  return res;
+beach:
+  gst_clear_object (&layer);
+  gst_clear_object (&clip);
 }
 
+GST_END_VALIDATE_ACTION;
 
-static gboolean
-_edit_container (GstValidateScenario * scenario, GstValidateAction * action)
+GES_START_VALIDATE_ACTION (_edit)
 {
   GList *layers = NULL;
-  GESTimelineElement *container;
+  GESTimelineElement *element;
+  GESFrameNumber fposition = GES_FRAME_NUMBER_NONE;
   GstClockTime position;
-  gboolean res = FALSE;
+  GError *err = NULL;
+  gboolean source_position = FALSE;
 
   gint new_layer_priority = -1;
   guint edge = GES_EDGE_NONE;
   guint mode = GES_EDIT_MODE_NORMAL;
 
   const gchar *edit_mode_str = NULL, *edge_str = NULL;
-  const gchar *clip_name;
-
-  DECLARE_AND_GET_TIMELINE (scenario, action);
+  const gchar *element_name;
 
-  clip_name = gst_structure_get_string (action->structure, "container-name");
+  res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
+  element_name = gst_structure_get_string (action->structure,
+      gst_structure_has_name (action->structure, "edit-container") ?
+      "container-name" : "element-name");
 
-  container = ges_timeline_get_element (timeline, clip_name);
-  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
+  element = ges_timeline_get_element (timeline, element_name);
+  REPORT_UNLESS (element, beach, "Could not find element %s", element_name);
+
+  if (!_get_clocktime (action->structure, "position", &position, &fposition)) {
+    fposition = 0;
+    if (!gst_structure_get_int (action->structure, "source-frame",
+            (gint *) & fposition)
+        && !gst_structure_get_int64 (action->structure, "source-frame",
+            &fposition)) {
+      gchar *structstr = gst_structure_to_string (action->structure);
+
+      GST_VALIDATE_REPORT_ACTION (scenario, action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "could not find `position` or `source-frame` in %s", structstr);
+      g_free (structstr);
+      goto beach;
+    }
 
-  if (!gst_validate_action_get_clocktime (scenario, action,
-          "position", &position)) {
-    GST_WARNING ("Could not get position");
-    goto beach;
+    source_position = TRUE;
+    position = GST_CLOCK_TIME_NONE;
   }
 
   if ((edit_mode_str =
-          gst_structure_get_string (action->structure, "edit-mode")))
-    g_return_val_if_fail (gst_validate_utils_enum_from_str (GES_TYPE_EDIT_MODE,
-            edit_mode_str, &mode), FALSE);
+          gst_structure_get_string (action->structure, "edit-mode"))) {
+    REPORT_UNLESS (gst_validate_utils_enum_from_str (GES_TYPE_EDIT_MODE,
+            edit_mode_str, &mode), beach,
+        "Could not get enum from %s", edit_mode_str);
+  }
+
+  if ((edge_str = gst_structure_get_string (action->structure, "edge"))) {
+    REPORT_UNLESS (gst_validate_utils_enum_from_str (GES_TYPE_EDGE, edge_str,
+            &edge), beach, "Could not get enum from %s", edge_str);
+  }
+
+  if (GES_FRAME_NUMBER_IS_VALID (fposition)) {
+    if (source_position) {
+      GESClip *clip = NULL;
+
+      if (GES_IS_CLIP (element))
+        clip = GES_CLIP (element);
+      else if (GES_IS_TRACK_ELEMENT (element))
+        clip = GES_CLIP (element->parent);
+
+      REPORT_UNLESS (clip, beach,
+          "Could not get find element to edit using source frame for %"
+          GST_PTR_FORMAT, action->structure);
+      position =
+          ges_clip_get_timeline_time_from_source_frame (clip, fposition, &err);
+    } else {
+      position = ges_timeline_get_frame_time (timeline, fposition);
+    }
 
-  if ((edge_str = gst_structure_get_string (action->structure, "edge")))
-    g_return_val_if_fail (gst_validate_utils_enum_from_str (GES_TYPE_EDGE,
-            edge_str, &edge), FALSE);
+    REPORT_UNLESS (GST_CLOCK_TIME_IS_VALID (position), beach,
+        "Invalid frame number '%" G_GINT64_FORMAT "': %s", fposition,
+        err ? err->message : "Unknown");
+  }
 
   gst_structure_get_int (action->structure, "new-layer-priority",
       &new_layer_priority);
 
-  gst_validate_printf (action, "Editing %s to %" GST_TIME_FORMAT
-      " in %s mode, edge: %s "
-      "with new layer prio: %d \n\n",
-      clip_name, GST_TIME_ARGS (position),
-      edit_mode_str ? edit_mode_str : "normal",
-      edge_str ? edge_str : "None", new_layer_priority);
-
-  if (!(res = ges_container_edit (GES_CONTAINER (container), layers,
+  if (!(res = ges_timeline_element_edit (element, layers,
               new_layer_priority, mode, edge, position))) {
-    gst_object_unref (container);
-    GST_ERROR ("HERE");
+
+    gchar *fpositionstr = GES_FRAME_NUMBER_IS_VALID (fposition)
+        ? g_strdup_printf ("(%" G_GINT64_FORMAT ")", fposition)
+        : NULL;
+
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not edit '%s' to %" GST_TIME_FORMAT
+        "%s in %s mode, edge: %s "
+        "with new layer prio: %d",
+        element_name, GST_TIME_ARGS (position),
+        fpositionstr ? fpositionstr : "",
+        edit_mode_str ? edit_mode_str : "normal",
+        edge_str ? edge_str : "None", new_layer_priority);
+    g_free (fpositionstr);
     goto beach;
   }
-  gst_object_unref (container);
 
 beach:
-  g_object_unref (timeline);
-  return res;
+  gst_clear_object (&element);
+  g_clear_error (&err);
 }
 
+GST_END_VALIDATE_ACTION;
+
 
 static void
-_commit_done_cb (GstBus * bus, GstMessage * message, GstValidateAction * action)
+_state_changed_cb (GstBus * bus, GstMessage * message,
+    GstValidateAction * action)
 {
-  gst_validate_action_set_done (action);
+  GstState next_state;
+
+  if (!GST_IS_PIPELINE (GST_MESSAGE_SRC (message)))
+    return;
+
+  gst_message_parse_state_changed (message, NULL, NULL, &next_state);
 
-  g_signal_handlers_disconnect_by_func (bus, _commit_done_cb, action);
+  if (next_state == GST_STATE_VOID_PENDING) {
+    gst_validate_action_set_done (action);
+
+    g_signal_handlers_disconnect_by_func (bus, _state_changed_cb, action);
+  }
 }
 
-static gboolean
-_commit (GstValidateScenario * scenario, GstValidateAction * action)
+GES_START_VALIDATE_ACTION (_commit)
 {
   GstBus *bus;
   GstState state;
 
-  DECLARE_AND_GET_TIMELINE_AND_PIPELINE (scenario, action);
-
   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
 
-  gst_validate_printf (action, "Commiting timeline %s\n",
+  gst_validate_printf (action, "Committing timeline %s\n",
       GST_OBJECT_NAME (timeline));
 
-  g_signal_connect (bus, "message::async-done", G_CALLBACK (_commit_done_cb),
-      action);
+  g_signal_connect (bus, "message::state-changed",
+      G_CALLBACK (_state_changed_cb), action);
 
   gst_element_get_state (pipeline, &state, NULL, 0);
   if (!ges_timeline_commit (timeline) || state < GST_STATE_PAUSED) {
-    g_signal_handlers_disconnect_by_func (bus, G_CALLBACK (_commit_done_cb),
+    g_signal_handlers_disconnect_by_func (bus, G_CALLBACK (_state_changed_cb),
         action);
-    gst_object_unref (timeline);
     gst_object_unref (bus);
-
-    return TRUE;
+    goto done;
   }
+
   gst_object_unref (bus);
-  gst_object_unref (timeline);
 
-  return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
+  res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
 }
 
-static gboolean
-_split_clip (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_split_clip)
 {
   const gchar *clip_name;
   GESTimelineElement *element;
   GstClockTime position;
 
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
   clip_name = gst_structure_get_string (action->structure, "clip-name");
 
   element = ges_timeline_get_element (timeline, clip_name);
-  g_return_val_if_fail (GES_IS_CLIP (element), FALSE);
-  g_object_unref (timeline);
+  REPORT_UNLESS (GES_IS_CLIP (element), beach, "Could not find clip: %s",
+      clip_name);
+  REPORT_UNLESS (gst_validate_action_get_clocktime (scenario, action,
+          "position", &position), beach,
+      "Could not find position in %" GST_PTR_FORMAT, action->structure);
+  res = (ges_clip_split (GES_CLIP (element), position) != NULL);
+
+beach:
+  gst_clear_object (&element);
+}
+
+GST_END_VALIDATE_ACTION;
+
+typedef struct
+{
+  GstValidateScenario *scenario;
+  GESTimelineElement *element;
+  GstValidateActionReturn res;
+  GstClockTime time;
+  gboolean on_children;
+  GstValidateAction *action;
+} PropertyData;
+
+static gboolean
+check_property (GQuark field_id, GValue * expected_value, PropertyData * data)
+{
+  GValue cvalue = G_VALUE_INIT, *tvalue = NULL, comparable_value = G_VALUE_INIT,
+      *observed_value;
+  const gchar *property = g_quark_to_string (field_id);
+  GstControlBinding *binding = NULL;
+
+  if (!data->on_children) {
+    GObject *tmpobject, *object = g_object_ref (G_OBJECT (data->element));
+    gchar **object_prop_name = g_strsplit (property, "::", 2);
+    gint i = 0;
+    GParamSpec *pspec = NULL;
+
+    while (TRUE) {
+      pspec =
+          g_object_class_find_property (G_OBJECT_GET_CLASS (object),
+          object_prop_name[i]);
+
+      if (!pspec) {
+        GST_VALIDATE_REPORT_ACTION (data->scenario, data->action,
+            SCENARIO_ACTION_EXECUTION_ERROR,
+            "Could not get property %s on %" GES_FORMAT,
+            object_prop_name[i], GES_ARGS (data->element));
+        data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+        g_strfreev (object_prop_name);
+
+        return FALSE;
+      }
+
+      if (!object_prop_name[++i])
+        break;
+
+      tmpobject = object;
+      g_object_get (tmpobject, pspec->name, &object, NULL);
+      g_object_unref (tmpobject);
+    }
 
-  g_return_val_if_fail (gst_validate_action_get_clocktime (scenario, action,
-          "position", &position), FALSE);
+    g_strfreev (object_prop_name);
+    g_value_init (&cvalue, pspec->value_type);
+    g_object_get_property (object, pspec->name, &cvalue);
+    g_object_unref (object);
+    goto compare;
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (data->time)) {
+    if (!GES_IS_TRACK_ELEMENT (data->element)) {
+      GST_VALIDATE_REPORT_ACTION (data->scenario, data->action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "Could not get property at time for type %s - only GESTrackElement supported",
+          G_OBJECT_TYPE_NAME (data->element));
+      data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+
+      return FALSE;
+    }
+
+    binding =
+        ges_track_element_get_control_binding (GES_TRACK_ELEMENT
+        (data->element), property);
+    if (binding) {
+      tvalue = gst_control_binding_get_value (binding, data->time);
+
+      if (!tvalue) {
+        GST_VALIDATE_REPORT_ACTION (data->scenario, data->action,
+            SCENARIO_ACTION_EXECUTION_ERROR,
+            "Could not get property: %s at %" GST_TIME_FORMAT, property,
+            GST_TIME_ARGS (data->time));
+        data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+
+        return FALSE;
+      }
+    }
+  }
+
+  if (!tvalue
+      && !ges_timeline_element_get_child_property (data->element, property,
+          &cvalue)) {
+    GST_VALIDATE_REPORT_ACTION (data->scenario, data->action,
+        SCENARIO_ACTION_EXECUTION_ERROR, "Could not get child property: %s:",
+        property);
+    data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+
+    return FALSE;
+  }
+
+compare:
+  observed_value = tvalue ? tvalue : &cvalue;
+
+  if (G_VALUE_TYPE (observed_value) != G_VALUE_TYPE (expected_value)) {
+    g_value_init (&comparable_value, G_VALUE_TYPE (observed_value));
+
+    if (G_VALUE_TYPE (observed_value) == GST_TYPE_CLOCK_TIME) {
+      GstClockTime t;
+
+      if (gst_validate_utils_get_clocktime (data->action->structure, property,
+              &t)) {
+        g_value_set_uint64 (&comparable_value, t);
+        expected_value = &comparable_value;
+      }
+    } else if (g_value_transform (expected_value, &comparable_value)) {
+      expected_value = &comparable_value;
+    }
+  }
 
-  return (ges_clip_split (GES_CLIP (element), position) != NULL);
+  if (gst_value_compare (observed_value, expected_value) != GST_VALUE_EQUAL) {
+    gchar *expected = gst_value_serialize (expected_value), *observed =
+        gst_value_serialize (observed_value);
+
+    GST_VALIDATE_REPORT_ACTION (data->scenario, data->action,
+        SCENARIO_ACTION_CHECK_ERROR,
+        "%s::%s expected value: '(%s)%s' different than observed: '(%s)%s'",
+        GES_TIMELINE_ELEMENT_NAME (data->element), property,
+        G_VALUE_TYPE_NAME (observed_value), expected,
+        G_VALUE_TYPE_NAME (expected_value), observed);
+
+    g_free (expected);
+    g_free (observed);
+    data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+  }
+
+  if (G_VALUE_TYPE (&comparable_value) != G_TYPE_NONE)
+    g_value_unset (&comparable_value);
+
+  if (tvalue) {
+    g_value_unset (tvalue);
+    g_free (tvalue);
+  } else
+    g_value_reset (&cvalue);
+  return TRUE;
 }
 
 static gboolean
-_set_track_restriction_caps (GstValidateScenario * scenario,
-    GstValidateAction * action)
+set_property (GQuark field_id, const GValue * value, PropertyData * data)
+{
+  const gchar *property = g_quark_to_string (field_id);
+
+  if (data->on_children) {
+    if (!ges_timeline_element_set_child_property (data->element, property,
+            value)) {
+      gchar *v = gst_value_serialize (value);
+
+      GST_VALIDATE_REPORT_ACTION (data->scenario, data->action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "Could not set %s child property %s to %s",
+          GES_TIMELINE_ELEMENT_NAME (data->element), property, v);
+
+      data->res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+      g_free (v);
+
+      return FALSE;
+    }
+  } else {
+    data->res =
+        gst_validate_object_set_property (GST_VALIDATE_REPORTER
+        (data->scenario), G_OBJECT (data->element), property, value, FALSE);
+  }
+
+  return TRUE;
+}
+
+GES_START_VALIDATE_ACTION (set_or_check_properties)
+{
+  GESTimelineElement *element;
+  GstStructure *structure;
+  const gchar *element_name;
+  gboolean is_setting = FALSE;
+  PropertyData data = {
+    .scenario = scenario,
+    .element = NULL,
+    .res = GST_VALIDATE_EXECUTE_ACTION_OK,
+    .time = GST_CLOCK_TIME_NONE,
+    .on_children =
+        !gst_structure_has_name (action->structure, "check-ges-properties")
+        && !gst_structure_has_name (action->structure, "set-ges-properties"),
+    .action = action,
+  };
+
+  is_setting = gst_structure_has_name (action->structure, "set-ges-properties")
+      || gst_structure_has_name (action->structure, "set-child-properties");
+  gst_validate_action_get_clocktime (scenario, action, "at-time", &data.time);
+
+  structure = gst_structure_copy (action->structure);
+  element_name = gst_structure_get_string (structure, "element-name");
+  element = ges_timeline_get_element (timeline, element_name);
+  if (!element) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Can not find element: %s", element_name);
+
+    data.res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto local_done;
+  }
+
+  data.element = element;
+  gst_structure_remove_fields (structure, "element-name", "at-time",
+      "project-uri", NULL);
+  gst_structure_foreach (structure,
+      is_setting ? (GstStructureForeachFunc) set_property
+      : (GstStructureForeachFunc) check_property, &data);
+  gst_object_unref (element);
+
+local_done:
+  gst_structure_free (structure);
+  res = data.res;
+}
+
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_set_track_restriction_caps)
 {
   GList *tmp;
   GstCaps *caps;
-  gboolean res = FALSE;
   GESTrackType track_types;
 
   const gchar *track_type_str =
       gst_structure_get_string (action->structure, "track-type");
   const gchar *caps_str = gst_structure_get_string (action->structure, "caps");
 
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
-  g_return_val_if_fail ((track_types =
-          gst_validate_utils_flags_from_str (GES_TYPE_TRACK_TYPE,
-              track_type_str)), FALSE);
+  REPORT_UNLESS (track_types =
+      gst_validate_utils_flags_from_str (GES_TYPE_TRACK_TYPE, track_type_str),
+      done, "Invalid track types: %s", track_type_str);
 
-  g_return_val_if_fail ((caps =
-          gst_caps_from_string (caps_str)) != NULL, FALSE);
+  REPORT_UNLESS (caps = gst_caps_from_string (caps_str),
+      done, "Invalid track restriction caps: %s", caps_str);
 
+  res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
     GESTrack *track = tmp->data;
 
@@ -383,35 +762,27 @@ _set_track_restriction_caps (GstValidateScenario * scenario,
       gchar *str;
 
       str = gst_caps_to_string (caps);
-      gst_validate_printf (action, "Setting restriction caps %s on track: %s\n",
-          str, GST_ELEMENT_NAME (track));
       g_free (str);
 
       ges_track_set_restriction_caps (track, caps);
 
-      res = TRUE;
+      res = GST_VALIDATE_EXECUTE_ACTION_OK;
     }
   }
   gst_caps_unref (caps);
-  gst_object_unref (timeline);
-
-  return res;
 }
 
-static gboolean
-_set_asset_on_element (GstValidateScenario * scenario,
-    GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_set_asset_on_element)
 {
   GESAsset *asset;
   GESTimelineElement *element;
   const gchar *element_name, *id;
 
-  gboolean res = TRUE;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
   element_name = gst_structure_get_string (action->structure, "element-name");
   element = ges_timeline_get_element (timeline, element_name);
-  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (element), FALSE);
+  REPORT_UNLESS (element, done, "Can't find %s", element_name);
 
   id = gst_structure_get_string (action->structure, "asset-id");
 
@@ -420,208 +791,120 @@ _set_asset_on_element (GstValidateScenario * scenario,
 
   asset = _ges_get_asset_from_timeline (timeline, G_OBJECT_TYPE (element), id,
       NULL);
-  if (asset == NULL) {
-    res = FALSE;
-    GST_ERROR ("Could not find asset: %s", id);
-    goto beach;
-  }
+  REPORT_UNLESS (asset, beach, "Could not find asset: %s", id);
 
   res = ges_extractable_set_asset (GES_EXTRACTABLE (element), asset);
 
 beach:
-  gst_object_unref (timeline);
-
-  return res;
+  gst_clear_object (&asset);
+  gst_clear_object (&element);
 }
 
-static gboolean
-_container_remove_child (GstValidateScenario * scenario,
-    GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_container_remove_child)
 {
-  GESContainer *container;
-  GESTimelineElement *child;
+  GESContainer *container = NULL;
+  GESTimelineElement *child = NULL;
   const gchar *container_name, *child_name;
 
-  gboolean res = TRUE;
-
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
   container_name =
       gst_structure_get_string (action->structure, "container-name");
   container =
-      GES_CONTAINER (ges_timeline_get_element (timeline, container_name));
-  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
+      (GESContainer *) ges_timeline_get_element (timeline, container_name);
+  REPORT_UNLESS (GES_IS_CONTAINER (container), beach,
+      "Could not find container: %s", container_name);
 
   child_name = gst_structure_get_string (action->structure, "child-name");
   child = ges_timeline_get_element (timeline, child_name);
-  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE);
-
-  gst_validate_printf (action, "Remove child %s from container %s\n",
-      child_name, GES_TIMELINE_ELEMENT_NAME (container));
+  REPORT_UNLESS (GES_IS_TIMELINE_ELEMENT (child), beach, "Could not find %s",
+      child_name);
 
   res = ges_container_remove (container, child);
 
-  gst_object_unref (timeline);
-
-  return res;
+beach:
+  gst_clear_object (&container);
+  gst_clear_object (&child);
 }
 
-static gboolean
-_ungroup (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_ungroup)
 {
   GESContainer *container;
   gboolean recursive = FALSE;
   const gchar *container_name;
 
-  gboolean res = TRUE;
-
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
   container_name =
       gst_structure_get_string (action->structure, "container-name");
   container =
-      GES_CONTAINER (ges_timeline_get_element (timeline, container_name));
-  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
-
-  gst_validate_printf (action, "Ungrouping children from container %s\n",
-      GES_TIMELINE_ELEMENT_NAME (container));
+      (GESContainer *) ges_timeline_get_element (timeline, container_name);
+  REPORT_UNLESS (GES_IS_CONTAINER (container), beach, "Could not find %s",
+      container_name);
 
   gst_structure_get_boolean (action->structure, "recursive", &recursive);
 
   g_list_free (ges_container_ungroup (container, recursive));
 
-  gst_object_unref (timeline);
-
-  return res;
+beach:
+  gst_clear_object (&container);
 }
 
-static GstValidateExecuteActionReturn
-_copy_element (GstValidateScenario * scenario, GstValidateAction * action)
+GST_END_VALIDATE_ACTION;
+
+GES_START_VALIDATE_ACTION (_copy_element)
 {
-  GESTimelineElement *element, *copied, *pasted;
+  GESTimelineElement *element = NULL, *copied = NULL, *pasted = NULL;
   gboolean recursive = FALSE;
   const gchar *element_name, *paste_name;
   GstClockTime position;
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
 
   element_name = gst_structure_get_string (action->structure, "element-name");
   element = ges_timeline_get_element (timeline, element_name);
 
-  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (element),
-      GST_VALIDATE_EXECUTE_ACTION_ERROR);
-
-  gst_validate_printf (action, "Copying element %s\n",
-      GES_TIMELINE_ELEMENT_NAME (element));
+  REPORT_UNLESS (GES_IS_CONTAINER (element), beach, "Could not find %s",
+      element_name);
 
   if (!gst_structure_get_boolean (action->structure, "recursive", &recursive))
     recursive = TRUE;
 
-  g_return_val_if_fail (gst_validate_action_get_clocktime (scenario, action,
-          "position", &position), FALSE);
+  REPORT_UNLESS (gst_validate_action_get_clocktime (scenario, action,
+          "position", &position), beach, "Could not find position");
 
   copied = ges_timeline_element_copy (element, recursive);
   pasted = ges_timeline_element_paste (copied, position);
-  gst_object_unref (timeline);
 
-  if (!pasted) {
-    GST_VALIDATE_REPORT (scenario,
-        g_quark_from_string ("scenario::execution-error"),
-        "Could not paste clip %s", element_name);
-    return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
-  }
+  REPORT_UNLESS (pasted, beach, "Could not paste clip %s", element_name);
 
   paste_name = gst_structure_get_string (action->structure, "paste-name");
-  if (paste_name) {
-    if (!ges_timeline_element_set_name (pasted, paste_name)) {
-      GST_VALIDATE_REPORT (scenario,
-          g_quark_from_string ("scenario::execution-error"),
-          "Could not set element name %s", paste_name);
-
-      return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
-    }
-  }
+  if (paste_name)
+    REPORT_UNLESS (ges_timeline_element_set_name (pasted, paste_name),
+        beach, "Could not set element name %s", paste_name);
 
-  return GST_VALIDATE_EXECUTE_ACTION_OK;
+beach:
+  gst_clear_object (&pasted);
+  gst_clear_object (&element);
+
+  /* `copied` is only used for the single paste operation, and is not
+   * actually in any timeline. We own it (it is actually still floating).
+   * `pasted` is the actual new object in the timeline. We own a
+   * reference to it. */
+  gst_clear_object (&copied);
 }
 
-static gboolean
-_set_control_source (GstValidateScenario * scenario, GstValidateAction * action)
-{
-  GESTrackElement *element;
-
-  gboolean ret = FALSE;
-  GstControlSource *source = NULL;
-  gchar *element_name, *property_name, *binding_type = NULL,
-      *source_type = NULL, *interpolation_mode = NULL;
-
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
-  g_return_val_if_fail (gst_structure_get (action->structure,
-          "element-name", G_TYPE_STRING, &element_name,
-          "property-name", G_TYPE_STRING, &property_name,
-          "binding-type", G_TYPE_STRING, &binding_type,
-          "source-type", G_TYPE_STRING, &source_type,
-          "interpolation-mode", G_TYPE_STRING, &interpolation_mode, NULL),
-      FALSE);
-
-  element =
-      GES_TRACK_ELEMENT (ges_timeline_get_element (timeline, element_name));
-
-  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
-
-  if (!binding_type)
-    binding_type = g_strdup ("direct");
-
-  if (source_type == NULL || !g_strcmp0 (source_type, "interpolation")) {
-    guint mode;
-
-    source = gst_interpolation_control_source_new ();
-
-    if (interpolation_mode)
-      g_return_val_if_fail (gst_validate_utils_enum_from_str
-          (GST_TYPE_INTERPOLATION_MODE, interpolation_mode, &mode), FALSE);
-    else
-      mode = GST_INTERPOLATION_MODE_LINEAR;
-
-    g_object_set (source, "mode", mode, NULL);
-
-  } else {
-    GST_ERROR_OBJECT (scenario, "Interpolation type %s not supported",
-        source_type);
-
-    goto done;
-  }
-
-  gst_validate_printf (action, "Setting control source on %s:%s\n",
-      element_name, property_name);
-  ret = ges_track_element_set_control_source (element,
-      source, property_name, binding_type);
-
-
-done:
-  gst_object_unref (timeline);
-  g_free (element_name);
-  g_free (binding_type);
-  g_free (source_type);
-  g_free (interpolation_mode);
-
-  return ret;
-}
+GST_END_VALIDATE_ACTION;
 
-static gboolean
-_validate_action_execute (GstValidateScenario * scenario,
-    GstValidateAction * action)
+GES_START_VALIDATE_ACTION (_validate_action_execute)
 {
   GError *err = NULL;
   ActionFromStructureFunc func;
 
-  DECLARE_AND_GET_TIMELINE (scenario, action);
-
   gst_structure_remove_field (action->structure, "playback-time");
   if (gst_structure_has_name (action->structure, "add-keyframe") ||
       gst_structure_has_name (action->structure, "remove-keyframe")) {
     func = _ges_add_remove_keyframe_from_struct;
+  } else if (gst_structure_has_name (action->structure, "set-control-source")) {
+    func = _ges_set_control_source_from_struct;
   } else if (gst_structure_has_name (action->structure, "add-clip")) {
     func = _ges_add_clip_from_struct;
   } else if (gst_structure_has_name (action->structure, "container-add-child")) {
@@ -633,22 +916,19 @@ _validate_action_execute (GstValidateScenario * scenario,
   }
 
   if (!func (timeline, action->structure, &err)) {
-    GST_VALIDATE_REPORT (scenario,
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
         g_quark_from_string ("scenario::execution-error"),
         "Could not execute %s (error: %s)",
         gst_structure_get_name (action->structure),
         err ? err->message : "None");
 
     g_clear_error (&err);
-
-    return TRUE;
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
   }
-
-  gst_object_unref (timeline);
-
-  return TRUE;
 }
 
+GST_END_VALIDATE_ACTION;
+
 static void
 _project_loaded_cb (GESProject * project, GESTimeline * timeline,
     GstValidateAction * action)
@@ -656,10 +936,10 @@ _project_loaded_cb (GESProject * project, GESTimeline * timeline,
   gst_validate_action_set_done (action);
 }
 
-static gboolean
-_load_project (GstValidateScenario * scenario, GstValidateAction * action)
+GES_START_VALIDATE_ACTION (_load_project)
 {
-  GESProject *project;
+  GstState state;
+  GESProject *project = NULL;
   GList *tmp, *tmp_full;
 
   gchar *uri = NULL;
@@ -669,37 +949,28 @@ _load_project (GstValidateScenario * scenario, GstValidateAction * action)
   gchar *tmpfile = g_strdup_printf ("%s%s%s", g_get_tmp_dir (),
       G_DIR_SEPARATOR_S, "tmpxgesload.xges");
 
-  GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
-  DECLARE_AND_GET_TIMELINE_AND_PIPELINE (scenario, action);
+  res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
+  REPORT_UNLESS (GES_IS_PIPELINE (pipeline), local_done,
+      "Not a GES pipeline, can't work with it");
 
-  gst_validate_printf (action, "Loading project from serialized content\n");
-
-  if (!GES_IS_PIPELINE (pipeline)) {
-    GST_VALIDATE_REPORT (scenario,
-        g_quark_from_string ("scenario::execution-error"),
-        "Not a GES pipeline, can't work with it");
-
-    goto fail;
-  }
+  gst_element_get_state (pipeline, &state, NULL, 0);
+  gst_element_set_state (pipeline, GST_STATE_NULL);
 
   content = gst_structure_get_string (action->structure, "serialized-content");
+  if (content) {
 
-  g_file_set_contents (tmpfile, content, -1, &error);
-  if (error) {
-    GST_VALIDATE_REPORT (scenario,
-        g_quark_from_string ("scenario::execution-error"),
+    g_file_set_contents (tmpfile, content, -1, &error);
+    REPORT_UNLESS (!error, local_done,
         "Could not set XML content: %s", error->message);
 
-    goto fail;
-  }
-
-  uri = gst_filename_to_uri (tmpfile, &error);
-  if (error) {
-    GST_VALIDATE_REPORT (scenario,
-        g_quark_from_string ("scenario::execution-error"),
+    uri = gst_filename_to_uri (tmpfile, &error);
+    REPORT_UNLESS (!error, local_done,
         "Could not set filename to URI: %s", error->message);
-
-    goto fail;
+  } else {
+    uri = g_strdup (gst_structure_get_string (action->structure, "uri"));
+    REPORT_UNLESS (uri, local_done,
+        "None of 'uri' or 'content' passed as parameter"
+        " can't load any timeline!");
   }
 
   tmp_full = ges_timeline_get_layers (timeline);
@@ -714,34 +985,138 @@ _load_project (GstValidateScenario * scenario, GstValidateAction * action)
 
   project = ges_project_new (uri);
   g_signal_connect (project, "loaded", G_CALLBACK (_project_loaded_cb), action);
-  ges_project_load (project, timeline, &error);
-  if (error) {
-    GST_VALIDATE_REPORT (scenario,
-        g_quark_from_string ("scenario::execution-error"),
-        "Could not load timeline: %s", error->message);
+  ges_project_load (project, gst_object_ref (timeline), &error);
+  REPORT_UNLESS (!error, local_done,
+      "Could not load timeline: %s", error->message);
 
-    goto fail;
+  gst_element_set_state (pipeline, state);
+
+local_done:
+  gst_clear_object (&project);
+  g_clear_error (&error);
+  g_free (uri);
+  g_free (tmpfile);
+}
+
+GST_END_VALIDATE_ACTION;
+
+static gint
+prepare_seek_action (GstValidateAction * action)
+{
+  gint res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
+  GESFrameNumber fstart, fstop;
+  GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
+  GstValidateActionType *type = gst_validate_get_action_type (action->type);
+  GError *err = NULL;
+
+  DECLARE_AND_GET_TIMELINE (scenario, action);
+
+  if (timeline
+      && ges_util_structure_get_clocktime (action->structure, "start", NULL,
+          &fstart)) {
+    GstClockTime start = ges_timeline_get_frame_time (timeline, fstart);
+
+    if (err) {
+      GST_VALIDATE_REPORT_ACTION (scenario, action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "Invalid seeking frame number '%" G_GINT64_FORMAT "': %s", fstart,
+          err->message);
+      goto done;
+    }
+    gst_structure_set (action->structure, "start", G_TYPE_UINT64, start, NULL);
+  }
+
+  if (timeline
+      && ges_util_structure_get_clocktime (action->structure, "stop", NULL,
+          &fstop)) {
+    GstClockTime stop = ges_timeline_get_frame_time (timeline, fstop);
+
+    if (err) {
+      GST_VALIDATE_REPORT_ACTION (scenario, action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "Invalid seeking frame number '%" G_GINT64_FORMAT "': %s", fstop,
+          err->message);
+      goto done;
+    }
+    gst_structure_set (action->structure, "stop", G_TYPE_UINT64, stop, NULL);
   }
 
+  gst_object_unref (scenario);
+  gst_object_unref (timeline);
+  return type->overriden_type->prepare (action);
+
 done:
-  if (error)
-    g_error_free (error);
+  gst_object_unref (scenario);
+  gst_object_unref (timeline);
+  return res;
+}
 
-  if (uri)
-    g_free (uri);
+static gint
+set_layer_active (GstValidateScenario * scenario, GstValidateAction * action)
+{
+  gboolean active;
+  gint i, layer_prio;
+  GESLayer *layer;
+  GList *tracks = NULL;
+  GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
+  gchar **track_names =
+      gst_validate_utils_get_strv (action->structure, "tracks");
 
-  g_free (tmpfile);
+  DECLARE_AND_GET_TIMELINE (scenario, action);
 
-  return res;
+  for (i = 0; track_names[i]; i++) {
+    GESTrack *track =
+        (GESTrack *) gst_bin_get_by_name (GST_BIN (timeline), track_names[i]);
 
-fail:
+    if (!track) {
+      GST_VALIDATE_REPORT_ACTION (scenario, action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "Could not find track %s", track_names[i]);
+      res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+      goto done;
+    }
 
-  /* We reported the issue ourself, so do not ask GstValidate
-   * to do that for us */
-  res = GST_VALIDATE_EXECUTE_ACTION_OK;
+    tracks = g_list_prepend (tracks, track);
+  }
 
-  goto done;
+  if (!gst_structure_get_int (action->structure, "layer-priority", &layer_prio)) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not find layer from %" GST_PTR_FORMAT, action->structure);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+  if (!(layer = g_list_nth_data (timeline->layers, layer_prio))) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR, "Could not find layer %d", layer_prio);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
 
+  if (!gst_structure_get_boolean (action->structure, "active", &active)) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not find 'active' boolean in %" GST_PTR_FORMAT,
+        action->structure);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+
+  if (!ges_layer_set_active_for_tracks (layer, active, tracks)) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not set active for track defined in %" GST_PTR_FORMAT,
+        action->structure);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+
+done:
+  g_strfreev (track_names);
+  gst_object_unref (timeline);
+  g_list_free_full (tracks, gst_object_unref);
+
+  return res;
 }
 
 #endif
@@ -750,10 +1125,20 @@ gboolean
 ges_validate_register_action_types (void)
 {
 #ifdef HAVE_GST_VALIDATE
+  GstValidateActionType *validate_seek, *seek_override;
+
+
   gst_validate_init ();
+  validate_seek = gst_validate_get_action_type ("seek");
 
   /*  *INDENT-OFF* */
-  gst_validate_register_action_type ("edit-container", "ges", _edit_container,
+  seek_override = gst_validate_register_action_type("seek", "ges", validate_seek->execute,
+                                    validate_seek->parameters, validate_seek->description,
+                                    validate_seek->flags);
+  gst_mini_object_unref(GST_MINI_OBJECT(validate_seek));
+  seek_override->prepare = prepare_seek_action;
+
+  gst_validate_register_action_type ("edit-container", "ges", _edit,
       (GstValidateActionParameter [])  {
         {
          .name = "container-name",
@@ -764,7 +1149,7 @@ ges_validate_register_action_types (void)
         {
           .name = "position",
           .description = "The new position of the GESContainer",
-          .mandatory = TRUE,
+          .mandatory = FALSE,
           .types = "double or string",
           .possible_variables = "position: The current position in the stream\n"
             "duration: The duration of the stream",
@@ -780,10 +1165,10 @@ ges_validate_register_action_types (void)
         {
           .name = "edge",
           .description = "The GESEdge to use to edit @container-name\n"
-                         "should be in [ edge_start, edge_end, edge_none ] ",
+                         "should be in [ start, end, none ] ",
           .mandatory = FALSE,
           .types = "string",
-          .def = "edge_none",
+          .def = "none",
         },
         {
           .name = "new-layer-priority",
@@ -794,11 +1179,79 @@ ges_validate_register_action_types (void)
           .types = "int",
           .def = "-1",
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
        },
        "Allows to edit a container (like a GESClip), for more details, have a look at:\n"
-       "ges_container_edit documentation, Note that the timeline will\n"
-       "be commited, and flushed so that the edition is taken into account",
+       "ges_timeline_element_edit documentation, Note that the timeline will\n"
+       "be committed, and flushed so that the edition is taken into account",
+       GST_VALIDATE_ACTION_TYPE_NONE);
+
+  gst_validate_register_action_type ("edit", "ges", _edit,
+      (GstValidateActionParameter [])  {
+        {
+         .name = "element-name",
+         .description = "The name of the element to edit",
+         .mandatory = TRUE,
+         .types = "string",
+        },
+        {
+          .name = "position",
+          .description = "The new position of the element",
+          .mandatory = FALSE,
+          .types = "double or string",
+          .possible_variables = "position: The current position in the stream\n"
+            "duration: The duration of the stream",
+           NULL
+        },
+        {
+          .name = "source-frame",
+          .description = "The new frame of the element, computed from the @element-name"
+            "clip's source frame.",
+          .mandatory = FALSE,
+          .types = "double or string",
+           NULL
+        },
+        {
+          .name = "edit-mode",
+          .description = "The GESEditMode to use to edit @element-name",
+          .mandatory = FALSE,
+          .types = "string",
+          .def = "normal",
+        },
+        {
+          .name = "edge",
+          .description = "The GESEdge to use to edit @element-name\n"
+                         "should be in [ start, end, none ] ",
+          .mandatory = FALSE,
+          .types = "string",
+          .def = "none",
+        },
+        {
+          .name = "new-layer-priority",
+          .description = "The priority of the layer @element should land in.\n"
+                         "If the layer you're trying to move the element to doesn't exist, it will\n"
+                         "be created automatically. -1 means no move.",
+          .mandatory = FALSE,
+          .types = "int",
+          .def = "-1",
+        },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
+        {NULL}
+       },
+       "Allows to edit a element (like a GESClip), for more details, have a look at:\n"
+       "ges_timeline_element_edit documentation, Note that the timeline will\n"
+       "be committed, and flushed so that the edition is taken into account",
        GST_VALIDATE_ACTION_TYPE_NONE);
 
   gst_validate_register_action_type ("add-asset", "ges", _add_asset,
@@ -815,6 +1268,12 @@ ges_validate_register_action_types (void)
           .mandatory = TRUE,
           NULL
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       },
       "Allows to add an asset to the current project", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -833,6 +1292,12 @@ ges_validate_register_action_types (void)
           .mandatory = TRUE,
           NULL
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         { NULL }
       },
       "Allows to remove an asset from the current project", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -847,6 +1312,12 @@ ges_validate_register_action_types (void)
           .mandatory = FALSE,
           NULL
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         { NULL }
       },
       "Allows to add a layer to the current timeline", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -861,11 +1332,17 @@ ges_validate_register_action_types (void)
         },
         {
           .name = "auto-transition",
-          .description = "Wheter auto-transition is activated on the new layer.",
+          .description = "Whether auto-transition is activated on the new layer.",
           .mandatory = FALSE,
           .types="boolean",
           .def = "False"
         },
+        {
+          .name = "project-uri",
+          .description = "The nested timeline to add clip to",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         { NULL }
       },
       "Allows to remove a layer from the current timeline", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -914,6 +1391,12 @@ ges_validate_register_action_types (void)
           .types = "double or string",
           .mandatory = FALSE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Allows to add a clip to a given layer", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -925,6 +1408,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Allows to remove a clip from a given layer", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -959,9 +1448,98 @@ ges_validate_register_action_types (void)
           .types = "gvalue",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Allows to change child property of an object", GST_VALIDATE_ACTION_TYPE_NONE);
 
+  gst_validate_register_action_type ("set-layer-active", "ges", set_layer_active,
+      (GstValidateActionParameter []) {
+        {
+          .name = "layer-priority",
+          .description = "The priority of the layer to set activness on",
+          .types = "gint",
+          .mandatory = TRUE,
+        },
+        {
+          .name = "active",
+          .description = "The activness of the layer",
+          .types = "gboolean",
+          .mandatory = TRUE,
+        },
+        {
+          .name = "tracks",
+          .description = "tracks",
+          .types = "{string, }",
+          .mandatory = FALSE,
+        },
+        {NULL}
+      }, "Set activness of a layer (on optional tracks).",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
+  gst_validate_register_action_type ("set-ges-properties", "ges", set_or_check_properties,
+      (GstValidateActionParameter []) {
+        {
+          .name = "element-name",
+          .description = "The name of the element on which to set properties",
+          .types = "string",
+          .mandatory = TRUE,
+        },
+        {NULL}
+      }, "Set `element-name` properties values defined by the"
+         " fields in the following format: `property_name=expected-value`",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
+  gst_validate_register_action_type ("check-ges-properties", "ges", set_or_check_properties,
+      (GstValidateActionParameter []) {
+        {
+          .name = "element-name",
+          .description = "The name of the element on which to check properties",
+          .types = "string",
+          .mandatory = TRUE,
+        },
+        {NULL}
+      }, "Check `element-name` properties values defined by the"
+         " fields in the following format: `property_name=expected-value`",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
+  gst_validate_register_action_type ("check-child-properties", "ges", set_or_check_properties,
+      (GstValidateActionParameter []) {
+        {
+          .name = "element-name",
+          .description = "The name of the element on which to check children properties",
+          .types = "string",
+          .mandatory = TRUE,
+        },
+        {
+          .name = "at-time",
+          .description = "The time at which to check the values, taking into"
+            " account the ControlBinding if any set.",
+          .types = "string",
+          .mandatory = FALSE,
+        },
+        {NULL}
+      }, "Check `element-name` children properties values defined by the"
+         " fields in the following format: `property_name=expected-value`",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
+  gst_validate_register_action_type ("set-child-properties", "ges", set_or_check_properties,
+      (GstValidateActionParameter []) {
+        {
+          .name = "element-name",
+          .description = "The name of the element on which to modify child properties",
+          .types = "string",
+          .mandatory = TRUE,
+        },
+        {NULL}
+      }, "Sets `element-name` children properties values defined by the"
+         " fields in the following format: `property-name=new-value`",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
   gst_validate_register_action_type ("split-clip", "ges", _split_clip,
       (GstValidateActionParameter []) {
         {
@@ -976,6 +1554,12 @@ ges_validate_register_action_types (void)
           .types = "double or string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Split a clip at a specified position.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -993,6 +1577,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Sets restriction caps on tracks of a specific type.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1010,6 +1600,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Sets restriction caps on tracks of a specific type.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1043,9 +1639,15 @@ ges_validate_register_action_types (void)
           .mandatory = FALSE,
           .def = "NULL"
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Add a child to @container-name. If asset-id and child-type are specified,"
-       " the child will be created and added. Otherwize @child-name has to be specified"
+       " the child will be created and added. Otherwise @child-name has to be specified"
        " and will be added to the container.", GST_VALIDATE_ACTION_TYPE_NONE);
 
   gst_validate_register_action_type ("container-remove-child", "ges", _container_remove_child,
@@ -1062,6 +1664,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Remove a child from @container-name.", FALSE);
 
@@ -1075,14 +1683,20 @@ ges_validate_register_action_types (void)
         },
         {
           .name = "recursive",
-          .description = "Wether to recurse ungrouping or not.",
+          .description = "Whether to recurse ungrouping or not.",
           .types = "boolean",
           .mandatory = FALSE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Ungroup children of @container-name.", FALSE);
 
-  gst_validate_register_action_type ("set-control-source", "ges", _set_control_source,
+  gst_validate_register_action_type ("set-control-source", "ges", _validate_action_execute,
       (GstValidateActionParameter []) {
         {
           .name = "element-name",
@@ -1117,6 +1731,12 @@ ges_validate_register_action_types (void)
           .mandatory = FALSE,
           .def = "linear",
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Adds a GstControlSource on @element-name::@property-name"
          " allowing you to then add keyframes on that property.", GST_VALIDATE_ACTION_TYPE_NONE);
@@ -1147,8 +1767,14 @@ ges_validate_register_action_types (void)
           .types = "float",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
-      }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE);
+      }, "Set a keyframe on @element-name:property-name.", GST_VALIDATE_ACTION_TYPE_NONE);
 
   gst_validate_register_action_type ("copy-element", "ges", _copy_element,
       (GstValidateActionParameter []) {
@@ -1177,6 +1803,12 @@ ges_validate_register_action_types (void)
           .types = "string",
           .mandatory = FALSE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
       }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE);
 
@@ -1200,20 +1832,34 @@ ges_validate_register_action_types (void)
           .types = "string or float",
           .mandatory = TRUE,
         },
+        {
+          .name = "project-uri",
+          .description = "The project URI with the serialized timeline to execute the action on",
+          .types = "string",
+          .mandatory = FALSE,
+        },
         {NULL}
-      }, "Remove a child from @container-name.", GST_VALIDATE_ACTION_TYPE_NONE);
+      }, "Remove a keyframe on @element-name:property-name.", GST_VALIDATE_ACTION_TYPE_NONE);
 
   gst_validate_register_action_type ("load-project", "ges", _load_project,
       (GstValidateActionParameter [])  {
         {
           .name = "serialized-content",
-          .description = "The full content of the XML describing project in XGES formet.",
-          .mandatory = TRUE,
+          .description = "The full content of the XML describing project in XGES format.",
+          .mandatory = FALSE,
+          .types = "string",
+          NULL
+        },
+        {
+          .name = "uri",
+          .description = "The uri of the project to load (used only if serialized-content is not provided)",
+          .mandatory = FALSE,
+          .types = "string",
           NULL
         },
         {NULL}
       },
-      "Loads a project either from its content passed in the serialized-content field.\n"
+      "Loads a project either from its content passed in the 'serialized-content' field or using the provided 'uri'.\n"
       "Note that it will completely clean the previous timeline",
       GST_VALIDATE_ACTION_TYPE_NONE);
 
index 4dd00e1..60ed5f9 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GES_VERSION_H__
-#define __GES_VERSION_H__
+#pragma once
 
 G_BEGIN_DECLS
 
@@ -28,5 +27,3 @@ G_BEGIN_DECLS
 #define GES_VERSION_NANO  (@GES_VERSION_NANO@)
 
 G_END_DECLS
-
-#endif /* __GES_H__ */
index d8b4a4a..ce83941 100644 (file)
  * SECTION:gesvideosource
  * @title: GESVideoSource
  * @short_description: Base Class for video sources
- *
- * # Children Properties:
- * You can use the following children properties through the
- * #ges_track_element_set_child_property and alike set of methods:
- *
- * <informaltable frame="none">
- * <tgroup cols="3">
- * <colspec colname="properties_type" colwidth="150px"/>
- * <colspec colname="properties_name" colwidth="200px"/>
- * <colspec colname="properties_flags" colwidth="400px"/>
- * <tbody>
- * <row>
- *  <entry role="property_type"><link linkend="gdouble"><type>double</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--alpha">alpha</link></entry>
- *  <entry>The desired alpha for the stream.</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gint"><type>gint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--posx">posx</link></entry>
- *  <entry>The desired x position for the stream.</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gint"><type>gint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--posy">posy</link></entry>
- *  <entry>The desired y position for the stream</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gint"><type>gint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--width">width</link></entry>
- *  <entry>The desired width for that source. Set to 0 if size is not mandatory, will be set to width of the current track.</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="gint"><type>gint</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--height">height</link></entry>
- *  <entry>The desired height for that source. Set to 0 if size is not mandatory, will be set to height of the current track.</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="GstDeinterlaceModes"><type>GstDeinterlaceModes</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--deinterlace-mode">deinterlace-mode</link></entry>
- *  <entry>Deinterlace Mode</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="GstDeinterlaceFields"><type>GstDeinterlaceFields</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--deinterlace-fields">deinterlace-fields</link></entry>
- *  <entry>Fields to use for deinterlacing</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="GstDeinterlaceFieldLayout"><type>GstDeinterlaceFieldLayout</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--deinterlace-tff">deinterlace-tff</link></entry>
- *  <entry>Deinterlace top field first</entry>
- * </row>
- * <row>
- *  <entry role="property_type"><link linkend="GstVideoOrientationMethod"><type>GstVideoOrientationMethod</type></link></entry>
- *  <entry role="property_name"><link linkend="GESVideoSource--video-direction">video-direction</link></entry>
- *  <entry>The desired video rotation and flipping.</entry>
- * </row>
- * </tbody>
- * </tgroup>
- * </informaltable>
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #include "ges-video-source.h"
 #include "ges-layer.h"
 #include "gstframepositioner.h"
+#include "ges-extractable.h"
 
 #define parent_class ges_video_source_parent_class
+static GESExtractableInterface *parent_extractable_iface = NULL;
 
 struct _GESVideoSourcePrivate
 {
@@ -104,8 +47,30 @@ struct _GESVideoSourcePrivate
   GstElement *capsfilter;
 };
 
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESVideoSource, ges_video_source,
-    GES_TYPE_SOURCE);
+static void
+ges_video_source_set_asset (GESExtractable * extractable, GESAsset * asset)
+{
+  GESVideoSource *self = GES_VIDEO_SOURCE (extractable);
+
+  parent_extractable_iface->set_asset (extractable, asset);
+
+  ges_video_source_get_natural_size (self,
+      &self->priv->positioner->natural_width,
+      &self->priv->positioner->natural_height);
+}
+
+static void
+ges_extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->set_asset = ges_video_source_set_asset;
+
+  parent_extractable_iface = g_type_interface_peek_parent (iface);
+}
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESVideoSource, ges_video_source,
+    GES_TYPE_SOURCE, G_ADD_PRIVATE (GESVideoSource)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
+        ges_extractable_interface_init));
 
 /* TrackElement VMethods */
 
@@ -124,56 +89,65 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   return res;
 }
 
-static void
-post_missing_element_message (GstElement * element, const gchar * name)
+
+static gboolean
+_set_parent (GESTimelineElement * element, GESTimelineElement * parent)
 {
-  GstMessage *msg;
+  GESVideoSource *self = GES_VIDEO_SOURCE (element);
+
+  if (!parent)
+    return TRUE;
 
-  msg = gst_missing_element_message_new (element, name);
-  gst_element_post_message (element, msg);
+  /* Some subclass might have different access to its natural size only
+   * once it knows its parent */
+  ges_video_source_get_natural_size (GES_VIDEO_SOURCE (self),
+      &self->priv->positioner->natural_width,
+      &self->priv->positioner->natural_height);
+
+  return TRUE;
 }
 
-static GstElement *
-ges_video_source_create_element (GESTrackElement * trksrc)
+
+static gboolean
+ges_video_source_create_filters (GESVideoSource * self, GPtrArray * elements,
+    gboolean needs_converters)
 {
-  GstElement *topbin;
-  GstElement *sub_element;
-  GstElement *queue = gst_element_factory_make ("queue", NULL);
-  GESVideoSourceClass *source_class = GES_VIDEO_SOURCE_GET_CLASS (trksrc);
-  GESVideoSource *self;
-  GstElement *positioner, *videoflip, *videoscale, *videorate, *capsfilter,
-      *videoconvert, *deinterlace;
-  const gchar *positioner_props[] =
-      { "alpha", "posx", "posy", "width", "height", NULL };
-  const gchar *deinterlace_props[] = { "mode", "fields", "tff", NULL };
+  GESTrackElement *trksrc = GES_TRACK_ELEMENT (self);
+  GstElement *positioner, *videoflip, *capsfilter;
+  const gchar *positioner_props[]
+  = { "alpha", "posx", "posy", "width", "height", "operator", NULL };
   const gchar *videoflip_props[] = { "video-direction", NULL };
 
-  if (!source_class->create_source)
-    return NULL;
-
-  sub_element = source_class->create_source (trksrc);
-
-  self = (GESVideoSource *) trksrc;
+  g_ptr_array_add (elements, gst_element_factory_make ("queue", NULL));
 
   /* That positioner will add metadata to buffers according to its
      properties, acting like a proxy for our smart-mixer dynamic pads. */
-  positioner = gst_element_factory_make ("framepositioner", "frame_tagger");
+  positioner = gst_element_factory_make ("framepositioner", NULL);
   g_object_set (positioner, "zorder",
       G_MAXUINT - GES_TIMELINE_ELEMENT_PRIORITY (self), NULL);
+  g_ptr_array_add (elements, positioner);
+
+  if (needs_converters)
+    g_ptr_array_add (elements, gst_element_factory_make ("videoconvert", NULL));
 
   /* If there's image-orientation tag, make sure the image is correctly oriented
    * before we scale it. */
   videoflip = gst_element_factory_make ("videoflip", "track-element-videoflip");
   g_object_set (videoflip, "video-direction", GST_VIDEO_ORIENTATION_AUTO, NULL);
+  g_ptr_array_add (elements, videoflip);
+
+  if (needs_converters) {
+    g_ptr_array_add (elements, gst_element_factory_make ("videoscale",
+            "track-element-videoscale"));
+    g_ptr_array_add (elements, gst_element_factory_make ("videoconvert",
+            "track-element-videoconvert"));
+  }
+  g_ptr_array_add (elements, gst_element_factory_make ("videorate",
+          "track-element-videorate"));
 
-  videoscale =
-      gst_element_factory_make ("videoscale", "track-element-videoscale");
-  videoconvert =
-      gst_element_factory_make ("videoconvert", "track-element-videoconvert");
-  videorate = gst_element_factory_make ("videorate", "track-element-videorate");
-  deinterlace = gst_element_factory_make ("deinterlace", "deinterlace");
   capsfilter =
       gst_element_factory_make ("capsfilter", "track-element-capsfilter");
+  g_ptr_array_add (elements, capsfilter);
 
   ges_frame_positioner_set_source_and_filter (GST_FRAME_POSITIONNER
       (positioner), trksrc, capsfilter);
@@ -183,30 +157,49 @@ ges_video_source_create_element (GESTrackElement * trksrc)
   ges_track_element_add_children_props (trksrc, videoflip, NULL, NULL,
       videoflip_props);
 
-  if (deinterlace == NULL) {
-    post_missing_element_message (sub_element, "deinterlace");
-
-    GST_ELEMENT_WARNING (sub_element, CORE, MISSING_PLUGIN,
-        ("Missing element '%s' - check your GStreamer installation.",
-            "deinterlace"), ("deinterlacing won't work"));
-    topbin =
-        ges_source_create_topbin ("videosrcbin", sub_element, queue,
-        videoconvert, positioner, videoflip, videoscale, videorate, capsfilter,
-        NULL);
-  } else {
-    ges_track_element_add_children_props (trksrc, deinterlace, NULL, NULL,
-        deinterlace_props);
-    topbin =
-        ges_source_create_topbin ("videosrcbin", sub_element, queue,
-        videoconvert, deinterlace, positioner, videoflip, videoscale, videorate,
-        capsfilter, NULL);
-  }
-
   self->priv->positioner = GST_FRAME_POSITIONNER (positioner);
   self->priv->positioner->scale_in_compositor =
       !GES_VIDEO_SOURCE_GET_CLASS (self)->ABI.abi.disable_scale_in_compositor;
+  ges_video_source_get_natural_size (self,
+      &self->priv->positioner->natural_width,
+      &self->priv->positioner->natural_height);
+
   self->priv->capsfilter = capsfilter;
 
+  return TRUE;
+}
+
+static GstElement *
+ges_video_source_create_element (GESTrackElement * trksrc)
+{
+  GstElement *topbin;
+  GstElement *sub_element;
+  GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_GET_CLASS (trksrc);
+  GESSourceClass *source_class = GES_SOURCE_GET_CLASS (trksrc);
+  GESVideoSource *self;
+  gboolean needs_converters = TRUE;
+  GPtrArray *elements;
+
+  if (!source_class->create_source)
+    return NULL;
+
+  sub_element = source_class->create_source (GES_SOURCE (trksrc));
+
+  self = (GESVideoSource *) trksrc;
+  if (vsource_class->ABI.abi.needs_converters)
+    needs_converters = vsource_class->ABI.abi.needs_converters (self);
+
+  elements = g_ptr_array_new ();
+  g_assert (vsource_class->ABI.abi.create_filters);
+  if (!vsource_class->ABI.abi.create_filters (self, elements, needs_converters)) {
+    g_ptr_array_free (elements, TRUE);
+
+    return NULL;
+  }
+
+  topbin = ges_source_create_topbin (GES_SOURCE (trksrc), "videosrcbin",
+      sub_element, elements);
+
   return topbin;
 }
 
@@ -250,10 +243,13 @@ ges_video_source_class_init (GESVideoSourceClass * klass)
 
   element_class->set_priority = _set_priority;
   element_class->lookup_child = _lookup_child;
+  element_class->set_parent = _set_parent;
 
   track_element_class->nleobject_factorytype = "nlesource";
   track_element_class->create_element = ges_video_source_create_element;
-  video_source_class->create_source = NULL;
+  track_element_class->ABI.abi.default_track_type = GES_TRACK_TYPE_VIDEO;
+
+  video_source_class->ABI.abi.create_filters = ges_video_source_create_filters;
 }
 
 static void
@@ -263,3 +259,33 @@ ges_video_source_init (GESVideoSource * self)
   self->priv->positioner = NULL;
   self->priv->capsfilter = NULL;
 }
+
+/**
+ * ges_video_source_get_natural_size:
+ * @self: A #GESVideoSource
+ * @width: (out): The natural width of the underlying source
+ * @height: (out): The natural height of the underlying source
+ *
+ * Retrieves the natural size of the video stream. The natural size, is
+ * the size at which it will be displayed if no scaling is being applied.
+ *
+ * NOTE: The sources take into account the potential video rotation applied
+ * by the #videoflip element that is inside the source, effects applied on
+ * the clip which potentially also rotate the element are not taken into
+ * account.
+ *
+ * Returns: %TRUE if the object has a natural size, %FALSE otherwise.
+ *
+ * Since: 1.18
+ */
+gboolean
+ges_video_source_get_natural_size (GESVideoSource * self, gint * width,
+    gint * height)
+{
+  GESVideoSourceClass *klass = GES_VIDEO_SOURCE_GET_CLASS (self);
+
+  if (!klass->ABI.abi.get_natural_size)
+    return FALSE;
+
+  return klass->ABI.abi.get_natural_size (self, width, height);
+}
index fd84552..d2a895d 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_VIDEO_SOURCE
-#define _GES_VIDEO_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <gst/gst.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_VIDEO_SOURCE ges_video_source_get_type()
-
-#define GES_VIDEO_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_VIDEO_SOURCE, GESVideoSource))
-
-#define GES_VIDEO_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_VIDEO_SOURCE, GESVideoSourceClass))
-
-#define GES_IS_VIDEO_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_VIDEO_SOURCE))
-
-#define GES_IS_VIDEO_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_VIDEO_SOURCE))
-
-#define GES_VIDEO_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_VIDEO_SOURCE, GESVideoSourceClass))
-
-typedef struct _GESVideoSourcePrivate GESVideoSourcePrivate;
+GES_DECLARE_TYPE(VideoSource, video_source, VIDEO_SOURCE);
 
 /**
  * GESVideoSource:
@@ -76,6 +59,15 @@ struct _GESVideoSourceClass {
   GESSourceClass parent_class;
 
   /*< public >*/
+  /**
+   * GESVideoSource::create_element:
+   * @object: The #GESTrackElement
+   *
+   * Returns: (transfer floating): the #GstElement that the underlying nleobject
+   * controls.
+   *
+   * Deprecated: 1.20: Use #GESSourceClass::create_element instead.
+   */
   GstElement*  (*create_source)           (GESTrackElement * object);
 
   /*< private >*/
@@ -84,13 +76,14 @@ struct _GESVideoSourceClass {
     gpointer _ges_reserved[GES_PADDING];
     struct {
       gboolean disable_scale_in_compositor;
+      gboolean (*needs_converters)(GESVideoSource *self);
+      gboolean (*get_natural_size)(GESVideoSource* self, gint* width, gint* height);
+      gboolean (*create_filters)(GESVideoSource *self, GPtrArray *filters, gboolean needs_converters);
     } abi;
   } ABI;
 };
 
 GES_API
-GType ges_video_source_get_type (void);
+gboolean ges_video_source_get_natural_size(GESVideoSource* self, gint* width, gint* height);
 
 G_END_DECLS
-
-#endif /* _GES_VIDEO_SOURCE */
index 22e04d8..7d0ae58 100644 (file)
@@ -1,6 +1,8 @@
 /* GStreamer Editing Services
  * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
  *               2010 Nokia Corporation
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -21,7 +23,8 @@
 /**
  * SECTION:gesvideotestsource
  * @title: GESVideoTestSource
- * @short_description: produce solid colors and patterns
+ * @short_description: produce solid colors and patterns, possibly with a time
+ * overlay.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 struct _GESVideoTestSourcePrivate
 {
   GESVideoTestPattern pattern;
+
+  GstElement *capsfilter;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GESVideoTestSource, ges_video_test_source,
-    GES_TYPE_VIDEO_SOURCE);
+static void
+ges_extractable_interface_init (GESExtractableInterface * iface)
+{
+  iface->check_id = ges_test_source_asset_check_id;
+  iface->asset_type = GES_TYPE_TRACK_ELEMENT_ASSET;
+}
+
+static GstStructure *
+ges_video_test_source_asset_get_config (GESAsset * asset)
+{
+  const gchar *id = ges_asset_get_id (asset);
+  if (g_strcmp0 (id, g_type_name (ges_asset_get_extractable_type (asset))))
+    return gst_structure_from_string (ges_asset_get_id (asset), NULL);
+
+  return NULL;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GESVideoTestSource, ges_video_test_source,
+    GES_TYPE_VIDEO_SOURCE, G_ADD_PRIVATE (GESVideoTestSource)
+    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
+        ges_extractable_interface_init));
+
+static GstElement *ges_video_test_source_create_source (GESSource * source);
+
+static gboolean
+get_natural_size (GESVideoSource * source, gint * width, gint * height)
+{
+  gboolean res = FALSE;
+  GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (source);
+
+  if (parent) {
+    GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (parent));
+
+    if (asset)
+      res = ges_test_clip_asset_get_natural_size (asset, width, height);
+  }
+
+  if (!res) {
+    *width = DEFAULT_WIDTH;
+    *height = DEFAULT_HEIGHT;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+_set_parent (GESTimelineElement * element, GESTimelineElement * parent)
+{
+  gint width, height, fps_n, fps_d;
+  GstCaps *caps;
+  GESVideoTestSource *self = GES_VIDEO_TEST_SOURCE (element);
+
+
+  if (!parent)
+    goto done;
+
+  g_assert (self->priv->capsfilter);
+  /* Setting the parent ourself as we need it to get the natural size */
+  element->parent = parent;
+  if (!ges_video_source_get_natural_size (GES_VIDEO_SOURCE (self), &width,
+          &height)) {
+    width = DEFAULT_WIDTH;
+    height = DEFAULT_HEIGHT;
+  }
+
+  if (!ges_timeline_element_get_natural_framerate (parent, &fps_n, &fps_d)) {
+    fps_n = DEFAULT_FRAMERATE_N;
+    fps_d = DEFAULT_FRAMERATE_D;
+  }
+
+  caps = gst_caps_new_simple ("video/x-raw",
+      "width", G_TYPE_INT, width,
+      "height", G_TYPE_INT, height,
+      "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
+  g_object_set (self->priv->capsfilter, "caps", caps, NULL);
+  gst_caps_unref (caps);
+
+done:
+  return
+      GES_TIMELINE_ELEMENT_CLASS
+      (ges_video_test_source_parent_class)->set_parent (element, parent);
+}
+
+static gboolean
+_get_natural_framerate (GESTimelineElement * element, gint * fps_n,
+    gint * fps_d)
+{
+  gboolean res = FALSE;
+  GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (element);
+
+  if (parent) {
+    GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (parent));
 
-static GstElement *ges_video_test_source_create_source (GESTrackElement * self);
+    if (asset) {
+      res =
+          ges_clip_asset_get_natural_framerate (GES_CLIP_ASSET (asset), fps_n,
+          fps_d);
+    }
+  }
+
+  if (!res) {
+    *fps_n = DEFAULT_FRAMERATE_N;
+    *fps_d = DEFAULT_FRAMERATE_D;
+  }
+
+  return TRUE;
+}
+
+static void
+dispose (GObject * object)
+{
+  G_OBJECT_CLASS (ges_video_test_source_parent_class)->dispose (object);
+}
 
 static void
 ges_video_test_source_class_init (GESVideoTestSourceClass * klass)
 {
-  GESVideoSourceClass *source_class = GES_VIDEO_SOURCE_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GESVideoSourceClass *vsource_class = GES_VIDEO_SOURCE_CLASS (klass);
+  GESSourceClass *source_class = GES_SOURCE_CLASS (klass);
 
   source_class->create_source = ges_video_test_source_create_source;
+  vsource_class->ABI.abi.get_natural_size = get_natural_size;
+
+  object_class->dispose = dispose;
+
+  GES_TIMELINE_ELEMENT_CLASS (klass)->set_parent = _set_parent;
+  GES_TIMELINE_ELEMENT_CLASS (klass)->get_natural_framerate =
+      _get_natural_framerate;
 }
 
 static void
@@ -59,27 +182,90 @@ ges_video_test_source_init (GESVideoTestSource * self)
   self->priv->pattern = DEFAULT_VPATTERN;
 }
 
+static GstStructure *
+ges_video_test_source_get_config (GESVideoTestSource * self)
+{
+  GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
+  if (asset)
+    return ges_video_test_source_asset_get_config (asset);
+
+  return NULL;
+}
+
 static GstElement *
-ges_video_test_source_create_source (GESTrackElement * self)
+ges_video_test_source_create_overlay (GESVideoTestSource * self)
+{
+  const gchar *bindesc = NULL;
+  GstStructure *config = ges_video_test_source_get_config (self);
+
+  if (!config)
+    return NULL;
+
+  if (gst_structure_has_name (config, "time-overlay")) {
+    gboolean disable_timecodestamper;
+
+    if (gst_structure_get_boolean (config, "disable-timecodestamper",
+            &disable_timecodestamper))
+      bindesc = "timeoverlay";
+    else
+      bindesc = "timecodestamper ! timeoverlay";
+  }
+  gst_structure_free (config);
+
+  if (!bindesc)
+    return NULL;
+
+  return gst_parse_bin_from_description (bindesc, TRUE, NULL);
+}
+
+static GstElement *
+ges_video_test_source_create_source (GESSource * source)
 {
   GstCaps *caps;
   gint pattern;
-  GstElement *testsrc, *capsfilter;
-  const gchar *props[] = { "pattern", NULL };
+  GstElement *testsrc, *res;
+  GstElement *overlay;
+  const gchar *props[] =
+      { "pattern", "background-color", "foreground-color", NULL };
+  GPtrArray *elements;
+  GESVideoTestSource *self = GES_VIDEO_TEST_SOURCE (source);
+  GESTrackElement *element = GES_TRACK_ELEMENT (source);
 
+  g_assert (!GES_TIMELINE_ELEMENT_PARENT (source));
   testsrc = gst_element_factory_make ("videotestsrc", NULL);
-  capsfilter = gst_element_factory_make ("capsfilter", NULL);
-  pattern = ((GESVideoTestSource *) self)->priv->pattern;
+  self->priv->capsfilter = gst_element_factory_make ("capsfilter", NULL);
+  pattern = self->priv->pattern;
 
   g_object_set (testsrc, "pattern", pattern, NULL);
 
-  caps = gst_caps_new_empty_simple ("video/x-raw");
-  g_object_set (capsfilter, "caps", caps, NULL);
+  elements = g_ptr_array_new ();
+  g_ptr_array_add (elements, self->priv->capsfilter);
+  caps = gst_caps_new_simple ("video/x-raw",
+      "width", G_TYPE_INT, DEFAULT_WIDTH,
+      "height", G_TYPE_INT, DEFAULT_HEIGHT,
+      "framerate", GST_TYPE_FRACTION, DEFAULT_FRAMERATE_N, DEFAULT_FRAMERATE_D,
+      NULL);
+  g_object_set (self->priv->capsfilter, "caps", caps, NULL);
   gst_caps_unref (caps);
 
-  ges_track_element_add_children_props (self, testsrc, NULL, NULL, props);
+  overlay = ges_video_test_source_create_overlay (self);
+  if (overlay) {
+    const gchar *overlay_props[] =
+        { "time-mode", "text-y", "text-x", "text-width", "test-height",
+      "halignment", "valignment", "font-desc", NULL
+    };
+
+    ges_track_element_add_children_props (element, overlay, NULL,
+        NULL, overlay_props);
+    g_ptr_array_add (elements, overlay);
+  }
+
+  ges_track_element_add_children_props (element, testsrc, NULL, NULL, props);
+
+  res = ges_source_create_topbin (GES_SOURCE (element), "videotestsrc", testsrc,
+      elements);
 
-  return ges_source_create_topbin ("videotestsrc", testsrc, capsfilter, NULL);
+  return res;
 }
 
 /**
@@ -137,6 +323,11 @@ ges_video_test_source_get_pattern (GESVideoTestSource * source)
 GESVideoTestSource *
 ges_video_test_source_new (void)
 {
-  return g_object_new (GES_TYPE_VIDEO_TEST_SOURCE, "track-type",
-      GES_TRACK_TYPE_VIDEO, NULL);
+  GESVideoTestSource *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_VIDEO_TEST_SOURCE, NULL, NULL);
+
+  res = GES_VIDEO_TEST_SOURCE (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index e6f0afa..e6355af 100644 (file)
@@ -1,6 +1,8 @@
 /* GStreamer Editing Services
  * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
  *               2010 Nokia Corporation
+ * Copyright (C) 2020 Igalia S.L
+ *     Author: 2020 Thibault Saunier <tsaunier@igalia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -18,8 +20,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_VIDEO_TEST_SOURCE
-#define _GES_VIDEO_TEST_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-enums.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_VIDEO_TEST_SOURCE ges_video_test_source_get_type()
-
-#define GES_VIDEO_TEST_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_VIDEO_TEST_SOURCE, GESVideoTestSource))
-
-#define GES_VIDEO_TEST_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_VIDEO_TEST_SOURCE, GESVideoTestSourceClass))
-
-#define GES_IS_VIDEO_TEST_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_VIDEO_TEST_SOURCE))
-
-#define GES_IS_VIDEO_TEST_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_VIDEO_TEST_SOURCE))
-
-#define GES_VIDEO_TEST_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_VIDEO_TEST_SOURCE, GESVideoTestSourceClass))
-
-typedef struct _GESVideoTestSourcePrivate GESVideoTestSourcePrivate;
+GES_DECLARE_TYPE(VideoTestSource, video_test_source, VIDEO_TEST_SOURCE);
 
 /**
  * GESVideoTestSource:
+ *
+ * ### Children Properties
+ *
+ *  {{ libs/GESVideoTestSource-children-props.md }}
  */
 struct _GESVideoTestSource {
   /*< private >*/
@@ -67,9 +56,6 @@ struct _GESVideoTestSourceClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_video_test_source_get_type (void);
-
 GES_API void
 ges_video_test_source_set_pattern(GESVideoTestSource *self,
                                        GESVideoTestPattern pattern);
@@ -77,5 +63,3 @@ GES_API GESVideoTestPattern
 ges_video_test_source_get_pattern (GESVideoTestSource *source);
 
 G_END_DECLS
-
-#endif /* _GES_VIDEO_TEST_SOURCE */
index 90fdeda..1cfe9c7 100644 (file)
  * SECTION: gesvideotrack
  * @title: GESVideoTrack
  * @short_description: A standard GESTrack for raw video
+ *
+ * A #GESVideoTrack is a default video #GESTrack, with a
+ * #GES_TRACK_TYPE_VIDEO #GESTrack:track-type and "video/x-raw(ANY)"
+ * #GESTrack:caps.
+ *
+ * By default, a video track will have its #GESTrack:restriction-caps
+ * set to "video/x-raw" with the following properties:
+ *
+ * - width: 1280
+ * - height: 720
+ * - framerate: 30/1
+ *
+ * These fields are needed for negotiation purposes, but you can change
+ * their values if you wish. It is advised that you do so using
+ * ges_track_update_restriction_caps() with new values for the fields you
+ * wish to change, and any additional fields you may want to add. Unlike
+ * using ges_track_set_restriction_caps(), this will ensure that these
+ * default fields will at least have some value set.
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -28,6 +46,9 @@
 
 #include "ges-video-track.h"
 #include "ges-smart-video-mixer.h"
+#include "ges-internal.h"
+
+#define DEFAULT_RESTRICTION_CAPS "video/x-raw(ANY), framerate=" G_STRINGIFY(DEFAULT_FRAMERATE_N) "/" G_STRINGIFY(DEFAULT_FRAMERATE_D) ", width=" G_STRINGIFY(DEFAULT_WIDTH) ", height=" G_STRINGIFY(DEFAULT_HEIGHT)
 
 struct _GESVideoTrackPrivate
 {
@@ -134,24 +155,35 @@ ges_video_track_class_init (GESVideoTrackClass * klass)
 /**
  * ges_video_track_new:
  *
- * Creates a new #GESVideoTrack of type #GES_TRACK_TYPE_VIDEO and with generic
- * raw video caps ("video/x-raw");
+ * Creates a new video track, with a #GES_TRACK_TYPE_VIDEO
+ * #GESTrack:track-type and "video/x-raw(ANY)" #GESTrack:caps, and
+ * "video/x-raw" #GESTrack:restriction-caps with the properties:
+ *
+ * - width: 1280
+ * - height: 720
+ * - framerate: 30/1
+ *
+ * You should use ges_track_update_restriction_caps() if you wish to
+ * modify these fields, or add additional ones.
  *
- * Returns: (transfer floating): A new #GESTrack.
+ * Returns: (transfer floating): The newly created video track.
  */
 GESVideoTrack *
 ges_video_track_new (void)
 {
   GESVideoTrack *ret;
   GstCaps *caps = gst_caps_new_empty_simple ("video/x-raw");
+  GstCaps *restriction_caps = gst_caps_from_string (DEFAULT_RESTRICTION_CAPS);
 
   ret = g_object_new (GES_TYPE_VIDEO_TRACK, "track-type", GES_TRACK_TYPE_VIDEO,
       "caps", caps, NULL);
 
   ges_track_set_create_element_for_gap_func (GES_TRACK (ret),
       create_element_for_raw_video_gap);
+  ges_track_set_restriction_caps (GES_TRACK (ret), restriction_caps);
 
   gst_caps_unref (caps);
+  gst_caps_unref (restriction_caps);
 
   return ret;
 }
index 608ab41..1f0e925 100644 (file)
@@ -17,8 +17,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_VIDEO_TRACK_H_
-#define _GES_VIDEO_TRACK_H_
+#pragma once
 
 #include <glib-object.h>
 
 
 G_BEGIN_DECLS
 #define GES_TYPE_VIDEO_TRACK             (ges_video_track_get_type ())
-#define GES_VIDEO_TRACK(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_VIDEO_TRACK, GESVideoTrack))
-#define GES_VIDEO_TRACK_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_VIDEO_TRACK, GESVideoTrackClass))
-#define GES_IS_VIDEO_TRACK(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_VIDEO_TRACK))
-#define GES_IS_VIDEO_TRACK_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_VIDEO_TRACK))
-#define GES_VIDEO_TRACK_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_VIDEO_TRACK, GESVideoTrackClass))
-
-typedef struct _GESVideoTrackPrivate GESVideoTrackPrivate;
+GES_DECLARE_TYPE(VideoTrack, video_track, VIDEO_TRACK);
 
 struct _GESVideoTrackClass
 {
@@ -55,10 +48,6 @@ struct _GESVideoTrack
 };
 
 GES_API
-GType ges_video_track_get_type (void) G_GNUC_CONST;
-
-GES_API
 GESVideoTrack * ges_video_track_new (void);
 
 G_END_DECLS
-#endif /* _GES_VIDEO_TRACK_H_ */
index 4c36cca..e84af25 100644 (file)
@@ -139,6 +139,7 @@ ges_video_transition_class_init (GESVideoTransitionClass * klass)
   GObjectClass *object_class;
   GESTrackElementClass *toclass;
   GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
+  GESTrackElementClass *track_element_class = GES_TRACK_ELEMENT_CLASS (klass);
 
   object_class = G_OBJECT_CLASS (klass);
 
@@ -147,6 +148,8 @@ ges_video_transition_class_init (GESVideoTransitionClass * klass)
   object_class->dispose = ges_video_transition_dispose;
   object_class->finalize = ges_video_transition_finalize;
 
+  track_element_class->ABI.abi.default_track_type = GES_TRACK_TYPE_VIDEO;
+
   /**
    * GESVideoTransition:border:
    *
@@ -164,6 +167,7 @@ ges_video_transition_class_init (GESVideoTransitionClass * klass)
    *
    * The #GESVideoStandardTransitionType currently applied on the object
    *
+   * Deprecated:1.20: Use ges_timeline_element_[sg]et_child_property instead.
    */
   properties[PROP_TRANSITION_TYPE] =
       g_param_spec_enum ("transition-type", "Transition type",
@@ -177,6 +181,7 @@ ges_video_transition_class_init (GESVideoTransitionClass * klass)
    *
    * This value represents the direction of the transition.
    *
+    * Deprecated:1.20: Use ges_timeline_element_[sg]et_child_property instead.
    */
   properties[PROP_INVERT] =
       g_param_spec_boolean ("invert", "Invert",
@@ -326,11 +331,12 @@ set_interpolation (GstObject * element, GESVideoTransitionPrivate * priv,
 static GstElement *
 ges_video_transition_create_element (GESTrackElement * object)
 {
-  GstElement *topbin, *iconva, *iconvb, *oconv;
+  GstElement *topbin, *iconva, *iconvb;
   GstElement *mixer = NULL;
   GstPad *sinka_target, *sinkb_target, *src_target, *sinka, *sinkb, *src;
   GESVideoTransition *self;
   GESVideoTransitionPrivate *priv;
+  const gchar *smpte_properties[] = { "invert", "border", NULL };
 
   self = GES_VIDEO_TRANSITION (object);
   priv = self->priv;
@@ -345,14 +351,15 @@ ges_video_transition_create_element (GESTrackElement * object)
       gst_element_factory_make ("framepositioner", "frame_tagger");
   g_object_set (priv->positioner, "zorder",
       G_MAXUINT - GES_TIMELINE_ELEMENT_PRIORITY (self), NULL);
-  oconv = gst_element_factory_make ("videoconvert", "tr-csp-output");
 
-  gst_bin_add_many (GST_BIN (topbin), iconva, iconvb, priv->positioner,
-      oconv, NULL);
+  gst_bin_add_many (GST_BIN (topbin), iconva, iconvb, priv->positioner, NULL);
 
-  mixer = ges_smart_mixer_new (NULL);
-  GES_SMART_MIXER (mixer)->disable_zorder_alpha = TRUE;
-  g_object_set (GES_SMART_MIXER (mixer)->mixer, "background", 3, NULL); /* transparent */
+  mixer =
+      g_object_new (GES_TYPE_SMART_MIXER, "name",
+      GES_TIMELINE_ELEMENT_NAME (object), NULL);
+  GES_SMART_MIXER (mixer)->is_transition = TRUE;
+  gst_util_set_object_arg (G_OBJECT (GES_SMART_MIXER (mixer)->mixer),
+      "background", "transparent");
   gst_bin_add (GST_BIN (topbin), mixer);
 
   priv->mixer_sinka =
@@ -364,16 +371,19 @@ ges_video_transition_create_element (GESTrackElement * object)
       mixer, GES_VIDEO_STANDARD_TRANSITION_TYPE_BAR_WIPE_LR, &priv->smpte,
       priv, &priv->mixer_ghostb);
   g_object_set (priv->mixer_sinka, "zorder", 0, NULL);
-  gst_util_set_object_arg (G_OBJECT (priv->mixer_sinka), "operator", "source");
   g_object_set (priv->mixer_sinkb, "zorder", 1, NULL);
-  gst_util_set_object_arg (G_OBJECT (priv->mixer_sinkb), "operator", "add");
+  gst_util_set_object_arg (G_OBJECT (priv->mixer_sinka), "operator",
+      priv->type ==
+      GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "source" : "over");
+  gst_util_set_object_arg (G_OBJECT (priv->mixer_sinkb), "operator",
+      priv->type ==
+      GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "add" : "over");
 
   fast_element_link (mixer, priv->positioner);
-  fast_element_link (priv->positioner, oconv);
 
   sinka_target = gst_element_get_static_pad (iconva, "sink");
   sinkb_target = gst_element_get_static_pad (iconvb, "sink");
-  src_target = gst_element_get_static_pad (oconv, "src");
+  src_target = gst_element_get_static_pad (priv->positioner, "src");
 
   sinka = gst_ghost_pad_new ("sinka", sinka_target);
   sinkb = gst_ghost_pad_new ("sinkb", sinkb_target);
@@ -411,6 +421,9 @@ ges_video_transition_create_element (GESTrackElement * object)
 
   priv->pending_type = GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE;
 
+  ges_track_element_add_children_props (GES_TRACK_ELEMENT (self),
+      priv->smpte, NULL, NULL, smpte_properties);
+
   return topbin;
 }
 
@@ -539,6 +552,13 @@ ges_video_transition_set_transition_type_internal (GESVideoTransition
     g_object_set (priv->smpte, "type", (gint) type, NULL);
   }
 
+  gst_util_set_object_arg (G_OBJECT (priv->mixer_sinka), "operator",
+      priv->type ==
+      GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "source" : "over");
+  gst_util_set_object_arg (G_OBJECT (priv->mixer_sinkb), "operator",
+      priv->type ==
+      GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE ? "add" : "over");
+
   return TRUE;
 }
 
@@ -551,6 +571,8 @@ ges_video_transition_set_transition_type_internal (GESVideoTransition
  * the border width of the transition. In case this value does
  * not make sense for the current transition type, it is cached
  * for later use.
+ *
+ * Deprecated:1.20: Use ges_timeline_element_set_child_property instead.
  */
 void
 ges_video_transition_set_border (GESVideoTransition * self, guint value)
@@ -569,6 +591,8 @@ ges_video_transition_set_border (GESVideoTransition * self, guint value)
  *
  * Returns: The border values of @self or -1 if not meaningful
  * (this will happen when not using a smpte transition).
+ *
+ * Deprecated:1.20: Use ges_timeline_element_get_child_property instead.
  */
 gint
 ges_video_transition_get_border (GESVideoTransition * self)
@@ -593,6 +617,8 @@ ges_video_transition_get_border (GESVideoTransition * self)
  * the direction of the transition. In case this value does
  * not make sense for the current transition type, it is cached
  * for later use.
+ *
+ * Deprecated:1.20: Use ges_timeline_element_set_child_property instead.
  */
 void
 ges_video_transition_set_inverted (GESVideoTransition * self, gboolean inverted)
@@ -610,6 +636,8 @@ ges_video_transition_set_inverted (GESVideoTransition * self, gboolean inverted)
  * the direction of the transition.
  *
  * Returns: The invert value of @self
+ *
+ * Deprecated:1.20: Use ges_timeline_element_get_child_property instead.
  */
 gboolean
 ges_video_transition_is_inverted (GESVideoTransition * self)
@@ -661,8 +689,7 @@ ges_video_transition_get_transition_type (GESVideoTransition * trans)
   return trans->priv->type;
 }
 
-/**
- * ges_video_transition_new:
+/* ges_video_transition_new:
  *
  * Creates a new #GESVideoTransition.
  *
@@ -672,6 +699,11 @@ ges_video_transition_get_transition_type (GESVideoTransition * trans)
 GESVideoTransition *
 ges_video_transition_new (void)
 {
-  return g_object_new (GES_TYPE_VIDEO_TRANSITION, "track-type",
-      GES_TRACK_TYPE_VIDEO, NULL);
+  GESVideoTransition *res;
+  GESAsset *asset = ges_asset_request (GES_TYPE_VIDEO_TRANSITION, NULL, NULL);
+
+  res = GES_VIDEO_TRANSITION (ges_asset_extract (asset, NULL));
+  gst_object_unref (asset);
+
+  return res;
 }
index 4071438..398f17d 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_VIDEO_TRANSITION
-#define _GES_VIDEO_TRANSITION
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 G_BEGIN_DECLS
 
 #define GES_TYPE_VIDEO_TRANSITION ges_video_transition_get_type()
-
-#define GES_VIDEO_TRANSITION(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_VIDEO_TRANSITION, GESVideoTransition))
-
-#define GES_VIDEO_TRANSITION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_VIDEO_TRANSITION, GESVideoTransitionClass))
-
-#define GES_IS_VIDEO_TRANSITION(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_VIDEO_TRANSITION))
-
-#define GES_IS_VIDEO_TRANSITION_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_VIDEO_TRANSITION))
-
-#define GES_VIDEO_TRANSITION_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_VIDEO_TRANSITION, GESVideoTransitionClass))
-
-typedef struct _GESVideoTransitionPrivate GESVideoTransitionPrivate;
+GES_DECLARE_TYPE(VideoTransition, video_transition, VIDEO_TRANSITION);
 
 /**
  * GESVideoTransition:
@@ -75,9 +58,7 @@ struct _GESVideoTransitionClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_video_transition_get_type               (void);
-GES_API
+GES_DEPRECATED
 GESVideoTransition* ges_video_transition_new (void);
 
 GES_API
@@ -86,19 +67,16 @@ gboolean ges_video_transition_set_transition_type (GESVideoTransition * self,
 GES_API GESVideoStandardTransitionType
 ges_video_transition_get_transition_type          (GESVideoTransition * trans);
 
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties)
 void ges_video_transition_set_border              (GESVideoTransition * self,
                                                          guint value);
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties)
 gint ges_video_transition_get_border              (GESVideoTransition * self);
 
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_element_set_children_properties)
 void ges_video_transition_set_inverted            (GESVideoTransition * self,
                                                          gboolean inverted);
-GES_API
+GES_DEPRECATED_FOR(ges_timeline_element_get_children_properties)
 gboolean ges_video_transition_is_inverted        (GESVideoTransition * self);
 
 G_END_DECLS
-
-#endif /* _GES_TRACK_VIDEO_transition */
-
index 1939046..3eef67b 100644 (file)
 #include "config.h"
 #endif
 
-#include <gst/pbutils/missing-plugins.h>
+#include <stdio.h>
 
+#include <gst/pbutils/missing-plugins.h>
 #include "ges-utils.h"
 #include "ges-internal.h"
 #include "ges-track-element.h"
+#include "ges-uri-source.h"
 #include "ges-video-uri-source.h"
 #include "ges-uri-asset.h"
 #include "ges-extractable.h"
 
 struct _GESVideoUriSourcePrivate
 {
-  GstElement *decodebin;        /* Reference owned by parent class */
+  GESUriSource parent;
 };
 
 enum
@@ -47,48 +49,115 @@ enum
   PROP_URI
 };
 
-static void
-ges_video_uri_source_track_set_cb (GESVideoUriSource * self,
-    GParamSpec * arg G_GNUC_UNUSED, gpointer nothing)
+/* GESSource VMethod */
+static GstElement *
+ges_video_uri_source_create_source (GESSource * element)
+{
+  return ges_uri_source_create_source (GES_VIDEO_URI_SOURCE (element)->priv);
+}
+
+static gboolean
+ges_video_uri_source_needs_converters (GESVideoSource * source)
 {
-  GESTrack *track;
-  const GstCaps *caps = NULL;
+  GESTrack *track = ges_track_element_get_track (GES_TRACK_ELEMENT (source));
+
+  if (!track || ges_track_get_mixing (track)) {
+    GESAsset *asset = ges_asset_request (GES_TYPE_URI_CLIP,
+        GES_VIDEO_URI_SOURCE (source)->uri, NULL);
+    gboolean is_nested = FALSE;
 
-  if (!self->priv->decodebin)
-    return;
+    g_assert (asset);
 
-  track = ges_track_element_get_track (GES_TRACK_ELEMENT (self));
-  if (!track)
-    return;
+    g_object_get (asset, "is-nested-timeline", &is_nested, NULL);
+    gst_object_unref (asset);
+
+    return !is_nested;
+  }
 
-  caps = ges_track_get_caps (track);
 
-  GST_INFO_OBJECT (self, "Setting caps to: %" GST_PTR_FORMAT, caps);
-  g_object_set (self->priv->decodebin, "caps", caps, NULL);
+  return FALSE;
 }
 
-/* GESSource VMethod */
-static GstElement *
-ges_video_uri_source_create_source (GESTrackElement * trksrc)
+static GstDiscovererVideoInfo *
+_get_video_stream_info (GESVideoUriSource * self)
 {
-  GESVideoUriSource *self;
-  GESTrack *track;
-  GstElement *decodebin;
-  const GstCaps *caps = NULL;
+  GstDiscovererStreamInfo *info;
+  GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
+
+  if (!asset) {
+    GST_DEBUG_OBJECT (self, "No asset set yet");
+    return NULL;
+  }
 
-  self = (GESVideoUriSource *) trksrc;
+  info = ges_uri_source_asset_get_stream_info (GES_URI_SOURCE_ASSET (asset));
 
-  track = ges_track_element_get_track (trksrc);
-  if (track)
-    caps = ges_track_get_caps (track);
+  if (!GST_IS_DISCOVERER_VIDEO_INFO (info)) {
+    GST_ERROR_OBJECT (self, "Doesn't have a video info (%" GST_PTR_FORMAT
+        ")", info);
+    return NULL;
+  }
 
-  decodebin = self->priv->decodebin = gst_element_factory_make ("uridecodebin",
-      NULL);
+  return GST_DISCOVERER_VIDEO_INFO (info);
+}
 
-  g_object_set (decodebin, "caps", caps,
-      "expose-all-streams", FALSE, "uri", self->uri, NULL);
 
-  return decodebin;
+gboolean
+ges_video_uri_source_get_natural_size (GESVideoSource * source, gint * width,
+    gint * height)
+{
+  const GstTagList *tags = NULL;
+  gchar *rotation_info = NULL;
+  gint videoflip_method, rotate_angle;
+  GstDiscovererVideoInfo *info =
+      _get_video_stream_info (GES_VIDEO_URI_SOURCE (source));
+
+  if (!info)
+    return FALSE;
+
+  *width = gst_discoverer_video_info_get_width (info);
+  *height = gst_discoverer_video_info_get_height (info);
+  if (!ges_timeline_element_lookup_child (GES_TIMELINE_ELEMENT (source),
+          "GstVideoFlip::video-direction", NULL, NULL))
+    goto done;
+
+  ges_timeline_element_get_child_properties (GES_TIMELINE_ELEMENT (source),
+      "GstVideoFlip::video-direction", &videoflip_method, NULL);
+
+  /* Rotating 90 degrees, either way, rotate */
+  if (videoflip_method == 1 || videoflip_method == 3)
+    goto rotate;
+
+  if (videoflip_method != 8)
+    goto done;
+
+  /* Rotation is automatic, we need to check if the media file is naturally
+     rotated */
+  tags =
+      gst_discoverer_stream_info_get_tags (GST_DISCOVERER_STREAM_INFO (info));
+  if (!tags)
+    goto done;
+
+  if (!gst_tag_list_get_string (tags, GST_TAG_IMAGE_ORIENTATION,
+          &rotation_info))
+    goto done;
+
+  if (sscanf (rotation_info, "rotate-%d", &rotate_angle) == 1) {
+    if (rotate_angle == 90 || rotate_angle == 270)
+      goto rotate;
+  }
+
+done:
+  g_free (rotation_info);
+  return TRUE;
+
+rotate:
+  GST_INFO_OBJECT (source, "Stream is rotated, taking that into account");
+  *width =
+      gst_discoverer_video_info_get_height (GST_DISCOVERER_VIDEO_INFO (info));
+  *height =
+      gst_discoverer_video_info_get_width (GST_DISCOVERER_VIDEO_INFO (info));
+
+  goto done;
 }
 
 /* Extractable interface implementation */
@@ -100,25 +169,10 @@ ges_extractable_check_id (GType type, const gchar * id, GError ** error)
 }
 
 static void
-extractable_set_asset (GESExtractable * self, GESAsset * asset)
-{
-  /* FIXME That should go into #GESTrackElement, but
-   * some work is needed to make sure it works properly */
-
-  if (ges_track_element_get_track_type (GES_TRACK_ELEMENT (self)) ==
-      GES_TRACK_TYPE_UNKNOWN) {
-    ges_track_element_set_track_type (GES_TRACK_ELEMENT (self),
-        ges_track_element_asset_get_track_type (GES_TRACK_ELEMENT_ASSET
-            (asset)));
-  }
-}
-
-static void
 ges_extractable_interface_init (GESExtractableInterface * iface)
 {
   iface->asset_type = GES_TYPE_URI_SOURCE_ASSET;
   iface->check_id = ges_extractable_check_id;
-  iface->set_asset = extractable_set_asset;
 }
 
 G_DEFINE_TYPE_WITH_CODE (GESVideoUriSource, ges_video_uri_source,
@@ -126,18 +180,84 @@ G_DEFINE_TYPE_WITH_CODE (GESVideoUriSource, ges_video_uri_source,
     G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE,
         ges_extractable_interface_init));
 
+static gboolean
+_find_positioner (GstElement * a, GstElement * b)
+{
+  return !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (a)),
+      "framepositioner");
+}
+
+static void
+post_missing_element_message (GstElement * element, const gchar * name)
+{
+  GstMessage *msg;
+
+  msg = gst_missing_element_message_new (element, name);
+  gst_element_post_message (element, msg);
+}
 
 /* GObject VMethods */
+static gboolean
+ges_video_uri_source_create_filters (GESVideoSource * source,
+    GPtrArray * elements, gboolean needs_converters)
+{
+  GESAsset *asset = ges_extractable_get_asset (GES_EXTRACTABLE (source));
+  GstDiscovererVideoInfo *info =
+      GST_DISCOVERER_VIDEO_INFO (ges_uri_source_asset_get_stream_info
+      (GES_URI_SOURCE_ASSET (asset)));
+
+  g_assert (GES_IS_URI_SOURCE_ASSET (asset));
+  if (!GES_VIDEO_SOURCE_CLASS (ges_video_uri_source_parent_class)
+      ->ABI.abi.create_filters (source, elements, needs_converters))
+    return FALSE;
+
+  if (gst_discoverer_video_info_is_interlaced (info)) {
+    const gchar *deinterlace_props[] = { "mode", "fields", "tff", NULL };
+    GstElement *deinterlace =
+        gst_element_factory_make ("deinterlace", "deinterlace");
+
+    if (deinterlace == NULL) {
+      post_missing_element_message (ges_track_element_get_nleobject
+          (GES_TRACK_ELEMENT (source)), "deinterlace");
+
+      GST_ELEMENT_WARNING (ges_track_element_get_nleobject (GES_TRACK_ELEMENT
+              (source)), CORE, MISSING_PLUGIN,
+          ("Missing element '%s' - check your GStreamer installation.",
+              "deinterlace"), ("deinterlacing won't work"));
+    } else {
+      /* Right after the queue */
+      g_ptr_array_insert (elements, 1, gst_element_factory_make ("videoconvert",
+              NULL));
+      g_ptr_array_insert (elements, 2, deinterlace);
+      ges_track_element_add_children_props (GES_TRACK_ELEMENT (source),
+          deinterlace, NULL, NULL, deinterlace_props);
+    }
+
+  }
+
+  if (ges_uri_source_asset_is_image (GES_URI_SOURCE_ASSET (asset))) {
+    guint i;
+
+    g_ptr_array_find_with_equal_func (elements, NULL,
+        (GEqualFunc) _find_positioner, &i);
+    /* Adding the imagefreeze right before the positionner so positioning can happen
+     * properly */
+    g_ptr_array_insert (elements, i,
+        gst_element_factory_make ("imagefreeze", NULL));
+  }
+
+  return TRUE;
+}
 
 static void
 ges_video_uri_source_get_property (GObject * object, guint property_id,
     GValue * value, GParamSpec * pspec)
 {
-  GESVideoUriSource *uriclip = GES_VIDEO_URI_SOURCE (object);
+  GESVideoUriSource *urisource = GES_VIDEO_URI_SOURCE (object);
 
   switch (property_id) {
     case PROP_URI:
-      g_value_set_string (value, uriclip->uri);
+      g_value_set_string (value, urisource->uri);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -148,15 +268,15 @@ static void
 ges_video_uri_source_set_property (GObject * object, guint property_id,
     const GValue * value, GParamSpec * pspec)
 {
-  GESVideoUriSource *uriclip = GES_VIDEO_URI_SOURCE (object);
+  GESVideoUriSource *urisource = GES_VIDEO_URI_SOURCE (object);
 
   switch (property_id) {
     case PROP_URI:
-      if (uriclip->uri) {
-        GST_WARNING_OBJECT (object, "Uri already set to %s", uriclip->uri);
+      if (urisource->uri) {
+        GST_WARNING_OBJECT (object, "Uri already set to %s", urisource->uri);
         return;
       }
-      uriclip->uri = g_value_dup_string (value);
+      urisource->priv->uri = urisource->uri = g_value_dup_string (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -164,25 +284,25 @@ ges_video_uri_source_set_property (GObject * object, guint property_id,
 }
 
 static void
-ges_video_uri_source_dispose (GObject * object)
+ges_video_uri_source_finalize (GObject * object)
 {
-  GESVideoUriSource *uriclip = GES_VIDEO_URI_SOURCE (object);
+  GESVideoUriSource *urisource = GES_VIDEO_URI_SOURCE (object);
 
-  if (uriclip->uri)
-    g_free (uriclip->uri);
+  g_free (urisource->uri);
 
-  G_OBJECT_CLASS (ges_video_uri_source_parent_class)->dispose (object);
+  G_OBJECT_CLASS (ges_video_uri_source_parent_class)->finalize (object);
 }
 
 static void
 ges_video_uri_source_class_init (GESVideoUriSourceClass * klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GESVideoSourceClass *source_class = GES_VIDEO_SOURCE_CLASS (klass);
+  GESSourceClass *src_class = GES_SOURCE_CLASS (klass);
+  GESVideoSourceClass *video_src_class = GES_VIDEO_SOURCE_CLASS (klass);
 
   object_class->get_property = ges_video_uri_source_get_property;
   object_class->set_property = ges_video_uri_source_set_property;
-  object_class->dispose = ges_video_uri_source_dispose;
+  object_class->finalize = ges_video_uri_source_finalize;
 
   /**
    * GESVideoUriSource:uri:
@@ -193,16 +313,21 @@ ges_video_uri_source_class_init (GESVideoUriSourceClass * klass)
       g_param_spec_string ("uri", "URI", "uri of the resource",
           NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
-  source_class->create_source = ges_video_uri_source_create_source;
+  src_class->select_pad = ges_uri_source_select_pad;
+
+  src_class->create_source = ges_video_uri_source_create_source;
+  video_src_class->ABI.abi.needs_converters =
+      ges_video_uri_source_needs_converters;
+  video_src_class->ABI.abi.get_natural_size =
+      ges_video_uri_source_get_natural_size;
+  video_src_class->ABI.abi.create_filters = ges_video_uri_source_create_filters;
 }
 
 static void
 ges_video_uri_source_init (GESVideoUriSource * self)
 {
   self->priv = ges_video_uri_source_get_instance_private (self);
-
-  g_signal_connect (self, "notify::track",
-      G_CALLBACK (ges_video_uri_source_track_set_cb), NULL);
+  ges_uri_source_init (GES_TRACK_ELEMENT (self), self->priv);
 }
 
 /**
index c1bc16e..d024160 100644 (file)
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_VIDEO_URI_SOURCE
-#define _GES_VIDEO_URI_SOURCE
+#pragma once
 
 #include <glib-object.h>
 #include <ges/ges-types.h>
 
 G_BEGIN_DECLS
 
+/**
+ * GESUriSource: (attributes doc.skip=true):
+ */
+typedef struct _GESUriSource GESUriSource;
 #define GES_TYPE_VIDEO_URI_SOURCE ges_video_uri_source_get_type()
-
-#define GES_VIDEO_URI_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_VIDEO_URI_SOURCE, GESVideoUriSource))
-
-#define GES_VIDEO_URI_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_VIDEO_URI_SOURCE, GESVideoUriSourceClass))
-
-#define GES_IS_VIDEO_URI_SOURCE(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_VIDEO_URI_SOURCE))
-
-#define GES_IS_VIDEO_URI_SOURCE_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_VIDEO_URI_SOURCE))
-
-#define GES_VIDEO_URI_SOURCE_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_VIDEO_URI_SOURCE, GESVideoUriSourceClass))
-
-typedef struct _GESVideoUriSourcePrivate GESVideoUriSourcePrivate;
+GES_DECLARE_TYPE(VideoUriSource, video_uri_source, VIDEO_URI_SOURCE);
 
 /**
  * GESVideoUriSource:
+ *
+ * ### Children Properties
+ *
+ *  {{ libs/GESVideoUriSource-children-props.md }}
  */
 struct _GESVideoUriSource {
   /*< private >*/
@@ -55,7 +46,7 @@ struct _GESVideoUriSource {
 
   gchar *uri;
 
-  GESVideoUriSourcePrivate *priv;
+  GESUriSource *priv;
 
   /* Padding for API extension */
   gpointer _ges_reserved[GES_PADDING];
@@ -69,10 +60,4 @@ struct _GESVideoUriSourceClass {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-GES_API
-GType ges_video_uri_source_get_type (void);
-
 G_END_DECLS
-
-#endif /* _GES_VIDEO_URI_SOURCE */
-
index f282d2d..63660f1 100644 (file)
 #include <locale.h>
 
 #include "ges.h"
+#include <glib/gstdio.h>
 #include "ges-internal.h"
 
 #define parent_class ges_xml_formatter_parent_class
 #define API_VERSION 0
-#define MINOR_VERSION 5
-#define VERSION 0.5
+#define MINOR_VERSION 8
+#define VERSION 0.8
 
 #define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL)
 
 #define _GET_PRIV(o) (((GESXmlFormatter*)o)->priv)
 
+typedef struct
+{
+  const gchar *id;
+  gint start_line;
+  gint start_char;
+  gint fd;
+  gchar *filename;
+  GError *error;
+  GMainLoop *ml;
+} SubprojectData;
+
 struct _GESXmlFormatterPrivate
 {
   gboolean ges_opened;
@@ -49,15 +61,25 @@ struct _GESXmlFormatterPrivate
   GString *str;
 
   GHashTable *element_id;
+  GHashTable *subprojects_map;
+  SubprojectData *subproject;
+  gint subproject_depth;
 
   guint nbelements;
 
   guint min_version;
 };
 
+G_LOCK_DEFINE_STATIC (uri_subprojects_map_lock);
+/* { project_uri: { subproject_uri: new_suproject_uri}} */
+static GHashTable *uri_subprojects_map = NULL;
+
 G_DEFINE_TYPE_WITH_PRIVATE (GESXmlFormatter, ges_xml_formatter,
     GES_TYPE_BASE_XML_FORMATTER);
 
+static GString *_save_project (GESFormatter * formatter, GString * str,
+    GESProject * project, GESTimeline * timeline, GError ** error, guint depth);
+
 static inline void
 _parse_ges_element (GMarkupParseContext * context, const gchar * element_name,
     const gchar ** attribute_names, const gchar ** attribute_values,
@@ -71,7 +93,7 @@ _parse_ges_element (GMarkupParseContext * context, const gchar * element_name,
   if (g_strcmp0 (element_name, "ges")) {
     g_set_error (error, G_MARKUP_ERROR,
         G_MARKUP_ERROR_INVALID_CONTENT,
-        "element '%s', Missing <ges> element'", element_name);
+        "Found element '%s', Missing '<ges>' element'", element_name);
     return;
   }
 
@@ -167,6 +189,7 @@ _parse_encoding_profile (GMarkupParseContext * context,
   if (str_preset_properties) {
     preset_properties = gst_structure_from_string (str_preset_properties, NULL);
     if (preset_properties == NULL) {
+      gst_caps_unref (capsformat);
       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
           "element '%s', Wrong preset-properties format.", element_name);
       return;
@@ -176,6 +199,9 @@ _parse_encoding_profile (GMarkupParseContext * context,
   ges_base_xml_formatter_add_encoding_profile (GES_BASE_XML_FORMATTER (self),
       type, NULL, name, description, capsformat, preset, preset_properties,
       preset_name, 0, 0, NULL, 0, FALSE, NULL, TRUE, error);
+
+  if (preset_properties)
+    gst_structure_free (preset_properties);
 }
 
 static inline void
@@ -300,6 +326,7 @@ _parse_asset (GMarkupParseContext * context, const gchar * element_name,
   GType extractable_type;
   const gchar *id, *extractable_type_name, *metadatas = NULL, *properties =
       NULL, *proxy_id = NULL;
+  GESXmlFormatterPrivate *priv = _GET_PRIV (self);
 
   if (!g_markup_collect_attributes (element_name, attribute_names,
           attribute_values, error, G_MARKUP_COLLECT_STRING, "id", &id,
@@ -311,6 +338,39 @@ _parse_asset (GMarkupParseContext * context, const gchar * element_name,
     return;
 
   extractable_type = g_type_from_name (extractable_type_name);
+  if (extractable_type == GES_TYPE_TIMELINE) {
+    SubprojectData *subproj_data = g_malloc0 (sizeof (SubprojectData));
+    const gchar *nid;
+
+    priv->subproject = subproj_data;
+    G_LOCK (uri_subprojects_map_lock);
+    nid = g_hash_table_lookup (priv->subprojects_map, id);
+    G_UNLOCK (uri_subprojects_map_lock);
+
+    if (!nid) {
+      subproj_data->id = id;
+      subproj_data->fd =
+          g_file_open_tmp ("XXXXXX.xges", &subproj_data->filename, error);
+      if (subproj_data->fd == -1) {
+        GST_ERROR_OBJECT (self, "Could not create subproject file for %s", id);
+        return;
+      }
+      g_markup_parse_context_get_position (context, &subproj_data->start_line,
+          &subproj_data->start_char);
+      id = g_filename_to_uri (subproj_data->filename, NULL, NULL);
+      G_LOCK (uri_subprojects_map_lock);
+      g_hash_table_insert (priv->subprojects_map, g_strdup (subproj_data->id),
+          (gchar *) id);
+      G_UNLOCK (uri_subprojects_map_lock);
+      GST_INFO_OBJECT (self, "Serialized subproject %sis now at: %s",
+          subproj_data->id, id);
+    } else {
+      GST_DEBUG_OBJECT (self, "Subproject already exists: %s -> %s", id, nid);
+      id = nid;
+      subproj_data->start_line = -1;
+    }
+  }
+
   if (extractable_type == G_TYPE_NONE)
     g_set_error (error, G_MARKUP_ERROR,
         G_MARKUP_ERROR_INVALID_CONTENT,
@@ -326,6 +386,16 @@ _parse_asset (GMarkupParseContext * context, const gchar * element_name,
     if (properties)
       props = gst_structure_from_string (properties, NULL);
 
+    if (extractable_type == GES_TYPE_URI_CLIP) {
+      G_LOCK (uri_subprojects_map_lock);
+      if (g_hash_table_contains (priv->subprojects_map, id)) {
+        id = g_hash_table_lookup (priv->subprojects_map, id);
+
+        GST_DEBUG_OBJECT (self, "Using subproject %s", id);
+      }
+      G_UNLOCK (uri_subprojects_map_lock);
+    }
+
     ges_base_xml_formatter_add_asset (GES_BASE_XML_FORMATTER (self), id,
         extractable_type, props, metadatas, proxy_id, error);
     if (props)
@@ -364,6 +434,8 @@ _parse_track (GMarkupParseContext * context, const gchar * element_name,
 
   if (properties) {
     props = gst_structure_from_string (properties, NULL);
+    if (!props)
+      goto wrong_properties;
   }
 
   ges_base_xml_formatter_add_track (GES_BASE_XML_FORMATTER (self), track_type,
@@ -390,6 +462,13 @@ convertion_failed:
       g_strerror (errno));
   return;
 
+wrong_properties:
+  gst_clear_caps (&caps);
+  g_set_error (error, G_MARKUP_ERROR,
+      G_MARKUP_ERROR_INVALID_CONTENT,
+      "element '%s', Can not create properties: %s'", element_name, properties);
+  return;
+
 }
 
 static inline void
@@ -401,13 +480,16 @@ _parse_layer (GMarkupParseContext * context, const gchar * element_name,
   guint priority;
   GType extractable_type = G_TYPE_NONE;
   const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL,
-      *extractable_type_name;
+      *extractable_type_name, *deactivated_tracks_str;
+
+  gchar **deactivated_tracks = NULL;
 
   if (!g_markup_collect_attributes (element_name, attribute_names,
           attribute_values, error,
           G_MARKUP_COLLECT_STRING, "priority", &strprio,
           COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name,
           COLLECT_STR_OPT, "properties", &properties,
+          COLLECT_STR_OPT, "deactivated-tracks", &deactivated_tracks_str,
           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
     return;
 
@@ -441,8 +523,13 @@ _parse_layer (GMarkupParseContext * context, const gchar * element_name,
   if (errno)
     goto convertion_failed;
 
+  if (deactivated_tracks_str)
+    deactivated_tracks = g_strsplit (deactivated_tracks_str, " ", -1);
+
   ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self),
-      extractable_type, priority, props, metadatas, error);
+      extractable_type, priority, props, metadatas, deactivated_tracks, error);
+
+  g_strfreev (deactivated_tracks);
 
 done:
   if (props)
@@ -473,6 +560,7 @@ _parse_clip (GMarkupParseContext * context,
   GstStructure *props = NULL, *children_props = NULL;
   GESTrackType track_types;
   GstClockTime start, inpoint = 0, duration, layer_prio;
+  GESXmlFormatterPrivate *priv = _GET_PRIV (self);
 
   const gchar *strid, *asset_id, *strstart, *strin, *strduration, *strrate,
       *strtrack_types, *strtype, *metadatas = NULL, *properties =
@@ -533,11 +621,19 @@ _parse_clip (GMarkupParseContext * context,
       goto wrong_children_properties;
   }
 
+  G_LOCK (uri_subprojects_map_lock);
+  if (g_hash_table_contains (priv->subprojects_map, asset_id)) {
+    asset_id = g_hash_table_lookup (priv->subprojects_map, asset_id);
+    GST_DEBUG_OBJECT (self, "Using subproject %s", asset_id);
+  }
+  G_UNLOCK (uri_subprojects_map_lock);
   ges_base_xml_formatter_add_clip (GES_BASE_XML_FORMATTER (self),
       strid, asset_id, type, start, inpoint, duration, layer_prio,
       track_types, props, children_props, metadatas, error);
   if (props)
     gst_structure_free (props);
+  if (children_props)
+    gst_structure_free (children_props);
 
   return;
 
@@ -553,6 +649,8 @@ wrong_children_properties:
       G_MARKUP_ERROR_INVALID_CONTENT,
       "element '%s', Clip %s children properties '%s', could no be deserialized",
       element_name, asset_id, children_properties);
+  if (props)
+    gst_structure_free (props);
   return;
 
 convertion_failed:
@@ -601,7 +699,7 @@ _parse_binding (GMarkupParseContext * context, const gchar * element_name,
     if (strlen (pair)) {
       GstTimedValue *value;
 
-      value = g_slice_new (GstTimedValue);
+      value = g_new0 (GstTimedValue, 1);
       value_pair = g_strsplit (pair, ":", 0);
       value->timestamp = g_ascii_strtoull (value_pair[0], NULL, 10);
       value->value = g_ascii_strtod (value_pair[1], NULL);
@@ -623,14 +721,16 @@ _parse_source (GMarkupParseContext * context, const gchar * element_name,
     const gchar ** attribute_names, const gchar ** attribute_values,
     GESXmlFormatter * self, GError ** error)
 {
-  GstStructure *children_props = NULL;
-  const gchar *track_id = NULL, *children_properties = NULL;
+  GstStructure *children_props = NULL, *props = NULL;
+  const gchar *track_id = NULL, *children_properties = NULL, *properties =
+      NULL, *metadatas = NULL;
 
   if (!g_markup_collect_attributes (element_name, attribute_names,
           attribute_values, error,
           G_MARKUP_COLLECT_STRING, "track-id", &track_id,
           COLLECT_STR_OPT, "children-properties", &children_properties,
-          G_MARKUP_COLLECT_INVALID)) {
+          COLLECT_STR_OPT, "properties", &properties,
+          COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID)) {
     return;
   }
 
@@ -640,12 +740,22 @@ _parse_source (GMarkupParseContext * context, const gchar * element_name,
       goto wrong_children_properties;
   }
 
+  if (properties) {
+    props = gst_structure_from_string (properties, NULL);
+    if (props == NULL)
+      goto wrong_properties;
+  }
+
   ges_base_xml_formatter_add_source (GES_BASE_XML_FORMATTER (self), track_id,
-      children_props);
+      children_props, props, metadatas);
 
+done:
   if (children_props)
     gst_structure_free (children_props);
 
+  if (props)
+    gst_structure_free (props);
+
   return;
 
 wrong_children_properties:
@@ -653,6 +763,13 @@ wrong_children_properties:
       G_MARKUP_ERROR_INVALID_CONTENT,
       "element '%s', children properties '%s', could no be deserialized",
       element_name, children_properties);
+  goto done;
+
+wrong_properties:
+  g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+      "element '%s', properties '%s', could no be deserialized",
+      element_name, properties);
+  goto done;
 }
 
 static inline void
@@ -775,13 +892,21 @@ _parse_element_start (GMarkupParseContext * context, const gchar * element_name,
 {
   GESXmlFormatterPrivate *priv = _GET_PRIV (self);
 
-  if (!G_UNLIKELY (priv->ges_opened))
+  if (priv->subproject) {
+    if (g_strcmp0 (element_name, "ges") == 0) {
+      priv->subproject_depth += 1;
+    }
+    return;
+  }
+
+  if (!G_UNLIKELY (priv->ges_opened)) {
     _parse_ges_element (context, element_name, attribute_names,
         attribute_values, self, error);
-  else if (!G_UNLIKELY (priv->project_opened))
+  else if (!G_UNLIKELY (priv->project_opened))
     _parse_project (context, element_name, attribute_names, attribute_values,
         self, error);
-  else if (g_strcmp0 (element_name, "encoding-profile") == 0)
+  else if (g_strcmp0 (element_name, "ges") == 0) {
+  } else if (g_strcmp0 (element_name, "encoding-profile") == 0)
     _parse_encoding_profile (context, element_name, attribute_names,
         attribute_values, self, error);
   else if (g_strcmp0 (element_name, "stream-profile") == 0)
@@ -821,19 +946,88 @@ _parse_element_start (GMarkupParseContext * context, const gchar * element_name,
     GST_LOG_OBJECT (self, "Element %s not handled", element_name);
 }
 
+static gboolean
+_save_subproject_data (GESXmlFormatter * self, SubprojectData * subproj_data,
+    gint subproject_end_line, gint subproject_end_char, GError ** error)
+{
+  gsize size;
+  gint line = 1, i;
+  gboolean res = FALSE;
+  gsize start = 0, end = 0;
+  gchar *xml = GES_BASE_XML_FORMATTER (self)->xmlcontent;
+
+  for (i = 0; xml[i] != '\0'; i++) {
+    if (!start && line == subproj_data->start_line) {
+      i += subproj_data->start_char - 1;
+      start = i;
+    }
+
+    if (line == subproject_end_line) {
+      end = i + subproject_end_char - 1;
+      break;
+    }
+
+    if (xml[i] == '\n')
+      line++;
+  }
+  g_assert (start && end);
+  size = (end - start);
+
+  GST_INFO_OBJECT (self, "Saving subproject %s from %d:%d(%" G_GSIZE_FORMAT
+      ") to %d:%d(%" G_GSIZE_FORMAT ")",
+      subproj_data->id, subproj_data->start_line, subproj_data->start_char,
+      start, subproject_end_line, subproject_end_char, end);
+
+  res = g_file_set_contents (subproj_data->filename, &xml[start], size, error);
+
+  return res;
+}
+
 static void
 _parse_element_end (GMarkupParseContext * context,
     const gchar * element_name, gpointer self, GError ** error)
 {
+  GESXmlFormatterPrivate *priv = _GET_PRIV (self);
+  SubprojectData *subproj_data = priv->subproject;
+
   /*GESXmlFormatterPrivate *priv = _GET_PRIV (self); */
-  if (g_strcmp0 (element_name, "ges") == 0 && GES_FORMATTER (self)->project) {
-    gchar *version = g_strdup_printf ("%d.%d",
-        API_VERSION, GES_XML_FORMATTER (self)->priv->min_version);
 
-    ges_meta_container_set_string (GES_META_CONTAINER (GES_FORMATTER
-            (self)->project), GES_META_FORMAT_VERSION, version);
+  if (!g_strcmp0 (element_name, "ges")) {
+    gint subproject_end_line, subproject_end_char;
+
+    if (priv->subproject_depth)
+      priv->subproject_depth -= 1;
+
+    if (!subproj_data) {
+      if (GES_FORMATTER (self)->project) {
+        gchar *version = g_strdup_printf ("%d.%d",
+            API_VERSION, GES_XML_FORMATTER (self)->priv->min_version);
+
+        ges_meta_container_set_string (GES_META_CONTAINER (GES_FORMATTER
+                (self)->project), GES_META_FORMAT_VERSION, version);
+
+        g_free (version);
+        _GET_PRIV (self)->ges_opened = FALSE;
+      }
+    } else if (subproj_data->start_line != -1 && !priv->subproject_depth) {
+      g_markup_parse_context_get_position (context, &subproject_end_line,
+          &subproject_end_char);
+      _save_subproject_data (GES_XML_FORMATTER (self), subproj_data,
+          subproject_end_line, subproject_end_char, error);
+
+      subproj_data->filename = NULL;
+      g_close (subproj_data->fd, error);
+      subproj_data->id = NULL;
+      subproj_data->start_line = 0;
+      subproj_data->start_char = 0;
+    }
 
-    g_free (version);
+    if (!priv->subproject_depth) {
+      g_clear_pointer (&priv->subproject, g_free);
+    }
+  } else if (!g_strcmp0 (element_name, "clip")) {
+    if (!priv->subproject)
+      ges_base_xml_formatter_end_current_clip (GES_BASE_XML_FORMATTER (self));
   }
 }
 
@@ -852,43 +1046,58 @@ _error_parsing (GMarkupParseContext * context, GError * error,
 
 /* XML writting utils */
 static inline void
-append_escaped (GString * str, gchar * tmpstr)
+string_add_indents (GString * str, guint depth, gboolean prepend)
+{
+  gint i;
+  for (i = 0; i < depth; i++)
+    prepend ? g_string_prepend (str, "  ") : g_string_append (str, "  ");
+}
+
+static inline void
+string_append_with_depth (GString * str, const gchar * string, guint depth)
+{
+  string_add_indents (str, depth, FALSE);
+  g_string_append (str, string);
+}
+
+static inline void
+append_escaped (GString * str, gchar * tmpstr, guint depth)
 {
-  g_string_append (str, tmpstr);
+  string_append_with_depth (str, tmpstr, depth);
   g_free (tmpstr);
 }
 
-static inline gboolean
-_can_serialize_spec (GParamSpec * spec)
+gboolean
+ges_util_can_serialize_spec (GParamSpec * spec)
 {
   if (!(spec->flags & G_PARAM_WRITABLE)) {
-    GST_DEBUG ("%s from %s is not writable",
+    GST_LOG ("%s from %s is not writable",
         spec->name, g_type_name (spec->owner_type));
 
     return FALSE;
   } else if (spec->flags & G_PARAM_CONSTRUCT_ONLY) {
-    GST_DEBUG ("%s from %s is construct only",
+    GST_LOG ("%s from %s is construct only",
         spec->name, g_type_name (spec->owner_type));
 
     return FALSE;
   } else if (spec->flags & GES_PARAM_NO_SERIALIZATION &&
       g_type_is_a (spec->owner_type, GES_TYPE_TIMELINE_ELEMENT)) {
-    GST_DEBUG ("%s from %s is set as GES_PARAM_NO_SERIALIZATION",
+    GST_LOG ("%s from %s is set as GES_PARAM_NO_SERIALIZATION",
         spec->name, g_type_name (spec->owner_type));
 
     return FALSE;
   } else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (spec), G_TYPE_OBJECT)) {
-    GST_DEBUG ("%s from %s contains GObject, can't serialize that.",
+    GST_LOG ("%s from %s contains GObject, can't serialize that.",
         spec->name, g_type_name (spec->owner_type));
 
     return FALSE;
   } else if ((g_type_is_a (spec->owner_type, GST_TYPE_OBJECT) &&
           !g_strcmp0 (spec->name, "name"))) {
 
-    GST_DEBUG ("We do not want to serialize the name of GstObjects.");
+    GST_LOG ("We do not want to serialize the name of GstObjects.");
     return FALSE;
   } else if (G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_GTYPE) {
-    GST_DEBUG ("%s from %s contains a GType, can't serialize.",
+    GST_LOG ("%s from %s contains a GType, can't serialize.",
         spec->name, g_type_name (spec->owner_type));
     return FALSE;
   }
@@ -908,7 +1117,8 @@ _init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec)
 }
 
 static gchar *
-_serialize_properties (GObject * object, const gchar * fieldname, ...)
+_serialize_properties (GObject * object, gint * ret_n_props,
+    const gchar * fieldname, ...)
 {
   gchar *ret;
   guint n_props, j;
@@ -921,22 +1131,31 @@ _serialize_properties (GObject * object, const gchar * fieldname, ...)
     GValue val = { 0 };
 
     spec = pspecs[j];
+    if (!ges_util_can_serialize_spec (spec))
+      continue;
+
+    _init_value_from_spec_for_serialization (&val, spec);
+    g_object_get_property (object, spec->name, &val);
+    if (gst_value_compare (g_param_spec_get_default_value (spec),
+            &val) == GST_VALUE_EQUAL) {
+      GST_INFO ("Ignoring %s as it is using the default value", spec->name);
+      goto next;
+    }
+
     if (spec->value_type == GST_TYPE_CAPS) {
-      GstCaps *caps;
       gchar *caps_str;
+      const GstCaps *caps = gst_value_get_caps (&val);
 
-      g_object_get (object, spec->name, &caps, NULL);
       caps_str = gst_caps_to_string (caps);
       gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL);
-      if (caps)
-        gst_caps_unref (caps);
       g_free (caps_str);
-    } else if (_can_serialize_spec (spec)) {
-      _init_value_from_spec_for_serialization (&val, spec);
-      g_object_get_property (object, spec->name, &val);
-      gst_structure_set_value (structure, spec->name, &val);
-      g_value_unset (&val);
+      goto next;
     }
+
+    gst_structure_set_value (structure, spec->name, &val);
+
+  next:
+    g_value_unset (&val);
   }
   g_free (pspecs);
 
@@ -948,35 +1167,207 @@ _serialize_properties (GObject * object, const gchar * fieldname, ...)
   }
 
   ret = gst_structure_to_string (structure);
+  if (ret_n_props)
+    *ret_n_props = gst_structure_n_fields (structure);
   gst_structure_free (structure);
 
   return ret;
 }
 
-static inline void
-_save_assets (GESXmlFormatter * self, GString * str, GESProject * project)
+static void
+project_loaded_cb (GESProject * project, GESTimeline * timeline,
+    SubprojectData * data)
+{
+  g_main_loop_quit (data->ml);
+}
+
+static void
+error_loading_asset_cb (GESProject * project, GError * err,
+    const gchar * unused_id, GType extractable_type, SubprojectData * data)
+{
+  data->error = g_error_copy (err);
+  g_main_loop_quit (data->ml);
+}
+
+static gboolean
+_save_subproject (GESXmlFormatter * self, GString * str, GESProject * project,
+    GESAsset * subproject, GError ** error, guint depth)
+{
+  GString *substr;
+  GESTimeline *timeline;
+  gchar *properties, *metas;
+  GESXmlFormatterPrivate *priv = self->priv;
+  GMainContext *context = g_main_context_get_thread_default ();
+  const gchar *id = ges_asset_get_id (subproject);
+  SubprojectData data = { 0, };
+
+  if (!g_strcmp0 (ges_asset_get_id (GES_ASSET (project)), id)) {
+    g_set_error (error, G_MARKUP_ERROR,
+        G_MARKUP_ERROR_INVALID_CONTENT,
+        "Project %s trying to recurse into itself", id);
+    return FALSE;
+  }
+
+  G_LOCK (uri_subprojects_map_lock);
+  g_hash_table_insert (priv->subprojects_map, g_strdup (id), g_strdup (id));
+  G_UNLOCK (uri_subprojects_map_lock);
+  timeline = GES_TIMELINE (ges_asset_extract (subproject, error));
+  if (!timeline) {
+    return FALSE;
+  }
+
+  if (!context)
+    context = g_main_context_default ();
+
+  data.ml = g_main_loop_new (context, TRUE);
+  g_signal_connect (subproject, "loaded", (GCallback) project_loaded_cb, &data);
+  g_signal_connect (subproject, "error-loading-asset",
+      (GCallback) error_loading_asset_cb, &data);
+  g_main_loop_run (data.ml);
+
+  g_signal_handlers_disconnect_by_func (subproject, project_loaded_cb, &data);
+  g_signal_handlers_disconnect_by_func (subproject, error_loading_asset_cb,
+      &data);
+  if (data.error) {
+    g_propagate_error (error, data.error);
+    return FALSE;
+  }
+
+  subproject = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
+  substr = g_string_new (NULL);
+  properties = _serialize_properties (G_OBJECT (subproject), NULL, NULL);
+  metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (subproject));
+  append_escaped (str,
+      g_markup_printf_escaped
+      ("      <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s'>\n",
+          ges_asset_get_id (subproject),
+          g_type_name (ges_asset_get_extractable_type (subproject)), properties,
+          metas), depth);
+  self->priv->min_version = MAX (self->priv->min_version, 6);
+
+  depth += 4;
+  GST_DEBUG_OBJECT (self, "Saving subproject %s (depth: %d)",
+      ges_asset_get_id (subproject), depth / 4);
+  if (!_save_project (GES_FORMATTER (self), substr, GES_PROJECT (subproject),
+          timeline, error, depth)) {
+    g_string_free (substr, TRUE);
+    g_object_unref (subproject);
+    goto err;
+  }
+  GST_DEBUG_OBJECT (self, "DONE Saving subproject %s",
+      ges_asset_get_id (subproject));
+  depth -= 4;
+
+  g_string_append (str, substr->str);
+  g_string_free (substr, TRUE);
+  string_append_with_depth (str, "      </asset>\n", depth);
+
+err:
+  g_object_unref (subproject);
+
+  return TRUE;
+}
+
+static gint
+sort_assets (GESAsset * a, GESAsset * b)
+{
+  if (GES_IS_PROJECT (a))
+    return -1;
+
+  if (GES_IS_PROJECT (b))
+    return 1;
+
+  return 0;
+}
+
+static void
+_serialize_streams (GESXmlFormatter * self, GString * str,
+    GESUriClipAsset * asset, GError ** error, guint depth)
 {
-  char *properties, *metas;
+  const GList *tmp, *streams = ges_uri_clip_asset_get_stream_assets (asset);
+
+  for (tmp = streams; tmp; tmp = tmp->next) {
+    gchar *properties, *metas, *capsstr;
+    const gchar *id = ges_asset_get_id (tmp->data);
+    GstDiscovererStreamInfo *sinfo =
+        ges_uri_source_asset_get_stream_info (tmp->data);
+    GstCaps *caps = gst_discoverer_stream_info_get_caps (sinfo);
+
+    properties = _serialize_properties (tmp->data, NULL, NULL);
+    metas = ges_meta_container_metas_to_string (tmp->data);
+    capsstr = gst_caps_to_string (caps);
+
+    append_escaped (str,
+        g_markup_printf_escaped
+        ("        <stream-info id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' caps='%s'/>\n",
+            id, g_type_name (ges_asset_get_extractable_type (tmp->data)),
+            properties, metas, capsstr), depth);
+    self->priv->min_version = MAX (self->priv->min_version, 6);
+    g_free (metas);
+    g_free (properties);
+    g_free (capsstr);
+    gst_caps_unref (caps);
+  }
+
+}
+
+static inline gboolean
+_save_assets (GESXmlFormatter * self, GString * str, GESProject * project,
+    GError ** error, guint depth)
+{
+  gchar *properties, *metas;
   GESAsset *asset, *proxy;
   GList *assets, *tmp;
+  const gchar *id;
+  GESXmlFormatterPrivate *priv = self->priv;
 
   assets = ges_project_list_assets (project, GES_TYPE_EXTRACTABLE);
-  for (tmp = assets; tmp; tmp = tmp->next) {
+  for (tmp = g_list_sort (assets, (GCompareFunc) sort_assets); tmp;
+      tmp = tmp->next) {
     asset = GES_ASSET (tmp->data);
-    properties = _serialize_properties (G_OBJECT (asset), NULL);
+    id = ges_asset_get_id (asset);
+
+    if (GES_IS_PROJECT (asset)) {
+      if (!_save_subproject (self, str, project, asset, error, depth))
+        return FALSE;
+
+      continue;
+    }
+
+    if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) {
+      G_LOCK (uri_subprojects_map_lock);
+      if (g_hash_table_contains (priv->subprojects_map, id)) {
+        id = g_hash_table_lookup (priv->subprojects_map, id);
+
+        GST_DEBUG_OBJECT (self, "Using subproject %s", id);
+      }
+      G_UNLOCK (uri_subprojects_map_lock);
+    }
+
+    properties = _serialize_properties (G_OBJECT (asset), NULL, NULL);
     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset));
     append_escaped (str,
         g_markup_printf_escaped
         ("      <asset id='%s' extractable-type-name='%s' properties='%s' metadatas='%s' ",
-            ges_asset_get_id (asset),
-            g_type_name (ges_asset_get_extractable_type (asset)), properties,
-            metas));
+            id, g_type_name (ges_asset_get_extractable_type (asset)),
+            properties, metas), depth);
 
     /*TODO Save the whole list of proxies */
     proxy = ges_asset_get_proxy (asset);
     if (proxy) {
+      const gchar *proxy_id = ges_asset_get_id (proxy);
+
+      if (ges_asset_get_extractable_type (asset) == GES_TYPE_URI_CLIP) {
+        G_LOCK (uri_subprojects_map_lock);
+        if (g_hash_table_contains (priv->subprojects_map, proxy_id)) {
+          proxy_id = g_hash_table_lookup (priv->subprojects_map, proxy_id);
+
+          GST_DEBUG_OBJECT (self, "Using subproject %s", id);
+        }
+        G_UNLOCK (uri_subprojects_map_lock);
+      }
       append_escaped (str, g_markup_printf_escaped (" proxy-id='%s' ",
-              ges_asset_get_id (proxy)));
+              proxy_id), depth);
 
       if (!g_list_find (assets, proxy)) {
         assets = g_list_append (assets, gst_object_ref (proxy));
@@ -987,15 +1378,25 @@ _save_assets (GESXmlFormatter * self, GString * str, GESProject * project)
 
       self->priv->min_version = MAX (self->priv->min_version, 3);
     }
-    g_string_append (str, "/>\n");
+    g_string_append (str, ">\n");
+
+    if (GES_IS_URI_CLIP_ASSET (asset)) {
+      _serialize_streams (self, str, GES_URI_CLIP_ASSET (asset), error, depth);
+    }
+
+    string_append_with_depth (str, "      </asset>\n", depth);
     g_free (properties);
     g_free (metas);
   }
+
   g_list_free_full (assets, gst_object_unref);
+
+  return TRUE;
 }
 
 static inline void
-_save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
+_save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
+    guint depth)
 {
   gchar *strtmp, *metas;
   GESTrack *track;
@@ -1007,13 +1408,13 @@ _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
   tracks = ges_timeline_get_tracks (timeline);
   for (tmp = tracks; tmp; tmp = tmp->next) {
     track = GES_TRACK (tmp->data);
-    properties = _serialize_properties (G_OBJECT (track), NULL);
+    properties = _serialize_properties (G_OBJECT (track), NULL, "caps", NULL);
     strtmp = gst_caps_to_string (ges_track_get_caps (track));
     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track));
     append_escaped (str,
         g_markup_printf_escaped
         ("      <track caps='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'/>\n",
-            strtmp, track->type, nb_tracks++, properties, metas));
+            strtmp, track->type, nb_tracks++, properties, metas), depth);
     g_free (strtmp);
     g_free (metas);
     g_free (properties);
@@ -1022,7 +1423,8 @@ _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
 }
 
 static inline void
-_save_children_properties (GString * str, GESTimelineElement * element)
+_save_children_properties (GString * str, GESTimelineElement * element,
+    guint depth)
 {
   GstStructure *structure;
   GParamSpec **pspecs, *spec;
@@ -1036,7 +1438,7 @@ _save_children_properties (GString * str, GESTimelineElement * element)
     GValue val = { 0 };
     spec = pspecs[i];
 
-    if (_can_serialize_spec (spec)) {
+    if (ges_util_can_serialize_spec (spec)) {
       gchar *spec_name =
           g_strdup_printf ("%s::%s", g_type_name (spec->owner_type),
           spec->name);
@@ -1054,14 +1456,15 @@ _save_children_properties (GString * str, GESTimelineElement * element)
 
   struct_str = gst_structure_to_string (structure);
   append_escaped (str,
-      g_markup_printf_escaped (" children-properties='%s'", struct_str));
+      g_markup_printf_escaped (" children-properties='%s'", struct_str), 0);
   gst_structure_free (structure);
   g_free (struct_str);
 }
 
 /* TODO : Use this function for every track element with controllable properties */
 static inline void
-_save_keyframes (GString * str, GESTrackElement * trackelement, gint index)
+_save_keyframes (GString * str, GESTrackElement * trackelement, gint index,
+    guint depth)
 {
   GHashTable *bindings_hashtable;
   GHashTableIter iter;
@@ -1091,12 +1494,14 @@ _save_keyframes (GString * str, GESTrackElement * trackelement, gint index)
         append_escaped (str,
             g_markup_printf_escaped
             ("            <binding type='%s' source_type='interpolation' property='%s'",
-                absolute ? "direct-absolute" : "direct", (gchar *) key));
+                absolute ? "direct-absolute" : "direct", (gchar *) key), depth);
 
         g_object_get (source, "mode", &mode, NULL);
-        append_escaped (str, g_markup_printf_escaped (" mode='%d'", mode));
-        append_escaped (str, g_markup_printf_escaped (" track_id='%d'", index));
-        append_escaped (str, g_markup_printf_escaped (" values ='"));
+        append_escaped (str, g_markup_printf_escaped (" mode='%d'", mode),
+            depth);
+        append_escaped (str, g_markup_printf_escaped (" track_id='%d'", index),
+            depth);
+        append_escaped (str, g_markup_printf_escaped (" values ='"), depth);
         timed_values =
             gst_timed_value_control_source_get_all
             (GST_TIMED_VALUE_CONTROL_SOURCE (source));
@@ -1107,11 +1512,14 @@ _save_keyframes (GString * str, GESTrackElement * trackelement, gint index)
           value = (GstTimedValue *) tmp->data;
           append_escaped (str, g_markup_printf_escaped (" %" G_GUINT64_FORMAT
                   ":%s ", value->timestamp, g_ascii_dtostr (strbuf,
-                      G_ASCII_DTOSTR_BUF_SIZE, value->value)));
+                      G_ASCII_DTOSTR_BUF_SIZE, value->value)), depth);
         }
-        append_escaped (str, g_markup_printf_escaped ("'/>\n"));
+        g_list_free (timed_values);
+        append_escaped (str, g_markup_printf_escaped ("'/>\n"), depth);
       } else
         GST_DEBUG ("control source not in [interpolation]");
+
+      gst_object_unref (source);
     } else
       GST_DEBUG ("Binding type not in [direct, direct-absolute]");
   }
@@ -1119,7 +1527,7 @@ _save_keyframes (GString * str, GESTrackElement * trackelement, gint index)
 
 static inline void
 _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
-    GESTimeline * timeline)
+    GESTimeline * timeline, guint depth)
 {
   GESTrack *tck;
   GList *tmp, *tracks;
@@ -1151,9 +1559,8 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
   }
   g_list_free_full (tracks, gst_object_unref);
 
-  properties = _serialize_properties (G_OBJECT (trackelement), "start",
-      "in-point", "duration", "locked", "max-duration", "name", "priority",
-      NULL);
+  properties = _serialize_properties (G_OBJECT (trackelement), NULL, "start",
+      "duration", "locked", "name", "priority", NULL);
   metas =
       ges_meta_container_metas_to_string (GES_META_CONTAINER (trackelement));
   extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (trackelement));
@@ -1162,21 +1569,100 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
           " type-name='%s' track-type='%i' track-id='%i' properties='%s' metadatas='%s'",
           extractable_id, clip_id,
           g_type_name (G_OBJECT_TYPE (trackelement)), tck->type, track_id,
-          properties, metas));
+          properties, metas), depth);
   g_free (extractable_id);
   g_free (properties);
   g_free (metas);
 
-  _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement));
-  append_escaped (str, g_markup_printf_escaped (">\n"));
+  _save_children_properties (str, GES_TIMELINE_ELEMENT (trackelement), depth);
+  append_escaped (str, g_markup_printf_escaped (">\n"), depth);
 
-  _save_keyframes (str, trackelement, -1);
+  _save_keyframes (str, trackelement, -1, depth);
 
-  append_escaped (str, g_markup_printf_escaped ("          </effect>\n"));
+  append_escaped (str, g_markup_printf_escaped ("          </effect>\n"),
+      depth);
 }
 
 static inline void
-_save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
+_save_layer_track_activness (GESXmlFormatter * self, GESLayer * layer,
+    GString * str, GESTimeline * timeline, guint depth)
+{
+  guint nb_tracks = 0, i;
+  GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
+  GArray *deactivated_tracks = g_array_new (TRUE, FALSE, sizeof (gint32));
+
+  for (tmp = tracks; tmp; tmp = tmp->next, nb_tracks++) {
+    if (!ges_layer_get_active_for_track (layer, tmp->data))
+      g_array_append_val (deactivated_tracks, nb_tracks);
+  }
+
+  if (!deactivated_tracks->len) {
+    g_string_append (str, ">\n");
+    goto done;
+  }
+
+  self->priv->min_version = MAX (self->priv->min_version, 7);
+  g_string_append (str, " deactivated-tracks='");
+  for (i = 0; i < deactivated_tracks->len; i++)
+    g_string_append_printf (str, "%d ", g_array_index (deactivated_tracks, gint,
+            i));
+  g_string_append (str, "'>\n");
+
+done:
+  g_array_free (deactivated_tracks, TRUE);
+  g_list_free_full (tracks, gst_object_unref);
+}
+
+static void
+_save_source (GESXmlFormatter * self, GString * str,
+    GESTimelineElement * element, GESTimeline * timeline, GList * tracks,
+    guint depth)
+{
+  gint index, n_props;
+  gboolean serialize;
+  gchar *properties, *metas;
+
+  if (!GES_IS_SOURCE (element))
+    return;
+
+  g_object_get (element, "serialize", &serialize, NULL);
+  if (!serialize) {
+    GST_DEBUG_OBJECT (element, "Should not be serialized");
+    return;
+  }
+
+  index =
+      g_list_index (tracks,
+      ges_track_element_get_track (GES_TRACK_ELEMENT (element)));
+  append_escaped (str,
+      g_markup_printf_escaped
+      ("          <source track-id='%i' ", index), depth);
+
+  properties = _serialize_properties (G_OBJECT (element), &n_props,
+      "in-point", "priority", "start", "duration", "track", "track-type"
+      "uri", "name", "max-duration", NULL);
+
+  /* Try as possible to allow older versions of GES to load the files */
+  if (n_props) {
+    self->priv->min_version = MAX (self->priv->min_version, 7);
+    g_string_append_printf (str, "properties='%s' ", properties);
+  }
+  g_free (properties);
+
+  metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (element));
+  g_string_append_printf (str, "metadatas='%s' ", metas);
+  g_free (metas);
+
+  _save_children_properties (str, element, depth);
+  append_escaped (str, g_markup_printf_escaped (">\n"), depth);
+  _save_keyframes (str, GES_TRACK_ELEMENT (element), index, depth);
+  append_escaped (str, g_markup_printf_escaped ("          </source>\n"),
+      depth);
+}
+
+static inline void
+_save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
+    guint depth)
 {
   gchar *properties, *metas;
   GESLayer *layer;
@@ -1189,15 +1675,18 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
     layer = GES_LAYER (tmplayer->data);
 
     priority = ges_layer_get_priority (layer);
-    properties = _serialize_properties (G_OBJECT (layer), "priority", NULL);
+    properties =
+        _serialize_properties (G_OBJECT (layer), NULL, "priority", NULL);
     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer));
     append_escaped (str,
         g_markup_printf_escaped
-        ("      <layer priority='%i' properties='%s' metadatas='%s'>\n",
-            priority, properties, metas));
+        ("      <layer priority='%i' properties='%s' metadatas='%s'",
+            priority, properties, metas), depth);
     g_free (properties);
     g_free (metas);
 
+    _save_layer_track_activness (self, layer, str, timeline, depth);
+
     clips = ges_layer_get_clips (layer);
     for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
       GList *effects, *tmpeffect;
@@ -1216,10 +1705,18 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
 
       /* We escape all mandatrorry properties that are handled sparetely
        * and vtype for StandarTransition as it is the asset ID */
-      properties = _serialize_properties (G_OBJECT (clip),
+      properties = _serialize_properties (G_OBJECT (clip), NULL,
           "supported-formats", "rate", "in-point", "start", "duration",
           "max-duration", "priority", "vtype", "uri", NULL);
       extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip));
+      if (GES_IS_URI_CLIP (clip)) {
+        G_LOCK (uri_subprojects_map_lock);
+        if (g_hash_table_contains (priv->subprojects_map, extractable_id))
+          extractable_id =
+              g_strdup (g_hash_table_lookup (priv->subprojects_map,
+                  extractable_id));
+        G_UNLOCK (uri_subprojects_map_lock);
+      }
       metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (clip));
       append_escaped (str,
           g_markup_printf_escaped ("        <clip id='%i' asset-id='%s'"
@@ -1229,11 +1726,11 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
               priv->nbelements, extractable_id,
               g_type_name (G_OBJECT_TYPE (clip)), priority,
               ges_clip_get_supported_formats (clip), _START (clip),
-              _DURATION (clip), _INPOINT (clip), 0, properties, metas));
+              _DURATION (clip), _INPOINT (clip), 0, properties, metas), depth);
       g_free (metas);
 
       if (GES_IS_TRANSITION_CLIP (clip)) {
-        _save_children_properties (str, GES_TIMELINE_ELEMENT (clip));
+        _save_children_properties (str, GES_TIMELINE_ELEMENT (clip), depth);
         self->priv->min_version = MAX (self->priv->min_version, 4);
       }
       g_string_append (str, ">\n");
@@ -1251,50 +1748,30 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
       effects = ges_clip_get_top_effects (clip);
       for (tmpeffect = effects; tmpeffect; tmpeffect = tmpeffect->next) {
         _save_effect (str, priv->nbelements,
-            GES_TRACK_ELEMENT (tmpeffect->data), timeline);
+            GES_TRACK_ELEMENT (tmpeffect->data), timeline, depth);
       }
       g_list_free (effects);
       tracks = ges_timeline_get_tracks (timeline);
 
       for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement;
           tmptrackelement = tmptrackelement->next) {
-        gint index;
-        gboolean serialize;
-
-        if (!GES_IS_SOURCE (tmptrackelement->data))
-          continue;
-
-        g_object_get (tmptrackelement->data, "serialize", &serialize, NULL);
-        if (!serialize) {
-          GST_DEBUG_OBJECT (tmptrackelement->data, "Should not be serialized");
-          continue;
-        }
-
-        index =
-            g_list_index (tracks,
-            ges_track_element_get_track (tmptrackelement->data));
-        append_escaped (str,
-            g_markup_printf_escaped ("          <source track-id='%i'", index));
-        _save_children_properties (str, tmptrackelement->data);
-        append_escaped (str, g_markup_printf_escaped (">\n"));
-        _save_keyframes (str, tmptrackelement->data, index);
-        append_escaped (str, g_markup_printf_escaped ("          </source>\n"));
+        _save_source (self, str, tmptrackelement->data, timeline, tracks,
+            depth);
       }
-
       g_list_free_full (tracks, gst_object_unref);
 
-      g_string_append (str, "        </clip>\n");
+      string_append_with_depth (str, "        </clip>\n", depth);
 
       priv->nbelements++;
     }
     g_list_free_full (clips, (GDestroyNotify) gst_object_unref);
-    g_string_append (str, "      </layer>\n");
+    string_append_with_depth (str, "      </layer>\n", depth);
   }
 }
 
 static void
 _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
-    GESGroup * group)
+    GESGroup * group, guint depth)
 {
   GList *tmp;
   gboolean serialize;
@@ -1318,15 +1795,16 @@ _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
   for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
     if (GES_IS_GROUP (tmp->data)) {
       _save_group (self, str, seen_groups,
-          GES_GROUP (GES_TIMELINE_ELEMENT (tmp->data)));
+          GES_GROUP (GES_TIMELINE_ELEMENT (tmp->data)), depth);
     }
   }
 
-  properties = _serialize_properties (G_OBJECT (group), NULL);
+  properties = _serialize_properties (G_OBJECT (group), NULL, NULL);
 
   metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group));
   self->priv->min_version = MAX (self->priv->min_version, 5);
 
+  string_add_indents (str, depth, FALSE);
   g_string_append_printf (str,
       "        <group id='%d' properties='%s' metadatas='%s'>\n",
       self->priv->nbelements, properties, metadatas);
@@ -1340,32 +1818,36 @@ _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
     gint id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->element_id,
             tmp->data));
 
+    string_add_indents (str, depth, FALSE);
     g_string_append_printf (str, "          <child id='%d' name='%s'/>\n", id,
         GES_TIMELINE_ELEMENT_NAME (tmp->data));
   }
-  g_string_append (str, "        </group>\n");
+  string_append_with_depth (str, "        </group>\n", depth);
 }
 
 static void
-_save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
+_save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
+    guint depth)
 {
   GList *tmp;
   GList *seen_groups = NULL;
 
-  g_string_append (str, "      <groups>\n");
+  string_append_with_depth (str, "      <groups>\n", depth);
   for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) {
-    _save_group (self, str, &seen_groups, tmp->data);
+    _save_group (self, str, &seen_groups, tmp->data, depth);
   }
   g_list_free (seen_groups);
-  g_string_append (str, "      </groups>\n");
+  string_append_with_depth (str, "      </groups>\n", depth);
 }
 
 static inline void
-_save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
+_save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
+    guint depth)
 {
   gchar *properties = NULL, *metas = NULL;
 
-  properties = _serialize_properties (G_OBJECT (timeline), "update", "name",
+  properties =
+      _serialize_properties (G_OBJECT (timeline), NULL, "update", "name",
       "async-handling", "message-forward", NULL);
 
   ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration",
@@ -1373,13 +1855,14 @@ _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline));
   append_escaped (str,
       g_markup_printf_escaped
-      ("    <timeline properties='%s' metadatas='%s'>\n", properties, metas));
+      ("    <timeline properties='%s' metadatas='%s'>\n", properties, metas),
+      depth);
 
-  _save_tracks (self, str, timeline);
-  _save_layers (self, str, timeline);
-  _save_groups (self, str, timeline);
+  _save_tracks (self, str, timeline, depth);
+  _save_layers (self, str, timeline, depth);
+  _save_groups (self, str, timeline, depth);
 
-  g_string_append (str, "    </timeline>\n");
+  string_append_with_depth (str, "    </timeline>\n", depth);
 
   g_free (properties);
   g_free (metas);
@@ -1387,10 +1870,12 @@ _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
 
 static void
 _save_stream_profiles (GESXmlFormatter * self, GString * str,
-    GstEncodingProfile * sprof, const gchar * profilename, guint id)
+    GstEncodingProfile * sprof, const gchar * profilename, guint id,
+    guint depth)
 {
   gchar *tmpc;
   GstCaps *tmpcaps;
+  GstStructure *properties;
   const gchar *preset, *preset_name, *name, *description;
 
   append_escaped (str,
@@ -1398,10 +1883,10 @@ _save_stream_profiles (GESXmlFormatter * self, GString * str,
       ("        <stream-profile parent='%s' id='%d' type='%s' "
           "presence='%d' ", profilename, id,
           gst_encoding_profile_get_type_nick (sprof),
-          gst_encoding_profile_get_presence (sprof)));
+          gst_encoding_profile_get_presence (sprof)), depth);
 
   if (!gst_encoding_profile_is_enabled (sprof)) {
-    append_escaped (str, g_strdup ("enabled='0' "));
+    append_escaped (str, g_strdup ("enabled='0' "), depth);
 
     self->priv->min_version = MAX (self->priv->min_version, 2);
   }
@@ -1409,50 +1894,46 @@ _save_stream_profiles (GESXmlFormatter * self, GString * str,
   tmpcaps = gst_encoding_profile_get_format (sprof);
   if (tmpcaps) {
     tmpc = gst_caps_to_string (tmpcaps);
-    append_escaped (str, g_markup_printf_escaped ("format='%s' ", tmpc));
+    append_escaped (str, g_markup_printf_escaped ("format='%s' ", tmpc), depth);
     gst_caps_unref (tmpcaps);
     g_free (tmpc);
   }
 
   name = gst_encoding_profile_get_name (sprof);
   if (name)
-    append_escaped (str, g_markup_printf_escaped ("name='%s' ", name));
+    append_escaped (str, g_markup_printf_escaped ("name='%s' ", name), depth);
 
   description = gst_encoding_profile_get_description (sprof);
   if (description)
     append_escaped (str, g_markup_printf_escaped ("description='%s' ",
-            description));
+            description), depth);
 
   preset = gst_encoding_profile_get_preset (sprof);
   if (preset) {
-    GstElement *encoder;
-
-    append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset));
+    append_escaped (str, g_markup_printf_escaped ("preset='%s' ", preset),
+        depth);
+  }
 
-    encoder = get_element_for_encoding_profile (sprof,
-        GST_ELEMENT_FACTORY_TYPE_ENCODER);
-    if (encoder) {
-      if (GST_IS_PRESET (encoder) &&
-          gst_preset_load_preset (GST_PRESET (encoder), preset)) {
+  properties = gst_encoding_profile_get_element_properties (sprof);
+  if (properties) {
+    gchar *props_str = gst_structure_to_string (properties);
 
-        gchar *settings = _serialize_properties (G_OBJECT (encoder), NULL);
-        append_escaped (str,
-            g_markup_printf_escaped ("preset-properties='%s' ", settings));
-        g_free (settings);
-      }
-      gst_object_unref (encoder);
-    }
+    append_escaped (str,
+        g_markup_printf_escaped ("preset-properties='%s' ", props_str), depth);
+    g_free (props_str);
+    gst_structure_free (properties);
   }
 
   preset_name = gst_encoding_profile_get_preset_name (sprof);
   if (preset_name)
     append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
-            preset_name));
+            preset_name), depth);
 
   tmpcaps = gst_encoding_profile_get_restriction (sprof);
   if (tmpcaps) {
     tmpc = gst_caps_to_string (tmpcaps);
-    append_escaped (str, g_markup_printf_escaped ("restriction='%s' ", tmpc));
+    append_escaped (str, g_markup_printf_escaped ("restriction='%s' ", tmpc),
+        depth);
     gst_caps_unref (tmpcaps);
     g_free (tmpc);
   }
@@ -1463,7 +1944,7 @@ _save_stream_profiles (GESXmlFormatter * self, GString * str,
     append_escaped (str,
         g_markup_printf_escaped ("pass='%d' variableframerate='%i' ",
             gst_encoding_video_profile_get_pass (vp),
-            gst_encoding_video_profile_get_variableframerate (vp)));
+            gst_encoding_video_profile_get_variableframerate (vp)), depth);
   }
 
   g_string_append (str, "/>\n");
@@ -1471,9 +1952,10 @@ _save_stream_profiles (GESXmlFormatter * self, GString * str,
 
 static inline void
 _save_encoding_profiles (GESXmlFormatter * self, GString * str,
-    GESProject * project)
+    GESProject * project, guint depth)
 {
   GstCaps *profformat;
+  GstStructure *properties;
   const gchar *profname, *profdesc, *profpreset, *proftype, *profpresetname;
 
   const GList *tmp;
@@ -1492,43 +1974,33 @@ _save_encoding_profiles (GESXmlFormatter * self, GString * str,
     append_escaped (str,
         g_markup_printf_escaped
         ("      <encoding-profile name='%s' description='%s' type='%s' ",
-            profname, profdesc, proftype));
+            profname, profdesc, proftype), depth);
 
     if (profpreset) {
-      GstElement *element;
-
       append_escaped (str, g_markup_printf_escaped ("preset='%s' ",
-              profpreset));
-
-      if (GST_IS_ENCODING_CONTAINER_PROFILE (prof)) {
-        element = get_element_for_encoding_profile (prof,
-            GST_ELEMENT_FACTORY_TYPE_MUXER);
-      } else {
-        element = get_element_for_encoding_profile (prof,
-            GST_ELEMENT_FACTORY_TYPE_ENCODER);
-      }
+              profpreset), depth);
+    }
 
-      if (element) {
-        if (GST_IS_PRESET (element) &&
-            gst_preset_load_preset (GST_PRESET (element), profpreset)) {
-          gchar *settings = _serialize_properties (G_OBJECT (element), NULL);
-          append_escaped (str,
-              g_markup_printf_escaped ("preset-properties='%s' ", settings));
-          g_free (settings);
-        }
-        gst_object_unref (element);
-      }
+    properties = gst_encoding_profile_get_element_properties (prof);
+    if (properties) {
+      gchar *props_str = gst_structure_to_string (properties);
 
+      append_escaped (str,
+          g_markup_printf_escaped ("preset-properties='%s' ", props_str),
+          depth);
+      g_free (props_str);
+      gst_structure_free (properties);
     }
 
     if (profpresetname)
       append_escaped (str, g_markup_printf_escaped ("preset-name='%s' ",
-              profpresetname));
+              profpresetname), depth);
 
     profformat = gst_encoding_profile_get_format (prof);
     if (profformat) {
       gchar *format = gst_caps_to_string (profformat);
-      append_escaped (str, g_markup_printf_escaped ("format='%s' ", format));
+      append_escaped (str, g_markup_printf_escaped ("format='%s' ", format),
+          depth);
       g_free (format);
       gst_caps_unref (profformat);
     }
@@ -1544,11 +2016,11 @@ _save_encoding_profiles (GESXmlFormatter * self, GString * str,
       for (tmp2 = gst_encoding_container_profile_get_profiles (container_prof);
           tmp2; tmp2 = tmp2->next, i++) {
         GstEncodingProfile *sprof = (GstEncodingProfile *) tmp2->data;
-        _save_stream_profiles (self, str, sprof, profname, i);
+        _save_stream_profiles (self, str, sprof, profname, i, depth);
       }
     }
     append_escaped (str,
-        g_markup_printf_escaped ("      </encoding-profile>\n"));
+        g_markup_printf_escaped ("      </encoding-profile>\n"), depth);
   }
   g_list_free (profiles);
 }
@@ -1558,41 +2030,51 @@ _save (GESFormatter * formatter, GESTimeline * timeline, GError ** error)
 {
   GString *str;
   GESProject *project;
-
-  gchar *projstr = NULL, *version;
-  gchar *properties = NULL, *metas = NULL;
-  GESXmlFormatter *self = GES_XML_FORMATTER (formatter);
-  GESXmlFormatterPrivate *priv;
-
-
-  priv = _GET_PRIV (formatter);
+  GESXmlFormatterPrivate *priv = _GET_PRIV (formatter);
 
   priv->min_version = 1;
   project = formatter->project;
   str = priv->str = g_string_new (NULL);
 
-  properties = _serialize_properties (G_OBJECT (project), NULL);
+  return _save_project (formatter, str, project, timeline, error, 0);
+}
+
+static GString *
+_save_project (GESFormatter * formatter, GString * str, GESProject * project,
+    GESTimeline * timeline, GError ** error, guint depth)
+{
+  gchar *projstr = NULL, *version;
+  gchar *properties = NULL, *metas = NULL;
+  GESXmlFormatter *self = GES_XML_FORMATTER (formatter);
+  GESXmlFormatterPrivate *priv = _GET_PRIV (formatter);
+
+  properties = _serialize_properties (G_OBJECT (project), NULL, NULL);
   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project));
   append_escaped (str,
       g_markup_printf_escaped ("  <project properties='%s' metadatas='%s'>\n",
-          properties, metas));
+          properties, metas), depth);
   g_free (properties);
   g_free (metas);
 
-  g_string_append (str, "    <encoding-profiles>\n");
-  _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project);
-  g_string_append (str, "    </encoding-profiles>\n");
+  string_append_with_depth (str, "    <encoding-profiles>\n", depth);
+  _save_encoding_profiles (GES_XML_FORMATTER (formatter), str, project, depth);
+  string_append_with_depth (str, "    </encoding-profiles>\n", depth);
 
-  g_string_append (str, "    <ressources>\n");
-  _save_assets (self, str, project);
-  g_string_append (str, "    </ressources>\n");
+  string_append_with_depth (str, "    <ressources>\n", depth);
+  if (!_save_assets (self, str, project, error, depth)) {
+    g_string_free (str, TRUE);
+    return NULL;
+  }
+  string_append_with_depth (str, "    </ressources>\n", depth);
 
-  _save_timeline (self, str, timeline);
-  g_string_append (str, "</project>\n</ges>");
+  _save_timeline (self, str, timeline, depth);
+  string_append_with_depth (str, "  </project>\n", depth);
+  string_append_with_depth (str, "</ges>\n", depth);
 
   projstr = g_strdup_printf ("<ges version='%i.%i'>\n", API_VERSION,
       priv->min_version);
   g_string_prepend (str, projstr);
+  string_add_indents (str, depth, TRUE);
   g_free (projstr);
 
   ges_meta_container_set_int (GES_META_CONTAINER (project),
@@ -1611,6 +2093,66 @@ _save (GESFormatter * formatter, GESTimeline * timeline, GError ** error)
   return str;
 }
 
+static void
+_setup_subprojects_map (GESXmlFormatterPrivate * priv, const gchar * uri)
+{
+  GHashTable *subprojects_map;
+
+  G_LOCK (uri_subprojects_map_lock);
+  if (!uri_subprojects_map)
+    uri_subprojects_map =
+        g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+        (GDestroyNotify) g_hash_table_unref);
+
+  subprojects_map = g_hash_table_lookup (uri_subprojects_map, uri);
+  if (!subprojects_map) {
+    subprojects_map =
+        g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+    g_hash_table_insert (uri_subprojects_map, g_strdup (uri), subprojects_map);
+  }
+  priv->subprojects_map = subprojects_map;
+  G_UNLOCK (uri_subprojects_map_lock);
+
+}
+
+void
+ges_xml_formatter_deinit (void)
+{
+  GST_DEBUG ("Deinit");
+  G_LOCK (uri_subprojects_map_lock);
+  if (uri_subprojects_map) {
+    g_hash_table_unref (uri_subprojects_map);
+    uri_subprojects_map = NULL;
+  }
+  G_UNLOCK (uri_subprojects_map_lock);
+}
+
+static gboolean
+_save_to_uri (GESFormatter * formatter, GESTimeline * timeline,
+    const gchar * uri, gboolean overwrite, GError ** error)
+{
+  _setup_subprojects_map (_GET_PRIV (formatter), uri);
+  return GES_FORMATTER_CLASS (parent_class)->save_to_uri (formatter, timeline,
+      uri, overwrite, error);
+}
+
+static gboolean
+_can_load_uri (GESFormatter * formatter, const gchar * uri, GError ** error)
+{
+  _setup_subprojects_map (_GET_PRIV (formatter), uri);
+  return GES_FORMATTER_CLASS (parent_class)->can_load_uri (formatter, uri,
+      error);
+}
+
+static gboolean
+_load_from_uri (GESFormatter * formatter, GESTimeline * timeline,
+    const gchar * uri, GError ** error)
+{
+  _setup_subprojects_map (_GET_PRIV (formatter), uri);
+  return GES_FORMATTER_CLASS (parent_class)->load_from_uri (formatter, timeline,
+      uri, error);
+}
+
 /***********************************************
  *                                             *
  *   GObject virtual methods implementation    *
@@ -1655,9 +2197,14 @@ ges_xml_formatter_class_init (GESXmlFormatterClass * self_class)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (self_class);
   GESBaseXmlFormatterClass *basexmlformatter_class;
+  GESFormatterClass *formatter_klass = GES_FORMATTER_CLASS (self_class);
 
   basexmlformatter_class = GES_BASE_XML_FORMATTER_CLASS (self_class);
 
+  formatter_klass->save_to_uri = _save_to_uri;
+  formatter_klass->can_load_uri = _can_load_uri;
+  formatter_klass->load_from_uri = _load_from_uri;
+
   object_class->get_property = _get_property;
   object_class->set_property = _set_property;
   object_class->dispose = _dispose;
@@ -1670,7 +2217,7 @@ ges_xml_formatter_class_init (GESXmlFormatterClass * self_class)
 
   ges_formatter_class_register_metas (GES_FORMATTER_CLASS (self_class),
       "ges", "GStreamer Editing Services project files",
-      "xges", "application/ges", VERSION, GST_RANK_PRIMARY);
+      "xges", "application/xges", VERSION, GST_RANK_PRIMARY);
 
   basexmlformatter_class->save = _save;
 }
index 940148d..a88f355 100644 (file)
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
+#pragma once
 
 #include "ges-base-xml-formatter.h"
 
-#ifndef GES_XML_FORMATTER_H
-#define GES_XML_FORMATTER_H
-
 G_BEGIN_DECLS
 #define GES_TYPE_XML_FORMATTER (ges_xml_formatter_get_type ())
-#define GES_XML_FORMATTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_XML_FORMATTER, GESXmlFormatter))
-#define GES_XML_FORMATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_XML_FORMATTER, GESXmlFormatterClass))
-#define GES_IS_XML_FORMATTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_XML_FORMATTER))
-#define GES_IS_XML_FORMATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_XML_FORMATTER))
-#define GES_XML_FORMATTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_XML_FORMATTER, GESXmlFormatterClass))
-typedef struct _GESXmlFormatterPrivate GESXmlFormatterPrivate;
-
-typedef struct
+GES_DECLARE_TYPE(XmlFormatter, xml_formatter, XML_FORMATTER);
+
+struct _GESXmlFormatter
 {
   GESBaseXmlFormatter parent;
 
   GESXmlFormatterPrivate *priv;
 
   gpointer _ges_reserved[GES_PADDING];
-} GESXmlFormatter;
+};
 
-typedef struct
+struct _GESXmlFormatterClass
 {
   GESBaseXmlFormatterClass parent;
 
   gpointer _ges_reserved[GES_PADDING];
-} GESXmlFormatterClass;
-
-GES_API
-GType ges_xml_formatter_get_type (void);
+};
 
 G_END_DECLS
-#endif /* _GES_XML_FORMATTER_H */
index ac89662..d34c4ef 100644 (file)
--- a/ges/ges.c
+++ b/ges/ges.c
  * Boston, MA 02110-1301, USA.
  */
 
-/* TODO
- * Add a deinit function
+/**
+ * SECTION: ges.h
+ * @title: Initialization
+ * @short_description: GStreamer editing services initialization functions
  *
- * Do not forget to
- *  + g_ptr_array_unref (new_paths);
- *  + g_hash_table_unref (tried_uris);
+ * GES needs to be initialized after GStreamer itself. This section
+ * contains the various functions to do so.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -33,6 +34,8 @@
 #include <stdlib.h>
 #include <ges/ges.h>
 #include "ges/gstframepositioner.h"
+#include "ges/ges-smart-adder.h"
+#include "ges/ges-smart-video-mixer.h"
 #include "ges-internal.h"
 
 #ifndef DISABLE_XPTV
@@ -99,6 +102,13 @@ ges_init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
 {
   GESUriClipAssetClass *uriasset_klass = NULL;
   GstElementFactory *nlecomposition_factory = NULL;
+  static GstValueTable gstvtable = {
+    G_TYPE_NONE,
+    (GstValueCompareFunc) NULL,
+    (GstValueSerializeFunc) ges_marker_list_serialize,
+    (GstValueDeserializeFunc) ges_marker_list_deserialize
+  };
+  static gboolean marker_list_registered = FALSE;
 
   if (initialized_thread) {
     GST_DEBUG ("already initialized ges");
@@ -107,8 +117,12 @@ ges_init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
 
   uriasset_klass = g_type_class_ref (GES_TYPE_URI_CLIP_ASSET);
 
+  _init_formatter_assets ();
   if (!_ges_uri_asset_ensure_setup (uriasset_klass)) {
     GST_ERROR ("cannot setup uri asset");
+    if (error)
+      *error = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN,
+          "Cannot initialize URI asset class.");
     goto failed;
   }
 
@@ -123,8 +137,6 @@ ges_init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
   }
   gst_object_unref (nlecomposition_factory);
 
-
-
   /* register clip classes with the system */
 
   g_type_class_ref (GES_TYPE_TEST_CLIP);
@@ -133,21 +145,17 @@ ges_init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
   g_type_class_ref (GES_TYPE_TRANSITION_CLIP);
   g_type_class_ref (GES_TYPE_OVERLAY_CLIP);
   g_type_class_ref (GES_TYPE_OVERLAY_TEXT_CLIP);
+  g_type_class_ref (GES_TYPE_EFFECT_CLIP);
 
   g_type_class_ref (GES_TYPE_GROUP);
 
-  /* register formatter types with the system */
-#ifndef DISABLE_XPTV
-  g_type_class_ref (GES_TYPE_PITIVI_FORMATTER);
-#endif
-  g_type_class_ref (GES_TYPE_COMMAND_LINE_FORMATTER);
-  g_type_class_ref (GES_TYPE_XML_FORMATTER);
-
   /* Register track elements */
   g_type_class_ref (GES_TYPE_EFFECT);
 
   ges_asset_cache_init ();
 
+  gst_element_register (NULL, "gesaudiomixer", 0, GES_TYPE_SMART_ADDER);
+  gst_element_register (NULL, "gescompositor", 0, GES_TYPE_SMART_MIXER);
   gst_element_register (NULL, "framepositioner", 0, GST_TYPE_FRAME_POSITIONNER);
   gst_element_register (NULL, "gespipeline", 0, GES_TYPE_PIPELINE);
 
@@ -155,6 +163,12 @@ ges_init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
   initialized_thread = g_thread_self ();
   g_type_class_unref (uriasset_klass);
 
+  if (!marker_list_registered) {
+    gstvtable.type = GES_TYPE_MARKER_LIST;
+    gst_value_register (&gstvtable);
+    marker_list_registered = TRUE;
+  }
+
   GST_DEBUG ("GStreamer Editing Services initialized");
 
   return TRUE;
@@ -231,21 +245,14 @@ ges_deinit (void)
   g_type_class_unref (g_type_class_peek (GES_TYPE_TRANSITION_CLIP));
   g_type_class_unref (g_type_class_peek (GES_TYPE_OVERLAY_CLIP));
   g_type_class_unref (g_type_class_peek (GES_TYPE_OVERLAY_TEXT_CLIP));
+  g_type_class_unref (g_type_class_peek (GES_TYPE_EFFECT_CLIP));
 
   g_type_class_unref (g_type_class_peek (GES_TYPE_GROUP));
-
-  /* register formatter types with the system */
-#ifndef DISABLE_XPTV
-  g_type_class_unref (g_type_class_peek (GES_TYPE_PITIVI_FORMATTER));
-#endif
-
-  g_type_class_unref (g_type_class_peek (GES_TYPE_COMMAND_LINE_FORMATTER));
-  g_type_class_unref (g_type_class_peek (GES_TYPE_XML_FORMATTER));
-
   /* Register track elements */
   g_type_class_unref (g_type_class_peek (GES_TYPE_EFFECT));
 
   ges_asset_cache_deinit ();
+  ges_xml_formatter_deinit ();
 
   initialized_thread = NULL;
   G_UNLOCK (init_lock);
@@ -261,7 +268,7 @@ parse_goption_arg (const gchar * s_opt,
     const gchar * arg, gpointer data, GError ** err)
 {
   if (g_strcmp0 (s_opt, "--ges-version") == 0) {
-    g_print ("GStreamer Editing Services version %s\n", PACKAGE_VERSION);
+    gst_print ("GStreamer Editing Services version %s\n", PACKAGE_VERSION);
     exit (0);
   } else if (g_strcmp0 (s_opt, "--ges-sample-paths") == 0) {
     ges_add_missing_uri_relocation_uri (arg, FALSE);
index 1a01bf9..0fcded9 100644 (file)
--- a/ges/ges.h
+++ b/ges/ges.h
@@ -18,8 +18,7 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef __GES_H__
-#define __GES_H__
+#pragma once
 
 #include <glib.h>
 #include <gst/gst.h>
@@ -34,6 +33,7 @@
 #include <ges/ges-clip.h>
 #include <ges/ges-pipeline.h>
 #include <ges/ges-source-clip.h>
+#include <ges/ges-time-overlay-clip.h>
 #include <ges/ges-test-clip.h>
 #include <ges/ges-title-clip.h>
 #include <ges/ges-operation-clip.h>
@@ -83,6 +83,7 @@
 #include <ges/ges-audio-track.h>
 #include <ges/ges-video-track.h>
 #include <ges/ges-version.h>
+#include <ges/ges-marker-list.h>
 
 G_BEGIN_DECLS
 
@@ -111,5 +112,3 @@ GES_API
 gboolean ges_is_initialized                  (void);
 
 G_END_DECLS
-
-#endif /* __GES_H__ */
diff --git a/ges/ges.resource b/ges/ges.resource
new file mode 100644 (file)
index 0000000..638794e
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/ges/">
+    <file>python/gesotioformatter.py</file>
+  </gresource>
+</gresources>
index 1c2fbe9..b332d82 100644 (file)
 #include "config.h"
 #endif
 
+#include <math.h>
 #include <gst/gst.h>
 #include <gst/video/video.h>
 
 #include "gstframepositioner.h"
+#include "ges-internal.h"
+
+GST_DEBUG_CATEGORY_STATIC (_framepositioner);
+#undef GST_CAT_DEFAULT
+#define GST_CAT_DEFAULT _framepositioner
 
 /* We  need to define a max number of pixel so we can interpolate them */
 #define MAX_PIXELS 100000
@@ -51,32 +57,208 @@ enum
   PROP_POSY,
   PROP_ZORDER,
   PROP_WIDTH,
-  PROP_HEIGHT
+  PROP_HEIGHT,
+  PROP_OPERATOR,
+  PROP_LAST,
 };
 
+static GParamSpec *properties[PROP_LAST];
+
 static GstStaticPadTemplate gst_frame_positioner_src_template =
 GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("video/x-raw")
+    GST_STATIC_CAPS ("video/x-raw(ANY)")
     );
 
 static GstStaticPadTemplate gst_frame_positioner_sink_template =
 GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("video/x-raw")
+    GST_STATIC_CAPS ("video/x-raw(ANY)")
     );
 
 G_DEFINE_TYPE (GstFramePositioner, gst_frame_positioner,
     GST_TYPE_BASE_TRANSFORM);
 
+static GType
+gst_compositor_operator_get_type_and_default_value (int *default_operator_value)
+{
+  GstElement *compositor =
+      gst_element_factory_create (ges_get_compositor_factory (), NULL);
+
+  GstPad *compositorPad =
+      gst_element_request_pad_simple (compositor, "sink_%u");
+
+  GParamSpec *pspec =
+      g_object_class_find_property (G_OBJECT_GET_CLASS (compositorPad),
+      "operator");
+  GType ret = 0;
+
+  if (pspec) {
+    *default_operator_value =
+        g_value_get_enum (g_param_spec_get_default_value (pspec));
+    g_return_val_if_fail (pspec, G_TYPE_NONE);
+
+    ret = pspec->value_type;
+  }
+
+  gst_element_release_request_pad (compositor, compositorPad);
+  gst_object_unref (compositorPad);
+  gst_object_unref (compositor);
+
+  return ret;
+}
+
 static void
 _weak_notify_cb (GstFramePositioner * pos, GObject * old)
 {
   pos->current_track = NULL;
 }
 
+static gboolean
+is_user_positionned (GstFramePositioner * self)
+{
+  gint i;
+  GParamSpec *positioning_props[] = {
+    properties[PROP_WIDTH],
+    properties[PROP_HEIGHT],
+    properties[PROP_POSX],
+    properties[PROP_POSY],
+  };
+
+  if (self->user_positioned)
+    return TRUE;
+
+  for (i = 0; i < G_N_ELEMENTS (positioning_props); i++) {
+    GstControlBinding *b = gst_object_get_control_binding (GST_OBJECT (self),
+        positioning_props[i]->name);
+
+    if (b) {
+      gst_object_unref (b);
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+static gboolean
+auto_position (GstFramePositioner * self)
+{
+  gdouble scaled_width = -1, scaled_height = -1, x, y;
+
+  if (is_user_positionned (self)) {
+    GST_DEBUG_OBJECT (self, "Was positioned by the user, not auto positioning");
+    return FALSE;
+  }
+
+  if (!self->natural_width || !self->natural_height)
+    return FALSE;
+
+  if (self->track_width == self->natural_width &&
+      self->track_height == self->natural_height)
+    return TRUE;
+
+  scaled_height =
+      gst_util_uint64_scale_int (self->natural_height, self->track_width,
+      self->natural_width);
+  scaled_width = self->track_width;
+  if (scaled_height > self->track_height) {
+    scaled_height = self->track_height;
+    scaled_width =
+        gst_util_uint64_scale_int (self->natural_width, self->track_height,
+        self->natural_height);
+  }
+
+  x = MAX (0, (self->track_width - scaled_width) / 2.f);
+  y = MAX (0, (self->track_height - scaled_height) / 2.f);
+
+  GST_INFO_OBJECT (self, "Scalling video to match track size from "
+      "%dx%d to %fx%f",
+      self->natural_width, self->natural_height, scaled_width, scaled_height);
+  self->width = scaled_width;
+  self->height = scaled_height;
+  self->posx = x;
+  self->posy = y;
+
+  return TRUE;
+}
+
+typedef struct
+{
+  gdouble *value;
+  gint old_track_value;
+  gint track_value;
+  GParamSpec *pspec;
+} RepositionPropertyData;
+
+static void
+reposition_properties (GstFramePositioner * pos, gint old_track_width,
+    gint old_track_height)
+{
+  gint i;
+  RepositionPropertyData props_data[] = {
+    {&pos->width, old_track_width, pos->track_width, properties[PROP_WIDTH]},
+    {&pos->height, old_track_height, pos->track_height,
+        properties[PROP_HEIGHT]},
+    {&pos->posx, old_track_width, pos->track_width, properties[PROP_POSX]},
+    {&pos->posy, old_track_height, pos->track_height, properties[PROP_POSY]},
+  };
+
+  for (i = 0; i < G_N_ELEMENTS (props_data); i++) {
+    GList *values, *tmp;
+    gboolean absolute;
+    GstTimedValueControlSource *source = NULL;
+
+    RepositionPropertyData d = props_data[i];
+    GstControlBinding *binding =
+        gst_object_get_control_binding (GST_OBJECT (pos), d.pspec->name);
+
+    *(d.value) =
+        *(d.value) * (gdouble) d.track_value / (gdouble) d.old_track_value;
+
+    if (!binding)
+      continue;
+
+    if (!GST_IS_DIRECT_CONTROL_BINDING (binding)) {
+      GST_FIXME_OBJECT (pos, "Implement support for control binding type: %s",
+          G_OBJECT_TYPE_NAME (binding));
+
+      goto next;
+    }
+
+    g_object_get (binding, "control_source", &source, NULL);
+    if (!source || !GST_IS_TIMED_VALUE_CONTROL_SOURCE (source)) {
+      GST_FIXME_OBJECT (pos, "Implement support for control source type: %s",
+          source ? G_OBJECT_TYPE_NAME (source) : "NULL");
+
+      goto next;
+    }
+
+    values =
+        gst_timed_value_control_source_get_all (GST_TIMED_VALUE_CONTROL_SOURCE
+        (source));
+
+    if (!values)
+      goto next;
+
+    g_object_get (binding, "absolute", &absolute, NULL);
+    for (tmp = values; tmp; tmp = tmp->next) {
+      GstTimedValue *value = tmp->data;
+
+      gst_timed_value_control_source_set (source, value->timestamp,
+          value->value * d.track_value / d.old_track_value);
+    }
+
+    g_list_free (values);
+
+  next:
+    gst_clear_object (&source);
+    gst_object_unref (binding);
+  }
+}
+
 static void
 gst_frame_positioner_update_properties (GstFramePositioner * pos,
     gboolean track_mixing, gint old_track_width, gint old_track_height)
@@ -86,13 +268,12 @@ gst_frame_positioner_update_properties (GstFramePositioner * pos,
   if (pos->capsfilter == NULL)
     return;
 
+  caps = gst_caps_from_string ("video/x-raw(ANY)");
+
   if (pos->track_width && pos->track_height &&
       (!track_mixing || !pos->scale_in_compositor)) {
-    caps =
-        gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
+    gst_caps_set_simple (caps, "width", G_TYPE_INT,
         pos->track_width, "height", G_TYPE_INT, pos->track_height, NULL);
-  } else {
-    caps = gst_caps_new_empty_simple ("video/x-raw");
   }
 
   if (pos->fps_n != -1)
@@ -103,21 +284,41 @@ gst_frame_positioner_update_properties (GstFramePositioner * pos,
     gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
         pos->par_n, pos->par_d, NULL);
 
-  if (old_track_width && pos->width == old_track_width &&
-      old_track_height && pos->height == old_track_height &&
-      pos->track_height && pos->track_width &&
-      ((float) old_track_width / (float) old_track_height) ==
-      ((float) pos->track_width / (float) pos->track_height)) {
+  if (!pos->track_width || !pos->track_height) {
+    GST_INFO_OBJECT (pos, "Track doesn't have a proper size, not "
+        "positioning the source");
+    goto done;
+  } else if (auto_position (pos))
+    goto done;
+
+  if (!old_track_height || !old_track_width) {
+    GST_DEBUG_OBJECT (pos, "No old track size, can not properly reposition");
+    goto done;
+  }
 
-    GST_DEBUG_OBJECT (pos, "Following track size width old_track: %d -- pos: %d"
-        " || height, old_track %d -- pos: %d",
-        old_track_width, pos->width, old_track_height, pos->height);
+  if ((!pos->natural_width || !pos->natural_height) &&
+      (!pos->width || !pos->height)) {
+    GST_DEBUG_OBJECT (pos, "No natural aspect ratio and no user set "
+        " image size, can't not reposition.");
+    goto done;
+  }
 
-    pos->width = pos->track_width;
-    pos->height = pos->track_height;
+  if (gst_util_fraction_compare (old_track_width, old_track_height,
+          pos->track_width, pos->track_height)) {
+    GST_INFO_OBJECT (pos, "Not repositioning as track size change didn't"
+        " keep the same aspect ratio (previous %dx%d("
+        "ratio=%f), new: %dx%d(ratio=%f)",
+        old_track_width, old_track_height,
+        (gdouble) old_track_width / (gdouble) old_track_height,
+        pos->track_width, pos->track_height,
+        (gdouble) pos->track_width / (gdouble) pos->track_height);
+    goto done;
   }
 
-  GST_DEBUG_OBJECT (caps, "setting caps");
+  reposition_properties (pos, old_track_width, old_track_height);
+
+done:
+  GST_DEBUG_OBJECT (pos, "setting caps %" GST_PTR_FORMAT, caps);
 
   g_object_set (pos->capsfilter, "caps", caps, NULL);
 
@@ -255,10 +456,19 @@ gst_frame_positioner_dispose (GObject * object)
 static void
 gst_frame_positioner_class_init (GstFramePositionerClass * klass)
 {
+  int default_operator_value = 0;
+  GType operator_gtype =
+      gst_compositor_operator_get_type_and_default_value
+      (&default_operator_value);
+  guint n_pspecs = PROP_LAST;
+
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   GstBaseTransformClass *base_transform_class =
       GST_BASE_TRANSFORM_CLASS (klass);
 
+  GST_DEBUG_CATEGORY_INIT (_framepositioner, "framepositioner",
+      GST_DEBUG_FG_YELLOW, "ges frame positioner");
+
   gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
       &gst_frame_positioner_src_template);
   gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
@@ -275,19 +485,18 @@ gst_frame_positioner_class_init (GstFramePositionerClass * klass)
    *
    * The desired alpha for the stream.
    */
-  g_object_class_install_property (gobject_class, PROP_ALPHA,
-      g_param_spec_double ("alpha", "alpha", "alpha of the stream",
-          0.0, 1.0, 1.0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
+  properties[PROP_ALPHA] =
+      g_param_spec_double ("alpha", "alpha", "alpha of the stream", 0.0, 1.0,
+      1.0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE);
 
   /**
    * gstframepositioner:posx:
    *
    * The desired x position for the stream.
    */
-  g_object_class_install_property (gobject_class, PROP_POSX,
-      g_param_spec_int ("posx", "posx", "x position of the stream",
-          MIN_PIXELS, MAX_PIXELS, 0,
-          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
+  properties[PROP_POSX] =
+      g_param_spec_int ("posx", "posx", "x position of the stream", MIN_PIXELS,
+      MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE);
 
 
   /**
@@ -295,19 +504,18 @@ gst_frame_positioner_class_init (GstFramePositionerClass * klass)
    *
    * The desired y position for the stream.
    */
-  g_object_class_install_property (gobject_class, PROP_POSY,
-      g_param_spec_int ("posy", "posy", "y position of the stream",
-          MIN_PIXELS, MAX_PIXELS, 0,
-          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
+  properties[PROP_POSY] =
+      g_param_spec_int ("posy", "posy", "y position of the stream", MIN_PIXELS,
+      MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE);
 
   /**
    * gstframepositioner:zorder:
    *
    * The desired z order for the stream.
    */
-  g_object_class_install_property (gobject_class, PROP_ZORDER,
-      g_param_spec_uint ("zorder", "zorder", "z order of the stream",
-          0, G_MAXUINT, 0, G_PARAM_READWRITE));
+  properties[PROP_ZORDER] =
+      g_param_spec_uint ("zorder", "zorder", "z order of the stream", 0,
+      G_MAXUINT, 0, G_PARAM_READWRITE);
 
   /**
    * gesframepositioner:width:
@@ -315,9 +523,9 @@ gst_frame_positioner_class_init (GstFramePositionerClass * klass)
    * The desired width for that source.
    * Set to 0 if size is not mandatory, will be set to width of the current track.
    */
-  g_object_class_install_property (gobject_class, PROP_WIDTH,
-      g_param_spec_int ("width", "width", "width of the source",
-          0, MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
+  properties[PROP_WIDTH] =
+      g_param_spec_int ("width", "width", "width of the source", 0, MAX_PIXELS,
+      0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE);
 
   /**
    * gesframepositioner:height:
@@ -325,9 +533,27 @@ gst_frame_positioner_class_init (GstFramePositionerClass * klass)
    * The desired height for that source.
    * Set to 0 if size is not mandatory, will be set to height of the current track.
    */
-  g_object_class_install_property (gobject_class, PROP_HEIGHT,
-      g_param_spec_int ("height", "height", "height of the source",
-          0, MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
+  properties[PROP_HEIGHT] =
+      g_param_spec_int ("height", "height", "height of the source", 0,
+      MAX_PIXELS, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE);
+
+  /**
+   * gesframepositioner:operator:
+   *
+   * The blending operator for the source.
+   */
+  if (operator_gtype) {
+    properties[PROP_OPERATOR] =
+        g_param_spec_enum ("operator", "Operator",
+        "Blending operator to use for blending this pad over the previous ones",
+        operator_gtype, default_operator_value,
+        (G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE |
+            GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_STATIC_STRINGS));
+  } else {
+    n_pspecs--;
+  }
+
+  g_object_class_install_properties (gobject_class, n_pspecs, properties);
 
   gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
       "frame positioner", "Metadata",
@@ -338,12 +564,16 @@ gst_frame_positioner_class_init (GstFramePositionerClass * klass)
 static void
 gst_frame_positioner_init (GstFramePositioner * framepositioner)
 {
+  int default_operator_value;
+  gst_compositor_operator_get_type_and_default_value (&default_operator_value);
+
   framepositioner->alpha = 1.0;
   framepositioner->posx = 0.0;
   framepositioner->posy = 0.0;
   framepositioner->zorder = 0;
   framepositioner->width = 0;
   framepositioner->height = 0;
+  framepositioner->operator = default_operator_value;
   framepositioner->fps_n = -1;
   framepositioner->fps_d = -1;
   framepositioner->track_width = 0;
@@ -375,23 +605,32 @@ gst_frame_positioner_set_property (GObject * object, guint property_id,
       break;
     case PROP_POSX:
       framepositioner->posx = g_value_get_int (value);
+      framepositioner->user_positioned = TRUE;
       break;
     case PROP_POSY:
       framepositioner->posy = g_value_get_int (value);
+      framepositioner->user_positioned = TRUE;
       break;
     case PROP_ZORDER:
       framepositioner->zorder = g_value_get_uint (value);
       break;
     case PROP_WIDTH:
+      framepositioner->user_positioned = TRUE;
       framepositioner->width = g_value_get_int (value);
       gst_frame_positioner_update_properties (framepositioner, track_mixing,
           0, 0);
       break;
     case PROP_HEIGHT:
+      framepositioner->user_positioned = TRUE;
       framepositioner->height = g_value_get_int (value);
       gst_frame_positioner_update_properties (framepositioner, track_mixing,
           0, 0);
       break;
+    case PROP_OPERATOR:
+      framepositioner->operator = g_value_get_enum (value);
+      gst_frame_positioner_update_properties (framepositioner, track_mixing,
+          0, 0);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -406,28 +645,39 @@ gst_frame_positioner_get_property (GObject * object, guint property_id,
   GstFramePositioner *pos = GST_FRAME_POSITIONNER (object);
   gint real_width, real_height;
 
-  GST_DEBUG_OBJECT (pos, "get_property");
-
   switch (property_id) {
     case PROP_ALPHA:
       g_value_set_double (value, pos->alpha);
       break;
     case PROP_POSX:
-      g_value_set_int (value, pos->posx);
+      g_value_set_int (value, round (pos->posx));
       break;
     case PROP_POSY:
-      g_value_set_int (value, pos->posy);
+      g_value_set_int (value, round (pos->posy));
       break;
     case PROP_ZORDER:
       g_value_set_uint (value, pos->zorder);
       break;
     case PROP_WIDTH:
-      real_width = (pos->width > 0) ? pos->width : pos->track_width;
-      g_value_set_int (value, real_width);
+      if (pos->scale_in_compositor) {
+        g_value_set_int (value, round (pos->width));
+      } else {
+        real_width =
+            pos->width > 0 ? round (pos->width) : round (pos->track_width);
+        g_value_set_int (value, real_width);
+      }
       break;
     case PROP_HEIGHT:
-      real_height = (pos->height > 0) ? pos->height : pos->track_height;
-      g_value_set_int (value, real_height);
+      if (pos->scale_in_compositor) {
+        g_value_set_int (value, round (pos->height));
+      } else {
+        real_height =
+            pos->height > 0 ? round (pos->height) : round (pos->track_height);
+        g_value_set_int (value, real_height);
+      }
+      break;
+    case PROP_OPERATOR:
+      g_value_set_enum (value, pos->operator);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -438,7 +688,7 @@ gst_frame_positioner_get_property (GObject * object, guint property_id,
 GType
 gst_frame_positioner_meta_api_get_type (void)
 {
-  static volatile GType type;
+  static GType type;
   static const gchar *tags[] = { "video", NULL };
 
   if (g_once_init_enter (&type)) {
@@ -469,13 +719,17 @@ static gboolean
 gst_frame_positioner_meta_init (GstMeta * meta, gpointer params,
     GstBuffer * buffer)
 {
+  int default_operator_value = 0;
   GstFramePositionerMeta *smeta;
 
   smeta = (GstFramePositionerMeta *) meta;
 
+  gst_compositor_operator_get_type_and_default_value (&default_operator_value);
+
   smeta->alpha = 0.0;
   smeta->posx = smeta->posy = smeta->height = smeta->width = 0;
   smeta->zorder = 0;
+  smeta->operator = default_operator_value;
 
   return TRUE;
 }
@@ -499,6 +753,7 @@ gst_frame_positioner_meta_transform (GstBuffer * dest, GstMeta * meta,
     dmeta->width = smeta->width;
     dmeta->height = smeta->height;
     dmeta->zorder = smeta->zorder;
+    dmeta->operator = smeta->operator;
   }
 
   return TRUE;
@@ -521,11 +776,12 @@ gst_frame_positioner_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
 
   GST_OBJECT_LOCK (framepositioner);
   meta->alpha = framepositioner->alpha;
-  meta->posx = framepositioner->posx;
-  meta->posy = framepositioner->posy;
-  meta->width = framepositioner->width;
-  meta->height = framepositioner->height;
+  meta->posx = round (framepositioner->posx);
+  meta->posy = round (framepositioner->posy);
+  meta->width = round (framepositioner->width);
+  meta->height = round (framepositioner->height);
   meta->zorder = framepositioner->zorder;
+  meta->operator = framepositioner->operator;
   GST_OBJECT_UNLOCK (framepositioner);
 
   return GST_FLOW_OK;
index 9b1884f..0c1b28b 100644 (file)
@@ -47,11 +47,14 @@ struct _GstFramePositioner
 
   gboolean scale_in_compositor;
   gdouble alpha;
-  gint posx;
-  gint posy;
+  gdouble posx;
+  gdouble posy;
   guint zorder;
-  gint width;
-  gint height;
+  gdouble width;
+  gdouble height;
+  gint operator;
+  gint natural_width;
+  gint natural_height;
   gint track_width;
   gint track_height;
   gint fps_n;
@@ -60,6 +63,8 @@ struct _GstFramePositioner
   gint par_n;
   gint par_d;
 
+  gboolean user_positioned;
+
   /*  This should never be made public, no padding needed */
 };
 
@@ -77,6 +82,7 @@ struct _GstFramePositionerMeta {
   gint height;
   gint width;
   guint zorder;
+  gint operator;
 };
 
 G_GNUC_INTERNAL void ges_frame_positioner_set_source_and_filter (GstFramePositioner *pos,
index 8b57f61..cf835ed 100644 (file)
@@ -1,4 +1,4 @@
-ges_sources = [
+ges_sources = files([
     'ges.c',
     'ges-enums.c',
     'ges-meta-container.c',
@@ -7,12 +7,15 @@ ges_sources = [
     'ges-clip.c',
     'ges-pipeline.c',
     'ges-source-clip.c',
+    'ges-source-clip-asset.c',
     'ges-base-effect-clip.c',
     'ges-effect-clip.c',
     'ges-uri-clip.c',
+    'ges-uri-source.c',
     'ges-operation-clip.c',
     'ges-base-transition-clip.c',
     'ges-transition-clip.c',
+    'ges-time-overlay-clip.c',
     'ges-test-clip.c',
     'ges-title-clip.c',
     'ges-overlay-clip.c',
@@ -61,10 +64,11 @@ ges_sources = [
     'ges-validate.c',
     'ges-structured-interface.c',
     'ges-structure-parser.c',
+    'ges-marker-list.c',
     'gstframepositioner.c'
-]
+])
 
-ges_headers = [
+ges_headers = files([
     'ges-types.h',
     'ges.h',
     'ges-prelude.h',
@@ -76,6 +80,7 @@ ges_headers = [
     'ges-clip.h',
     'ges-pipeline.h',
     'ges-source-clip.h',
+    'ges-source-clip-asset.h',
     'ges-uri-clip.h',
     'ges-base-effect-clip.h',
     'ges-effect-clip.h',
@@ -83,6 +88,7 @@ ges_headers = [
     'ges-base-transition-clip.h',
     'ges-transition-clip.h',
     'ges-test-clip.h',
+    'ges-time-overlay-clip.h',
     'ges-title-clip.h',
     'ges-overlay-clip.h',
     'ges-text-overlay-clip.h',
@@ -92,6 +98,7 @@ ges_headers = [
     'ges-audio-track.h',
     'ges-video-track.h',
     'ges-track-element.h',
+    'ges-track-element-deprecated.h',
     'ges-source.h',
     'ges-operation.h',
     'ges-video-source.h',
@@ -122,12 +129,13 @@ ges_headers = [
     'ges-container.h',
     'ges-effect-asset.h',
     'ges-utils.h',
-    'ges-group.h'
-]
+    'ges-group.h',
+    'ges-marker-list.h'
+])
 
 if libxml_dep.found()
-  ges_sources += ['ges-pitivi-formatter.c']
-  ges_headers += ['ges-pitivi-formatter.h']
+  ges_sources += files(['ges-pitivi-formatter.c'])
+  ges_headers += files(['ges-pitivi-formatter.h'])
 endif
 
 version_data = configuration_data()
@@ -136,10 +144,10 @@ version_data.set('GES_VERSION_MINOR', gst_version_minor)
 version_data.set('GES_VERSION_MICRO', gst_version_micro)
 version_data.set('GES_VERSION_NANO', gst_version_nano)
 
-configure_file(input : 'ges-version.h.in',
+ges_headers += [configure_file(input : 'ges-version.h.in',
   output : 'ges-version.h',
   install_dir : join_paths(get_option('includedir'), 'gstreamer-1.0/ges'),
-  configuration : version_data)
+  configuration : version_data)]
 
 install_headers(ges_headers, subdir : 'gstreamer-1.0/ges')
 
@@ -157,7 +165,16 @@ parser = custom_target('gesparselex',
   command : [flex, '-Ppriv_ges_parse_yy', '--header-file=@OUTPUT1@', '-o', '@OUTPUT0@', '@INPUT@']
 )
 
-libges = library('ges-1.0', ges_sources, parser,
+ges_resources = []
+if has_python
+  ges_resources = gnome.compile_resources(
+      'ges-resources', 'ges.resource',
+      source_dir: '.',
+      c_name: 'ges'
+  )
+endif
+
+libges = library('ges-1.0', ges_sources, parser, ges_resources,
     version : libversion,
     soversion : soversion,
     darwin_versions : osxversion,
@@ -166,6 +183,13 @@ libges = library('ges-1.0', ges_sources, parser,
     install : true,
     dependencies : libges_deps)
 
+pkgconfig.generate(libges,
+  libraries : [gst_dep, gstbase_dep, gstpbutils_dep, gstcontroller_dep],
+  subdirs : pkgconfig_subdirs,
+  name : 'gst-editing-services-1.0',
+  description : 'GStreamer Editing Services',
+)
+
 ges_gen_sources = []
 if build_gir
     ges_gir_extra_args = gir_init_section + [ '--c-include=ges/ges.h' ]
@@ -177,7 +201,7 @@ if build_gir
       '-I' + meson.current_build_dir() + '/..',
       '--cflags-end']
     endif
-    ges_gen_sources += [gnome.generate_gir(libges,
+    ges_gir = gnome.generate_gir(libges,
         sources : ges_sources + ges_headers,
         namespace : 'GES',
         nsversion : apiversion,
@@ -188,7 +212,9 @@ if build_gir
         install : true,
         dependencies : libges_deps,
         extra_args : ges_gir_extra_args
-    )]
+    )
+
+    ges_gen_sources += [ges_gir]
 endif
 
 ges_dep = declare_dependency(link_with : libges,
@@ -196,3 +222,5 @@ ges_dep = declare_dependency(link_with : libges,
   sources : ges_gen_sources,
   dependencies : libges_deps,
 )
+
+meson.override_dependency('gst-editing-services-1.0', ges_dep)
index 1581d94..712d05d 100644 (file)
 %option noinput
 %option nounistd
 
-CLIP           [ ]+\+clip[ ]+
+CLIP            [ ]+\+clip[ ]+
 TEST_CLIP       [ ]+\+test-clip[ ]+
-TRANSITION     [ ]+\+transition[ ]+
-EFFECT         [ ]+\+effect[ ]+
-TITLE          [ ]+\+title[ ]+
+TRANSITION      [ ]+\+transition[ ]+
+EFFECT          [ ]+\+effect[ ]+
+TITLE           [ ]+\+title[ ]+
+TRACK           [ ]+\+track[ ]+
+KEYFRAME        [ ]+\+keyframes[ ]+
 
-SETTER         [ ]+set-[^ ]+[ ]+
+SETTER          [ ]+set-[^ ]+[ ]+
+
+STRING          \"(\\.|[^"])*\"
+/* A value string, as understood by gst_structure_from_string
+ * Characters are from GST_ASCII_IS_STRING
+ * NOTE: character set is *not* supposed to be locale dependent */
+VALUE           {STRING}|([abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+/:.-]+)
 
 %%
 
-\"(\\.|[^"])*\"  {
+={VALUE}        {
+               ges_structure_parser_parse_value (yyextra, yytext);
+}
+
+{STRING}        {
                ges_structure_parser_parse_string (yyextra, yytext, FALSE);
 }
 
-{CLIP}|{TRANSITION}|{EFFECT}|{TEST_CLIP}|{TITLE}   {
+{KEYFRAME}|{TRACK}|{CLIP}|{TRANSITION}|{EFFECT}|{TEST_CLIP}|{TITLE}   {
                ges_structure_parser_parse_symbol (yyextra, yytext);
 }
 
-{SETTER}        {
-       ges_structure_parser_parse_setter (yyextra, yytext);
+{SETTER}        {
+               ges_structure_parser_parse_setter (yyextra, yytext);
 }
 
-[ \t\n]+       {
+[ \t\n]+        {
                ges_structure_parser_parse_whitespace (yyextra);
 }
 
-.              {
+.               {
                /* add everything else */
                ges_structure_parser_parse_default (yyextra, yytext);
 }
diff --git a/ges/python/gesotioformatter.py b/ges/python/gesotioformatter.py
new file mode 100644 (file)
index 0000000..344e13b
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Copyright (C) 2019 Igalia S.L
+# Authors:
+#   Thibault Saunier <tsaunier@igalia.com>
+#
+
+import sys
+
+import gi
+import tempfile
+gi.require_version("GES", "1.0")
+gi.require_version("Gst", "1.0")
+
+from gi.repository import GObject
+from gi.repository import Gst
+Gst.init(None)
+from gi.repository import GES
+from gi.repository import GLib
+from collections import OrderedDict
+
+import opentimelineio as otio
+otio.adapters.from_name('xges')
+
+class GESOtioFormatter(GES.Formatter):
+    def do_save_to_uri(self, timeline, uri, overwrite):
+        if not Gst.uri_is_valid(uri) or Gst.uri_get_protocol(uri) != "file":
+            Gst.error("Protocol not supported for file: %s" % uri)
+            return False
+
+        with tempfile.NamedTemporaryFile(suffix=".xges") as tmpxges:
+            timeline.get_asset().save(timeline, "file://" + tmpxges.name, None, overwrite)
+
+            linker = otio.media_linker.MediaLinkingPolicy.ForceDefaultLinker
+            otio_timeline = otio.adapters.read_from_file(tmpxges.name, "xges", media_linker_name=linker)
+            location = Gst.uri_get_location(uri)
+            out_adapter = otio.adapters.from_filepath(location)
+            otio.adapters.write_to_file(otio_timeline, Gst.uri_get_location(uri), out_adapter.name)
+
+        return True
+
+    def do_can_load_uri(self, uri):
+        try:
+            if not Gst.uri_is_valid(uri) or Gst.uri_get_protocol(uri) != "file":
+                return False
+        except GLib.Error as e:
+            Gst.error(str(e))
+            return False
+
+        if uri.endswith(".xges"):
+            return False
+
+        try:
+            return otio.adapters.from_filepath(Gst.uri_get_location(uri)) is not None
+        except Exception as e:
+            Gst.info("Could not load %s -> %s" % (uri, e))
+            return False
+
+
+    def do_load_from_uri(self, timeline, uri):
+        location = Gst.uri_get_location(uri)
+        in_adapter = otio.adapters.from_filepath(location)
+        assert(in_adapter) # can_load_uri should have ensured it is loadable
+
+        linker = otio.media_linker.MediaLinkingPolicy.ForceDefaultLinker
+        otio_timeline = otio.adapters.read_from_file(
+            location,
+            in_adapter.name,
+            media_linker_name=linker
+        )
+
+        with tempfile.NamedTemporaryFile(suffix=".xges") as tmpxges:
+            otio.adapters.write_to_file(otio_timeline, tmpxges.name, "xges")
+            formatter = GES.Formatter.get_default().extract()
+            timeline.get_asset().add_formatter(formatter)
+            return formatter.load_from_uri(timeline, "file://" + tmpxges.name)
+
+GObject.type_register(GESOtioFormatter)
+known_extensions_mimetype_map = [
+    ("otio", "xml", "fcpxml"),
+    ("application/vnd.pixar.opentimelineio+json", "application/vnd.apple-xmeml+xml", "application/vnd.apple-fcp+xml")
+]
+
+extensions = []
+for adapter in otio.plugins.ActiveManifest().adapters:
+    if adapter.name != 'xges':
+        extensions.extend(adapter.suffixes)
+
+extensions_mimetype_map = [[], []]
+for i, ext in enumerate(known_extensions_mimetype_map[0]):
+    if ext in extensions:
+        extensions_mimetype_map[0].append(ext)
+        extensions_mimetype_map[1].append(known_extensions_mimetype_map[1][i])
+        extensions.remove(ext)
+extensions_mimetype_map[0].extend(extensions)
+
+GES.FormatterClass.register_metas(GESOtioFormatter, "otioformatter",
+    "GES Formatter using OpenTimelineIO",
+    ','.join(extensions_mimetype_map[0]),
+    ';'.join(extensions_mimetype_map[1]), 0.1, Gst.Rank.SECONDARY)
index 9de144e..93b5a30 100644 (file)
@@ -16,7 +16,7 @@ a library for creating audio and video editors
 GStreamer library for creating audio and video editors
  </description>
  <category></category>
- <bug-database rdf:resource="http://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer&amp;component=gst-editing-services" />
+ <bug-database rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/" />
  <screenshots></screenshots>
  <mailing-list rdf:resource="http://lists.freedesktop.org/mailman/listinfo/gstreamer-devel" />
  <programming-language>C</programming-language>
@@ -32,21 +32,61 @@ GStreamer library for creating audio and video editors
 
  <release>
   <Version>
-   <revision>1.16.2</revision>
-   <branch>1.16</branch>
+   <revision>1.19.2</revision>
+   <branch>master</branch>
+   <name></name>
+   <created>2021-09-23</created>
+   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.19.2.tar.xz" />
+  </Version>
+ </release>
+
+ <release>
+  <Version>
+   <revision>1.19.1</revision>
+   <branch>master</branch>
+   <name></name>
+   <created>2021-06-01</created>
+   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.19.1.tar.xz" />
+  </Version>
+ </release>
+
+ <release>
+  <Version>
+   <revision>1.18.0</revision>
+   <branch>master</branch>
    <name></name>
-   <created>2019-12-03</created>
-   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.16.2.tar.xz" />
+   <created>2020-09-08</created>
+   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.18.0.tar.xz" />
   </Version>
  </release>
 
  <release>
   <Version>
-   <revision>1.16.1</revision>
-   <branch>1.16</branch>
+   <revision>1.17.90</revision>
+   <branch>master</branch>
+   <name></name>
+   <created>2020-08-20</created>
+   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.17.90.tar.xz" />
+  </Version>
+ </release>
+
+ <release>
+  <Version>
+   <revision>1.17.2</revision>
+   <branch>master</branch>
+   <name></name>
+   <created>2020-07-03</created>
+   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.17.2.tar.xz" />
+  </Version>
+ </release>
+
+ <release>
+  <Version>
+   <revision>1.17.1</revision>
+   <branch>master</branch>
    <name></name>
-   <created>2019-09-23</created>
-   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.16.1.tar.xz" />
+   <created>2020-06-19</created>
+   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-1.17.1.tar.xz" />
   </Version>
  </release>
 
index 7b673ef..81fc109 100644 (file)
@@ -1,6 +1,6 @@
 project('gst-editing-services', 'c',
-  version : '1.16.2',
-  meson_version : '>= 0.47',
+  version : '1.19.2',
+  meson_version : '>= 0.54',
   default_options : [ 'warning_level=1',
                       'buildtype=debugoptimized' ])
 
@@ -25,24 +25,35 @@ curversion = gst_version_minor * 100 + gst_version_micro
 libversion = '@0@.@1@.0'.format(soversion, curversion)
 osxversion = curversion + 1
 
-glib_req = '>= 2.40.0'
+glib_req = '>= 2.56.0'
 gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor)
 
 cc = meson.get_compiler('c')
+mathlib = cc.find_library('m', required : false)
 
 cdata = configuration_data()
 
-# Ignore several spurious warnings for things gstreamer does very commonly
-# If a warning is completely useless and spammy, use '/wdXXXX' to suppress it
-# If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once
-# NOTE: Only add warnings here if you are sure they're spurious
+prefix = get_option('prefix')
+datadir = prefix / get_option('datadir')
+
 if cc.get_id() == 'msvc'
-  add_project_arguments(
+  msvc_args = [
+      # Ignore several spurious warnings for things gstreamer does very commonly
+      # If a warning is completely useless and spammy, use '/wdXXXX' to suppress it
+      # If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once
+      # NOTE: Only add warnings here if you are sure they're spurious
       '/wd4018', # implicit signed/unsigned conversion
       '/wd4146', # unary minus on unsigned (beware INT_MIN)
       '/wd4244', # lossy type conversion (e.g. double -> int)
       '/wd4305', # truncating type conversion (e.g. double -> float)
-      language : 'c')
+      cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8
+
+      # Enable some warnings on MSVC to match GCC/Clang behaviour
+      '/w14062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled
+      '/w14101', # 'identifier' : unreferenced local variable
+      '/w14189', # 'identifier' : local variable is initialized but not referenced
+  ]
+  add_project_arguments(msvc_args, language: 'c')
 endif
 
 if cc.has_link_argument('-Wl,-Bsymbolic-functions')
@@ -70,7 +81,7 @@ endif
 cdata.set('VERSION', '"@0@"'.format(gst_version))
 cdata.set('PACKAGE', '"gst-editing-services"')
 cdata.set('PACKAGE_VERSION', '"@0@"'.format(gst_version))
-cdata.set('PACKAGE_BUGREPORT', '"http://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer"')
+cdata.set('PACKAGE_BUGREPORT', '"https://gitlab.freedesktop.org/gstreamer/gst-editing-services/issues/new"')
 cdata.set('PACKAGE_NAME', '"GStreamer Editing Services"')
 cdata.set('GST_PACKAGE_NAME', '"GStreamer Editing Services"')
 cdata.set('GST_PACKAGE_ORIGIN', '"Unknown package origin"')
@@ -83,6 +94,8 @@ gstpbutils_dep = dependency('gstreamer-pbutils-' + apiversion, version : gst_req
     fallback : ['gst-plugins-base', 'pbutils_dep'])
 gstvideo_dep = dependency('gstreamer-video-' + apiversion, version : gst_req,
     fallback : ['gst-plugins-base', 'video_dep'])
+gstaudio_dep = dependency('gstreamer-audio-' + apiversion, version : gst_req,
+    fallback : ['gst-plugins-base', 'audio_dep'])
 gstbase_dep = dependency('gstreamer-base-1.0', version : gst_req,
     fallback : ['gstreamer', 'gst_base_dep'])
 if host_machine.system() != 'windows'
@@ -92,10 +105,10 @@ if host_machine.system() != 'windows'
 endif
 gstcontroller_dep = dependency('gstreamer-controller-1.0', version : gst_req,
   fallback : ['gstreamer', 'gst_controller_dep'])
-gstvalidate_dep = dependency('gst-validate-1.0', version : gst_req, required : false,
+gstvalidate_dep = dependency('gst-validate-1.0', version : gst_req, required : get_option('validate'),
   fallback : ['gst-devtools', 'validate_dep'])
 
-gio_dep = dependency('gio-2.0', fallback: ['glib', 'libgio_dep'])
+gio_dep = dependency('gio-2.0', version: glib_req, fallback: ['glib', 'libgio_dep'])
 libxml_dep = dependency('libxml-2.0', required: get_option('xptv'))
 cdata.set('DISABLE_XPTV', not libxml_dep.found())
 
@@ -103,21 +116,18 @@ cdata.set('DISABLE_XPTV', not libxml_dep.found())
 # gtk_dep = dependency('gtk+-3.0', required : false)
 
 libges_deps = [gst_dep, gstbase_dep, gstvideo_dep, gstpbutils_dep,
-               gstcontroller_dep, gio_dep, libxml_dep]
+               gstcontroller_dep, gio_dep, libxml_dep, mathlib]
 
 if gstvalidate_dep.found()
     libges_deps = libges_deps + [gstvalidate_dep]
     cdata.set('HAVE_GST_VALIDATE', 1)
 endif
 
-configure_file(output : 'config.h', configuration : cdata)
-
-
 gir = find_program('g-ir-scanner', required : get_option('introspection'))
 gnome = import('gnome')
 
 # Fixme, not very elegant.
-build_gir = gir.found() and not meson.is_cross_build()
+build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled())
 gir_init_section = [ '--add-init-section=' + \
     'extern void gst_init(gint*,gchar**);' + \
     'extern void ges_init(void);' + \
@@ -128,6 +138,72 @@ gir_init_section = [ '--add-init-section=' + \
     'gst_init(NULL,NULL);' + \
     'ges_init();', '--quiet']
 
+has_python = false
+if build_gir
+  pymod = import('python')
+  python = pymod.find_installation(required: get_option('python'))
+  if python.found()
+    # Workaround for https://github.com/mesonbuild/meson/issues/5629
+    pythonver = python.language_version()
+    python_dep = dependency('python-@0@-embed'.format(pythonver), version: '>=3', required: false)
+    if not python_dep.found()
+      python_dep = python.dependency(required : get_option('python'))
+    endif
+  else
+    python_dep = dependency('', required: false)
+  endif
+  if python_dep.found()
+    python_abi_flags = python.get_variable('ABIFLAGS', '')
+    pylib_loc = get_option('libpython-dir')
+
+    error_msg = ''
+    if not cc.compiles('#include <Python.h>', dependencies: [python_dep])
+      error_msg = 'Could not compile a simple program against python'
+    elif pylib_loc == ''
+      check_path_exists = 'import os, sys; assert(os.path.exists(sys.argv[1]))'
+      pylib_loc = python.get_variable('LIBPL', '')
+      if host_machine.system() != 'windows' and host_machine.system() != 'darwin'
+        pylib_ldlibrary = python.get_variable('LDLIBRARY', '')
+        if run_command(python, '-c', check_path_exists, join_paths(pylib_loc, pylib_ldlibrary)).returncode() != 0
+          # Workaround for Fedora
+          pylib_loc = python.get_variable('LIBDIR', '')
+          message('pylib_loc = @0@'.format(pylib_loc))
+        endif
+
+        res = run_command(python, '-c', check_path_exists, join_paths(pylib_loc, pylib_ldlibrary))
+        if res.returncode() != 0
+          error_msg = '@0@ doesn\' exist, can\'t use python'.format(join_paths(pylib_loc, pylib_ldlibrary))
+        endif
+      endif
+      if error_msg == ''
+        pylib_suffix = 'so'
+        if host_machine.system() == 'windows'
+          pylib_suffix = 'dll'
+        elif host_machine.system() == 'darwin'
+          pylib_suffix = 'dylib'
+        endif
+
+        gmodule_dep = dependency('gmodule-2.0')
+        libges_deps = libges_deps + [python_dep, gmodule_dep]
+        has_python = true
+        message('python_abi_flags = @0@'.format(python_abi_flags))
+        message('pylib_loc = @0@'.format(pylib_loc))
+        cdata.set('HAS_PYTHON', true)
+        cdata.set('PY_LIB_LOC', '"@0@"'.format(pylib_loc))
+        cdata.set('PY_ABI_FLAGS', '"@0@"'.format(python_abi_flags))
+        cdata.set('PY_LIB_SUFFIX', '"@0@"'.format(pylib_suffix))
+        cdata.set('PYTHON_VERSION', '"@0@"'.format(python_dep.version()))
+      else
+          if get_option('python').enabled()
+            error(error_msg)
+          else
+            message(error_msg)
+          endif
+      endif
+    endif
+  endif
+endif
+
 ges_c_args = ['-DHAVE_CONFIG_H', '-DG_LOG_DOMAIN="GES"']
 plugins_install_dir = '@0@/gstreamer-1.0'.format(get_option('libdir'))
 
@@ -173,13 +249,21 @@ foreach extra_arg : warning_flags
   endif
 endforeach
 
+python3 = import('python').find_installation()
+pkgconfig = import('pkgconfig')
+pkgconfig_subdirs = ['gstreamer-1.0']
+
 configinc = include_directories('.')
 subdir('ges')
 subdir('plugins')
-subdir('tools')
-subdir('pkgconfig')
+if not get_option('tools').disabled()
+  subdir('tools')
+endif
 subdir('tests')
-subdir('examples')
+if not get_option('examples').disabled()
+  subdir('examples')
+endif
+subdir('docs')
 
 override_detector = '''
 import sys
@@ -208,7 +292,6 @@ else:
         prefix, 'Lib', 'Python%d%d' % (version.major, version.minor),
         'site-packages', 'gi', 'overrides'))
 '''
-python3 = import('python').find_installation()
 pygi_override_dir = get_option('pygi-overrides-dir')
 if pygi_override_dir == ''
     cres = run_command(python3, '-c', override_detector, get_option('prefix'))
@@ -225,14 +308,24 @@ if pygi_override_dir != ''
   subdir('bindings/python')
 endif
 
-if build_machine.system() == 'windows'
-  message('Disabling gtk-doc while building on Windows')
-else
-  if find_program('gtkdoc-scan', required : get_option('gtk_doc')).found()
-    subdir('docs')
+# Set release date
+if gst_version_nano == 0
+  extract_release_date = find_program('scripts/extract-release-date-from-doap-file.py')
+  run_result = run_command(extract_release_date, gst_version, files('gst-editing-services.doap'))
+  if run_result.returncode() == 0
+    release_date = run_result.stdout().strip()
+    cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', release_date)
+    message('Package release date: ' + release_date)
   else
-    message('Not building documentation as gtk-doc was not found')
+    # Error out if our release can't be found in the .doap file
+    error(run_result.stderr())
   endif
 endif
 
+if gio_dep.version().version_compare('< 2.67.4')
+  cdata.set('g_memdup2(ptr,sz)', '(G_LIKELY(((guint64)(sz)) < G_MAXUINT)) ? g_memdup(ptr,sz) : (g_abort(),NULL)')
+endif
+
+configure_file(output: 'config.h', configuration: cdata)
+
 run_command(python3, '-c', 'import shutil; shutil.copy("hooks/pre-commit.hook", ".git/hooks/pre-commit")')
index cc9bde3..5f15a78 100644 (file)
@@ -1,10 +1,26 @@
-option('gtk_doc', type : 'feature', value : 'auto', yield : true,
-       description : 'Build API documentation with gtk-doc')
+# Common feature options
+option('doc', type : 'feature', value : 'auto', yield: true,
+       description: 'Enable documentation.')
+option('examples', type : 'feature', value : 'auto', yield : true,
+       description : 'Build examples')
 option('introspection', type : 'feature', value : 'auto', yield : true,
        description : 'Generate gobject-introspection bindings')
 option('tests', type : 'feature', value : 'auto', yield : true,
        description : 'Build and enable unit tests')
+option('tools', type : 'feature', value : 'auto', yield : true,
+       description : 'Build ges-launch command line tool')
+
+# GES options
+option('bash-completion', type : 'feature', value : 'auto',
+       description : 'Install bash completion files')
 option('pygi-overrides-dir', type : 'string', value : '',
         description: 'Path to pygobject overrides directory')
 option('xptv', type : 'feature', value : 'auto',
-       description : 'Build the deprecated xptv formater')
\ No newline at end of file
+       description : 'Build the deprecated xptv formater')
+option('python', type : 'feature', value : 'auto', yield: true,
+       description: 'Enable python formatters.')
+option('libpython-dir', type : 'string', value : '',
+        description: 'Path to find libpythonXX.so')
+option('validate', type : 'feature', value : 'auto', yield: true,
+       description: 'Enable GstValidate integration.')
+option('examples', type : 'feature', value : 'auto', yield : true)
\ No newline at end of file
diff --git a/packaging/common.tar.gz b/packaging/common.tar.gz
deleted file mode 100644 (file)
index 63b758d..0000000
Binary files a/packaging/common.tar.gz and /dev/null differ
index 43cd0d2..956cb9c 100644 (file)
@@ -3,22 +3,23 @@
 %define _libdebug_dir %{_libdir}/debug/usr/lib
 
 Name:           gst-editing-services
-Version:        1.16.2
-Release:        1
+Version:        1.19.2
+Release:        0
 License:        LGPL-2.0+
 Summary:        GStreamer Editing Service Plug-Ins
 Url:            http://gstreamer.freedesktop.org/
 Group:          Multimedia/Framework
 Source:         http://gstreamer.freedesktop.org/src/gst-editing-services/gst-editing-services-%{version}.tar.xz
-Source100:      common.tar.gz
+Source1001:     gst-editing-services.manifest
 
-BuildRequires:  pkgconfig(gstreamer-1.0)
-BuildRequires:  pkgconfig(gstreamer-plugins-base-1.0)
+BuildRequires:  gstreamer-devel >= %{version}
+BuildRequires:  gst-plugins-base-devel >= %{version}
 BuildRequires:  pkgconfig(libxml-2.0)
-BuildRequires:  autoconf automake libtool
+#BuildRequires:  autoconf automake libtool
 BuildRequires:  gobject-introspection-devel
 BuildRequires:  gtk-doc
 BuildRequires:  flex
+BuildRequires:  meson >= 0.48.0
 
 Requires:       gstreamer >= 1.0.0
 Supplements:    gstreamer
@@ -38,30 +39,33 @@ developing applications that use %{name}
 
 %prep
 %setup -q -n gst-editing-services-%{version}
-%setup -q -T -D -a 100
+cp %{SOURCE1001} .
 
 %build
-./autogen.sh
+mkdir -p build
+
 export CFLAGS="%{optflags} -fno-strict-aliasing\
  -fstack-protector-strong\
  -Wl,-z,relro\
  -D_FORTIFY_SOURCE=2\
  "
 
-%configure\
-        --disable-static\
-        --disable-maintainer-mode\
-        --disable-gtk-doc\
-        --disable-docbook\
-        --disable-examples\
-        --enable-tbm
-make %{?_smp_mflags} V=1
+meson --auto-feature=auto --prefix=/usr --libdir=%{_libdir} --datadir=%{_datadir} \
+  -D doc=disabled \
+  -D examples=disabled \
+  -D tests=disabled \
+  -D bash-completion=disabled \
+  -D validate=disabled \
+  build
+
+ninja -C build all %{?_smp_mflags}
 find . -name '.gitignore' | xargs rm -f
 
 %install
 rm -rf %{buildroot}
 
-%make_install
+export DESTDIR=%{buildroot}
+ninja -C build install
 
 %post -p /sbin/ldconfig
 
@@ -76,6 +80,9 @@ rm -rf %{buildroot}
 %{_lib_gstreamer_dir}/*.so
 %{_libdir}/girepository-1.0/GES-1.0.typelib
 
+%exclude %{_libdir}/gst-validate-launcher/python/launcher/apps/geslaunch.py
+%exclude %{_datadir}/gstreamer-%{gst_branch}/validate/scenarios/ges-edit-clip-while-paused.scenario
+
 %{_bindir}/*-%{gst_branch}
 
 %files devel
diff --git a/pkgconfig/.gitignore b/pkgconfig/.gitignore
deleted file mode 100644 (file)
index 6fd0ef0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.pc
diff --git a/pkgconfig/Makefile.am b/pkgconfig/Makefile.am
deleted file mode 100644 (file)
index e15e41b..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-pcfiles = \
-       gst-editing-services-@GST_API_VERSION@.pc
-
-pcfiles_uninstalled = \
-       gst-editing-services-@GST_API_VERSION@-uninstalled.pc
-
-all-local: $(pcfiles) $(pcfiles_uninstalled)
-
-### how to generate pc files
-%-@GST_API_VERSION@.pc: %.pc
-       cp $< $@
-%-@GST_API_VERSION@-uninstalled.pc: %-uninstalled.pc
-### the uninstalled libdir is depend of the build system used so set it here
-### rather than hardcoding it in the file directly.
-       $(AM_V_GEN) sed \
-               -e "s|[@]geslibdir[@]|$(abs_top_builddir)/ges/.libs|" \
-               $< > $@.tmp && mv $@.tmp $@
-
-pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = $(pcfiles)
-
-EXTRA_DIST = \
-       gst-editing-services.pc.in \
-       gst-editing-services-uninstalled.pc.in
-CLEANFILES = $(pcfiles) $(pcfiles_uninstalled)
diff --git a/pkgconfig/gst-editing-services-uninstalled.pc.in b/pkgconfig/gst-editing-services-uninstalled.pc.in
deleted file mode 100644 (file)
index 9cd1b61..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# the standard variables don't make sense for an uninstalled copy
-prefix=
-exec_prefix=
-libdir=@geslibdir@
-includedir=@abs_top_builddir@
-
-Name: gst-editing-services
-Description: GStreamer Editing Services
-Version: @VERSION@
-Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@ gstreamer-controller-@GST_API_VERSION@ gstreamer-pbutils-@GST_API_VERSION@
-Libs: -L${libdir} -lges-@GST_API_VERSION@
-Cflags: -I@abs_top_srcdir@ -I@abs_top_builddir@
diff --git a/pkgconfig/gst-editing-services.pc.in b/pkgconfig/gst-editing-services.pc.in
deleted file mode 100644 (file)
index 3469e35..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-prefix=@prefix@
-exec_prefix=@exec_prefix@
-libdir=@libdir@
-includedir=@includedir@/gstreamer-@GST_API_VERSION@
-
-Name: gst-editing-services
-Description: GStreamer Editing Services
-Version: @VERSION@
-Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@ gstreamer-controller-@GST_API_VERSION@ gstreamer-pbutils-@GST_API_VERSION@
-Libs: -L${libdir} -lges-@GST_API_VERSION@
-Cflags: -I${includedir}
diff --git a/pkgconfig/meson.build b/pkgconfig/meson.build
deleted file mode 100644 (file)
index a612b21..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-pkgconf = configuration_data()
-
-pkgconf.set('prefix', get_option('prefix'))
-pkgconf.set('exec_prefix', '${prefix}')
-pkgconf.set('libdir', '${prefix}/@0@'.format(get_option('libdir')))
-pkgconf.set('includedir', '${prefix}/@0@'.format(get_option('includedir')))
-pkgconf.set('GST_API_VERSION', apiversion)
-pkgconf.set('VERSION', gst_version)
-
-# needed for generating -uninstalled.pc files
-pkgconf.set('abs_top_builddir', join_paths(meson.current_build_dir(), '..'))
-pkgconf.set('abs_top_srcdir', join_paths(meson.current_source_dir(), '..'))
-pkgconf.set('geslibdir', join_paths(meson.build_root(), libges.outdir()))
-
-pkg_install_dir = '@0@/pkgconfig'.format(get_option('libdir'))
-
-pkg_files = ['gst-editing-services']
-
-foreach p : pkg_files
-  infile = p + '.pc.in'
-  outfile = p + '-1.0.pc'
-  configure_file(input : infile,
-    output : outfile,
-    configuration : pkgconf,
-    install_dir : pkg_install_dir)
-
-  infile = p + '-uninstalled.pc.in'
-  outfile = p + '-1.0-uninstalled.pc'
-  configure_file(input : infile,
-    output : outfile,
-    configuration : pkgconf)
-endforeach
-
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
deleted file mode 100644 (file)
index 796df5d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-SUBDIRS = ges nle
diff --git a/plugins/ges/Makefile.am b/plugins/ges/Makefile.am
deleted file mode 100644 (file)
index b9e895f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-plugin_LTLIBRARIES = libgstges.la
-
-libgstges_la_SOURCES = \
-       gesplugin.c \
-       gessrc.c \
-       gesdemux.c
-
-libgstges_la_CFLAGS = -I$(top_srcdir) \
-       $(GST_PBUTILS_CFLAGS) \
-       $(GST_PLUGINS_BASE_CFLAGS) \
-       $(GST_BASE_CFLAGS) $(GST_CFLAGS)
-
-libgstges_la_LIBADD = \
-       $(top_builddir)/ges/libges-@GST_API_VERSION@.la \
-       $(GST_PBUTILS_LIBS) \
-       $(GST_PLUGINS_BASE_LIBS) \
-       $(GST_BASE_LIBS) $(GST_LIBS)
-
-libgstges_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
-
-noinst_HEADERS = \
-       gesdemux.h \
-       gessrc.h
-
diff --git a/plugins/ges/gesbasebin.c b/plugins/ges/gesbasebin.c
new file mode 100644 (file)
index 0000000..5e1c6ca
--- /dev/null
@@ -0,0 +1,310 @@
+/* GStreamer GES plugin
+ *
+ * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * gesbasebin.h
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "gesbasebin.h"
+
+static GstStaticPadTemplate video_src_template =
+GST_STATIC_PAD_TEMPLATE ("video_src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("video/x-raw(ANY)"));
+
+static GstStaticPadTemplate audio_src_template =
+    GST_STATIC_PAD_TEMPLATE ("audio_src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("audio/x-raw(ANY);"));
+
+typedef struct
+{
+  GESTimeline *timeline;
+  GstFlowCombiner *flow_combiner;
+} GESBaseBinPrivate;
+
+enum
+{
+  PROP_0,
+  PROP_TIMELINE,
+  PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+G_DEFINE_TYPE_WITH_PRIVATE (GESBaseBin, ges_base_bin, GST_TYPE_BIN);
+
+GST_DEBUG_CATEGORY_STATIC (gesbasebin);
+#define GST_CAT_DEFAULT gesbasebin
+
+static void
+ges_base_bin_dispose (GObject * object)
+{
+  GESBaseBin *self = GES_BASE_BIN (object);
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  if (priv->timeline)
+    gst_clear_object (&priv->timeline);
+}
+
+static void
+ges_base_bin_finalize (GObject * object)
+{
+  GESBaseBin *self = GES_BASE_BIN (object);
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  gst_flow_combiner_free (priv->flow_combiner);
+}
+
+static void
+ges_base_bin_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GESBaseBin *self = GES_BASE_BIN (object);
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  switch (property_id) {
+    case PROP_TIMELINE:
+      g_value_set_object (value, priv->timeline);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+ges_base_bin_set_property (GObject * object, guint property_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GESBaseBin *self = GES_BASE_BIN (object);
+
+  switch (property_id) {
+    case PROP_TIMELINE:
+      ges_base_bin_set_timeline (self, g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+ges_base_bin_class_init (GESBaseBinClass * self_class)
+{
+  GObjectClass *gclass = G_OBJECT_CLASS (self_class);
+  GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class);
+
+  GST_DEBUG_CATEGORY_INIT (gesbasebin, "gesbasebin", 0, "ges bin element");
+
+  gst_tag_register ("is-ges-timeline", GST_TAG_FLAG_META, G_TYPE_BOOLEAN,
+      "is-ges-timeline", "The stream is a ges timeline.", NULL);
+
+  gclass->get_property = ges_base_bin_get_property;
+  gclass->set_property = ges_base_bin_set_property;
+  gclass->dispose = ges_base_bin_dispose;
+  gclass->finalize = ges_base_bin_finalize;
+
+  /**
+   * GESBaseBin:timeline:
+   *
+   * Timeline to use in this bin.
+   *
+   * Since: 1.16
+   */
+  properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
+      "Timeline to use in this src.",
+      GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gclass, PROP_LAST, properties);
+
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&video_src_template));
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&audio_src_template));
+
+  gst_type_mark_as_plugin_api (ges_base_bin_get_type (), 0);
+}
+
+static void
+ges_base_bin_init (GESBaseBin * self)
+{
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  ges_init ();
+
+  priv->flow_combiner = gst_flow_combiner_new ();
+}
+
+static gboolean
+ges_base_bin_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_STREAM_START:
+    {
+      const gchar *stream_id;
+      gchar *new_stream_id;
+      guint stream_group;
+      GstTagList *tlist = gst_tag_list_new ("is-ges-timeline", TRUE, NULL);
+      GstPad *peer = gst_pad_get_peer (pad);
+      GstEvent *new_event;
+
+      gst_event_parse_stream_start (event, &stream_id);
+      new_stream_id =
+          gst_pad_create_stream_id (peer,
+          GST_ELEMENT (GST_OBJECT_PARENT (parent)), stream_id);
+      gst_object_unref (peer);
+
+      new_event = gst_event_new_stream_start (new_stream_id);
+      if (gst_event_parse_group_id (event, &stream_group))
+        gst_event_set_group_id (new_event, stream_group);
+      gst_event_unref (event);
+      g_free (new_stream_id);
+
+      gst_pad_event_default (pad, parent, new_event);
+
+      gst_tag_list_set_scope (tlist, GST_TAG_SCOPE_GLOBAL);
+
+      return gst_pad_send_event (pad, gst_event_new_tag (tlist));
+    }
+    default:
+      break;
+  }
+
+  return gst_pad_event_default (pad, parent, event);
+}
+
+static GstFlowReturn
+ges_base_bin_src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
+{
+  GstFlowReturn result, chain_result;
+  GESBaseBin *self = GES_BASE_BIN (GST_OBJECT_PARENT (parent));
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  chain_result = gst_proxy_pad_chain_default (pad, GST_OBJECT (self), buffer);
+  result =
+      gst_flow_combiner_update_pad_flow (priv->flow_combiner, pad,
+      chain_result);
+
+  if (result == GST_FLOW_FLUSHING)
+    return chain_result;
+
+  return result;
+}
+
+
+gboolean
+ges_base_bin_set_timeline (GESBaseBin * self, GESTimeline * timeline)
+{
+  GList *tmp;
+  guint naudiopad = 0, nvideopad = 0;
+  GstBin *sbin = GST_BIN (self);
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
+
+  if (priv->timeline) {
+    GST_ERROR_OBJECT (sbin, "Implement changing timeline support");
+
+    return FALSE;
+  }
+
+  priv->timeline = gst_object_ref (timeline);
+  GST_INFO_OBJECT (sbin, "Setting timeline: %" GST_PTR_FORMAT, timeline);
+  gst_element_set_locked_state (GST_ELEMENT (timeline), TRUE);
+  if (!gst_bin_add (sbin, GST_ELEMENT (timeline))) {
+    GST_ERROR_OBJECT (sbin, "Could not add timeline to myself!");
+
+    return FALSE;
+  }
+
+  ges_timeline_commit (timeline);
+  for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
+    GstPad *gpad;
+    gchar *name = NULL;
+    GstElement *queue;
+    GESTrack *track = GES_TRACK (tmp->data);
+    GstPad *proxy_pad, *tmppad, *pad =
+        ges_timeline_get_pad_for_track (timeline, track);
+    GstStaticPadTemplate *template;
+
+    if (!pad) {
+      GST_WARNING_OBJECT (sbin, "No pad for track: %" GST_PTR_FORMAT, track);
+
+      continue;
+    }
+
+    if (track->type == GES_TRACK_TYPE_AUDIO) {
+      name = g_strdup_printf ("audio_%u", naudiopad++);
+      template = &audio_src_template;
+    } else if (track->type == GES_TRACK_TYPE_VIDEO) {
+      name = g_strdup_printf ("video_%u", nvideopad++);
+      template = &video_src_template;
+    } else {
+      GST_INFO_OBJECT (sbin, "Track type not handled: %" GST_PTR_FORMAT, track);
+      continue;
+    }
+
+    queue = gst_element_factory_make ("queue", NULL);
+    /* Add queues the same way as in GESPipeline */
+    g_object_set (G_OBJECT (queue), "max-size-buffers", 0,
+        "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND, NULL);
+    gst_bin_add (sbin, queue);
+    gst_element_sync_state_with_parent (GST_ELEMENT (queue));
+
+    tmppad = gst_element_get_static_pad (queue, "sink");
+    if (gst_pad_link (pad, tmppad) != GST_PAD_LINK_OK) {
+      GST_ERROR_OBJECT (sbin, "Could not link %s:%s and %s:%s",
+          GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (tmppad));
+
+      gst_object_unref (tmppad);
+      gst_object_unref (queue);
+      continue;
+    }
+
+    tmppad = gst_element_get_static_pad (queue, "src");
+    gpad = gst_ghost_pad_new_from_template (name, tmppad,
+        gst_static_pad_template_get (template));
+
+    gst_pad_set_active (gpad, TRUE);
+    gst_element_add_pad (GST_ELEMENT (sbin), gpad);
+
+    proxy_pad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (gpad)));
+    gst_flow_combiner_add_pad (priv->flow_combiner, proxy_pad);
+    gst_pad_set_chain_function (proxy_pad, ges_base_bin_src_chain);
+    gst_pad_set_event_function (proxy_pad, ges_base_bin_event);
+    gst_object_unref (proxy_pad);
+    GST_DEBUG_OBJECT (sbin, "Adding pad: %" GST_PTR_FORMAT, gpad);
+  }
+
+  gst_element_set_locked_state (GST_ELEMENT (timeline), FALSE);
+
+  gst_element_no_more_pads (GST_ELEMENT (sbin));
+  gst_element_sync_state_with_parent (GST_ELEMENT (timeline));
+
+  return TRUE;
+}
+
+GESTimeline *
+ges_base_bin_get_timeline (GESBaseBin * self)
+{
+  GESBaseBinPrivate *priv = ges_base_bin_get_instance_private (self);
+
+  return priv->timeline;
+}
similarity index 56%
rename from plugins/ges/gessrc.h
rename to plugins/ges/gesbasebin.h
index 976321a..7320452 100644 (file)
@@ -2,7 +2,7 @@
  *
  * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
  *
- * gstges.c
+ * gesbasebin.h
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  *
  */
 
-#ifndef __GES_SRC_H__
-#define __GES_SRC_H__
+#pragma once
 
 #include <gst/gst.h>
+#include <gst/base/gstflowcombiner.h>
 #include <ges/ges.h>
 
 G_BEGIN_DECLS
 
-GType ges_src_get_type (void);
+#define SUPRESS_UNUSED_WARNING(a) (void)a
 
-#define GES_SRC_TYPE (ges_src_get_type ())
-#define GES_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_SRC_TYPE, GESSrc))
-#define GES_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_SRC_TYPE, GESSrcClass))
-#define GES_IS_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_SRC_TYPE))
-#define GES_IS_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_SRC_TYPE))
-#define GES_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_SRC_TYPE, GESSrcClass))
+G_DECLARE_DERIVABLE_TYPE(GESBaseBin, ges_base_bin, GES, BASE_BIN, GstBin)
+struct _GESBaseBinClass
+{
+  GstBinClass parent_class;
+};
 
-typedef struct {
-  GstBin parent;
-
-  GESTimeline *timeline;
-} GESSrc;
-
-typedef struct {
-  GstBinClass parent;
-
-} GESSrcClass;
+gboolean ges_base_bin_set_timeline (GESBaseBin * self, GESTimeline * timeline);
+GESTimeline * ges_base_bin_get_timeline (GESBaseBin * self);
 
 G_END_DECLS
-#endif /* __GES_SRC_H__ */
index 4993b2a..ed13a06 100644 (file)
 #include "config.h"
 #endif
 
+#include "gesbasebin.h"
+
 #include <gst/gst.h>
 #include <glib/gstdio.h>
 #include <gst/pbutils/pbutils.h>
-#include "gesdemux.h"
+#include <gst/base/gstadapter.h>
+#include <ges/ges.h>
 
 GST_DEBUG_CATEGORY_STATIC (gesdemux);
 #define GST_CAT_DEFAULT gesdemux
 
-static GstStaticPadTemplate video_src_template =
-GST_STATIC_PAD_TEMPLATE ("video_src",
-    GST_PAD_SRC,
-    GST_PAD_SOMETIMES,
-    GST_STATIC_CAPS ("video/x-raw(ANY)"));
+G_DECLARE_FINAL_TYPE (GESDemux, ges_demux, GES, DEMUX, GESBaseBin);
+#define GES_DEMUX_DOC_CAPS \
+  "application/xges;" \
+  "text/x-xptv;" \
+  "application/vnd.pixar.opentimelineio+json;" \
+  "application/vnd.apple-xmeml+xml;" \
+  "application/vnd.apple-fcp+xml;" \
+
+struct _GESDemux
+{
+  GESBaseBin parent;
+
+  GESTimeline *timeline;
+  GstPad *sinkpad;
 
-static GstStaticPadTemplate audio_src_template =
-    GST_STATIC_PAD_TEMPLATE ("audio_src",
-    GST_PAD_SRC,
-    GST_PAD_SOMETIMES,
-    GST_STATIC_CAPS ("audio/x-raw(ANY);"));
+  GstAdapter *input_adapter;
 
-static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
-    GST_PAD_SINK,
-    GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("application/xges"));
+  gchar *upstream_uri;
+  GStatBuf stats;
+};
 
-G_DEFINE_TYPE (GESDemux, ges_demux, GST_TYPE_BIN);
+G_DEFINE_TYPE (GESDemux, ges_demux, ges_base_bin_get_type ());
+#define GES_DEMUX(obj) ((GESDemux*)obj)
 
 enum
 {
@@ -72,84 +80,90 @@ enum
 
 static GParamSpec *properties[PROP_LAST];
 
-static gboolean
-ges_demux_set_timeline (GESDemux * self, GESTimeline * timeline)
+static GstCaps *
+ges_demux_get_sinkpad_caps ()
 {
-  GList *tmp;
-  guint naudiopad = 0, nvideopad = 0;
-  GstBin *sbin = GST_BIN (self);
-
-  g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
-
-  if (self->timeline) {
-    GST_ERROR_OBJECT (self, "Implement changing timeline support");
+  GList *tmp, *formatters;
+  GstCaps *sinkpad_caps = gst_caps_new_empty ();
+
+  formatters = ges_list_assets (GES_TYPE_FORMATTER);
+  for (tmp = formatters; tmp; tmp = tmp->next) {
+    GstCaps *caps;
+    const gchar *mimetype =
+        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
+        GES_META_FORMATTER_MIMETYPE);
+    if (!mimetype)
+      continue;
 
-    return FALSE;
-  }
+    caps = gst_caps_from_string (mimetype);
 
-  GST_INFO_OBJECT (self, "Setting timeline: %" GST_PTR_FORMAT, timeline);
-  self->timeline = gst_object_ref (timeline);
+    if (!caps) {
+      GST_INFO_OBJECT (tmp->data,
+          "%s - could not create caps from mimetype: %s",
+          ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
+              GES_META_FORMATTER_NAME), mimetype);
 
-  if (!gst_bin_add (sbin, GST_ELEMENT (self->timeline))) {
-    GST_ERROR_OBJECT (self, "Could not add timeline to myself!");
+      continue;
+    }
 
-    return FALSE;
+    gst_caps_append (sinkpad_caps, caps);
   }
-  for (tmp = self->timeline->tracks; tmp; tmp = tmp->next) {
-    GstPad *gpad;
-    gchar *name = NULL;
-    GstElement *queue;
-    GESTrack *track = GES_TRACK (tmp->data);
-    GstPad *tmppad, *pad =
-        ges_timeline_get_pad_for_track (self->timeline, track);
-    GstStaticPadTemplate *template;
-
-    if (!pad) {
-      GST_WARNING_OBJECT (self, "No pad for track: %" GST_PTR_FORMAT, track);
+  g_list_free (formatters);
 
-      continue;
-    }
+  return sinkpad_caps;
+}
 
-    if (track->type == GES_TRACK_TYPE_AUDIO) {
-      name = g_strdup_printf ("audio_%u", naudiopad++);
-      template = &audio_src_template;
-    } else if (track->type == GES_TRACK_TYPE_VIDEO) {
-      name = g_strdup_printf ("video_%u", nvideopad++);
-      template = &video_src_template;
-    } else {
-      GST_INFO_OBJECT (self, "Track type not handled: %" GST_PTR_FORMAT, track);
+static gchar *
+ges_demux_get_extension (GstStructure * _struct)
+{
+  GList *tmp, *formatters;
+  gchar *ext = NULL;
+
+  formatters = ges_list_assets (GES_TYPE_FORMATTER);
+  for (tmp = formatters; tmp; tmp = tmp->next) {
+    gchar **extensions_a;
+    gint i, n_exts;
+    GstCaps *caps;
+    const gchar *mimetype =
+        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
+        GES_META_FORMATTER_MIMETYPE);
+    const gchar *extensions =
+        ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
+        GES_META_FORMATTER_EXTENSION);
+    if (!mimetype)
       continue;
-    }
 
-    queue = gst_element_factory_make ("queue", NULL);
-    /* Add queues the same way as in GESPipeline */
-    g_object_set (G_OBJECT (queue), "max-size-buffers", 0,
-        "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND, NULL);
-    gst_bin_add (GST_BIN (self), queue);
-    gst_element_sync_state_with_parent (GST_ELEMENT (queue));
+    if (!extensions)
+      continue;
 
-    tmppad = gst_element_get_static_pad (queue, "sink");
-    if (gst_pad_link (pad, tmppad) != GST_PAD_LINK_OK) {
-      GST_ERROR_OBJECT (self, "Could not link %s:%s and %s:%s",
-          GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (tmppad));
+    caps = gst_caps_from_string (mimetype);
+    if (!caps) {
+      GST_INFO_OBJECT (tmp->data,
+          "%s - could not create caps from mimetype: %s",
+          ges_meta_container_get_string (GES_META_CONTAINER (tmp->data),
+              GES_META_FORMATTER_NAME), mimetype);
 
-      gst_object_unref (tmppad);
-      gst_object_unref (queue);
       continue;
     }
 
-    tmppad = gst_element_get_static_pad (queue, "src");
-    gpad = gst_ghost_pad_new_from_template (name, tmppad,
-        gst_static_pad_template_get (template));
+    extensions_a = g_strsplit (extensions, ",", -1);
+    n_exts = g_strv_length (extensions_a);
+    for (i = 0; i < gst_caps_get_size (caps) && i < n_exts; i++) {
+      GstStructure *structure = gst_caps_get_structure (caps, i);
 
-    gst_pad_set_active (gpad, TRUE);
-    gst_element_add_pad (GST_ELEMENT (self), gpad);
-    GST_DEBUG_OBJECT (self, "Adding pad: %" GST_PTR_FORMAT, gpad);
+      if (gst_structure_has_name (_struct, gst_structure_get_name (structure))) {
+        ext = g_strdup (extensions_a[i]);
+        g_strfreev (extensions_a);
+        gst_caps_unref (caps);
+        goto done;
+      }
+    }
+    g_strfreev (extensions_a);
   }
+done:
+  g_list_free (formatters);
 
-  gst_element_sync_state_with_parent (GST_ELEMENT (self->timeline));
-
-  return TRUE;
+  return ext;
 }
 
 static void
@@ -160,7 +174,8 @@ ges_demux_get_property (GObject * object, guint property_id,
 
   switch (property_id) {
     case PROP_TIMELINE:
-      g_value_set_object (value, self->timeline);
+      g_value_set_object (value,
+          ges_base_bin_get_timeline (GES_BASE_BIN (self)));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -178,25 +193,19 @@ ges_demux_set_property (GObject * object, guint property_id,
 }
 
 static void
-ges_demux_dispose (GObject * object)
-{
-  GESDemux *self = GES_DEMUX (object);
-
-  if (self->timeline)
-    gst_clear_object (&self->timeline);
-}
-
-static void
 ges_demux_class_init (GESDemuxClass * self_class)
 {
+  GstPadTemplate *pad_template;
   GObjectClass *gclass = G_OBJECT_CLASS (self_class);
   GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class);
+  GstCaps *sinkpad_caps, *doc_caps;
 
   GST_DEBUG_CATEGORY_INIT (gesdemux, "gesdemux", 0, "ges demux element");
 
+  sinkpad_caps = ges_demux_get_sinkpad_caps ();
+
   gclass->get_property = ges_demux_get_property;
   gclass->set_property = ges_demux_set_property;
-  gclass->dispose = ges_demux_dispose;
 
   /**
    * GESDemux:timeline:
@@ -206,8 +215,7 @@ ges_demux_class_init (GESDemuxClass * self_class)
   properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
       "Timeline to use in this source.",
       GES_TYPE_TIMELINE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
-
-  g_object_class_install_properties (gclass, PROP_LAST, properties);
+  g_object_class_override_property (gclass, PROP_TIMELINE, "timeline");
 
   gst_element_class_set_static_metadata (gstelement_klass,
       "GStreamer Editing Services based 'demuxer'",
@@ -215,121 +223,278 @@ ges_demux_class_init (GESDemuxClass * self_class)
       "Demuxer for complex timeline file formats using GES.",
       "Thibault Saunier <tsaunier@igalia.com");
 
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&sink_template));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&video_src_template));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&audio_src_template));
+  pad_template =
+      gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, sinkpad_caps);
+  doc_caps = gst_caps_from_string (GES_DEMUX_DOC_CAPS);
+  gst_pad_template_set_documentation_caps (pad_template, doc_caps);
+  gst_clear_caps (&doc_caps);
+  gst_element_class_add_pad_template (gstelement_klass, pad_template);
+  gst_caps_unref (sinkpad_caps);
 }
 
 typedef struct
 {
   GESTimeline *timeline;
-  gchar *uri;
   GMainLoop *ml;
   GError *error;
-  GMutex lock;
-  GCond cond;
   gulong loaded_sigid;
   gulong error_sigid;
+  gulong error_asset_sigid;
 } TimelineConstructionData;
 
 static void
 project_loaded_cb (GESProject * project, GESTimeline * timeline,
     TimelineConstructionData * data)
 {
-  g_mutex_lock (&data->lock);
   data->timeline = timeline;
   g_signal_handler_disconnect (project, data->loaded_sigid);
   data->loaded_sigid = 0;
-  g_mutex_unlock (&data->lock);
 
   g_main_loop_quit (data->ml);
 }
 
 static void
-error_loading_asset_cb (GESProject * project, GError * error, gchar * id,
-    GType extractable_type, TimelineConstructionData * data)
+error_loading_cb (GESProject * project, GESTimeline * timeline,
+    GError * error, TimelineConstructionData * data)
 {
-  g_mutex_lock (&data->lock);
   data->error = g_error_copy (error);
   g_signal_handler_disconnect (project, data->error_sigid);
   data->error_sigid = 0;
-  g_mutex_unlock (&data->lock);
 
   g_main_loop_quit (data->ml);
 }
 
-/* TODO: Add a way to run a function in the right GES thread */
+static void
+error_loading_asset_cb (GESProject * project, GError * error, gchar * id,
+    GType extractable_type, TimelineConstructionData * data)
+{
+  data->error = g_error_copy (error);
+  g_signal_handler_disconnect (project, data->error_asset_sigid);
+  data->error_asset_sigid = 0;
+
+  g_main_loop_quit (data->ml);
+}
+
 static gboolean
-ges_timeline_new_from_uri_from_main_thread (TimelineConstructionData * data)
+ges_demux_src_probe (GstPad * pad, GstPadProbeInfo * info, GstElement * parent)
 {
-  GESProject *project = ges_project_new (data->uri);
-  GESUriClipAssetClass *klass = g_type_class_peek (GES_TYPE_URI_CLIP_ASSET);
-  GstDiscoverer *previous_discoverer = klass->discoverer;
-  GstClockTime timeout;
-  G_GNUC_UNUSED void *unused;
+  GESDemux *self = GES_DEMUX (parent);
+  GstStructure *structure =
+      (GstStructure *) gst_query_get_structure (info->data);
 
-  g_object_get (previous_discoverer, "timeout", &timeout, NULL);
+  if (gst_structure_has_name (structure, "NleCompositionQueryNeedsTearDown")) {
+    GstQuery *uri_query = gst_query_new_uri ();
 
-  /* Make sure to use a new discoverer in case we are being discovered,
-   * as discovering is done one by one, and the global discoverer won't
-   * have the chance to discover the project assets */
-  g_mutex_lock (&data->lock);
-  klass->discoverer = gst_discoverer_new (timeout, &data->error);
-  if (data->error) {
-    klass->discoverer = previous_discoverer;
-    g_mutex_unlock (&data->lock);
+    if (gst_pad_peer_query (self->sinkpad, uri_query)) {
+      gchar *upstream_uri = NULL;
+      GStatBuf stats;
+      gst_query_parse_uri (uri_query, &upstream_uri);
 
-    goto done;
+      if (gst_uri_has_protocol (upstream_uri, "file")) {
+        gchar *location = gst_uri_get_location (upstream_uri);
+
+        if (g_stat (location, &stats) < 0) {
+          GST_INFO_OBJECT (self, "Could not stat %s - not updating", location);
+
+          g_free (location);
+          g_free (upstream_uri);
+          goto done;
+        }
+
+        g_free (location);
+        GST_OBJECT_LOCK (self);
+        if (g_strcmp0 (upstream_uri, self->upstream_uri)
+            || stats.st_mtime != self->stats.st_mtime
+            || stats.st_size != self->stats.st_size) {
+          GST_INFO_OBJECT (self,
+              "Underlying file changed, asking for an update");
+          gst_structure_set (structure, "result", G_TYPE_BOOLEAN, TRUE, NULL);
+          g_free (self->upstream_uri);
+          self->upstream_uri = upstream_uri;
+          self->stats = stats;
+        } else {
+          g_free (upstream_uri);
+        }
+        GST_OBJECT_UNLOCK (self);
+      }
+    }
+  done:
+    gst_query_unref (uri_query);
   }
-  g_signal_connect (klass->discoverer, "discovered",
-      G_CALLBACK (klass->discovered), NULL);
-  gst_discoverer_start (klass->discoverer);
 
-  data->ml = g_main_loop_new (NULL, TRUE);
-  data->loaded_sigid =
-      g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb),
-      data);
-  data->error_sigid =
-      g_signal_connect (project, "error-loading-asset",
-      G_CALLBACK (error_loading_asset_cb), data);
+  return GST_PAD_PROBE_OK;
+}
+
+static gboolean
+ges_demux_set_srcpad_probe (GstElement * element, GstPad * pad,
+    gpointer user_data)
+{
+  gst_pad_add_probe (pad,
+      GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
+      (GstPadProbeCallback) ges_demux_src_probe, element, NULL);
+  return TRUE;
+}
+
+static void
+ges_demux_adapt_timeline_duration (GESDemux * self, GESTimeline * timeline)
+{
+  GType nleobject_type = g_type_from_name ("NleObject");
+  GstObject *parent, *tmpparent;
+
+  parent = gst_object_get_parent (GST_OBJECT (self));
+  while (parent) {
+    if (g_type_is_a (G_OBJECT_TYPE (parent), nleobject_type)) {
+      GstClockTime duration, inpoint, timeline_duration;
+
+      g_object_get (parent, "duration", &duration, "inpoint", &inpoint, NULL);
+      g_object_get (timeline, "duration", &timeline_duration, NULL);
+
+      if (inpoint + duration > timeline_duration) {
+        GESLayer *layer = ges_timeline_get_layer (timeline, 0);
+
+        if (layer) {
+          GESClip *clip = GES_CLIP (ges_test_clip_new ());
+          GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
+
+          g_object_set (clip, "start", timeline_duration, "duration",
+              inpoint + duration, "vpattern", GES_VIDEO_TEST_PATTERN_SMPTE75,
+              NULL);
+          ges_layer_add_clip (layer, clip);
+          for (tmp = tracks; tmp; tmp = tmp->next) {
+            if (GES_IS_VIDEO_TRACK (tmp->data)) {
+              GESEffect *text;
+              GstCaps *caps;
+              gchar *effect_str_full = NULL;
+              const gchar *effect_str =
+                  "textoverlay text=\"Nested timeline too short, please FIX!\" halignment=center valignment=center";
+
+              g_object_get (tmp->data, "restriction-caps", &caps, NULL);
+              if (caps) {
+                gchar *caps_str = gst_caps_to_string (caps);
+                effect_str = effect_str_full =
+                    g_strdup_printf ("capsfilter caps=\"%s\" ! %s", caps_str,
+                    effect_str);
+                g_free (caps_str);
+                gst_caps_unref (caps);
+              }
+              text = ges_effect_new (effect_str);
+              g_free (effect_str_full);
+
+              if (!ges_container_add (GES_CONTAINER (clip),
+                      GES_TIMELINE_ELEMENT (text))) {
+                GST_ERROR ("Could not add text overlay to ending clip!");
+              }
+            }
+
+          }
+          g_list_free_full (tracks, gst_object_unref);
+          GST_INFO_OBJECT (timeline,
+              "Added test clip with duration: %" GST_TIME_FORMAT " - %"
+              GST_TIME_FORMAT " to match parent nleobject duration",
+              GST_TIME_ARGS (timeline_duration),
+              GST_TIME_ARGS (inpoint + duration - timeline_duration));
+        }
+      }
+      gst_object_unref (parent);
+
+      return;
+    }
+
+    tmpparent = parent;
+    parent = gst_object_get_parent (GST_OBJECT (parent));
+    gst_object_unref (tmpparent);
+  }
+}
+
+static gboolean
+ges_demux_create_timeline (GESDemux * self, gchar * uri, GError ** error)
+{
+  GESProject *project = ges_project_new (uri);
+  G_GNUC_UNUSED void *unused;
+  TimelineConstructionData data = { 0, };
+  GMainContext *ctx = g_main_context_new ();
+  GstQuery *query;
 
-  unused = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data->error));
-  if (data->error) {
-    g_mutex_unlock (&data->lock);
+  g_main_context_push_thread_default (ctx);
+  data.ml = g_main_loop_new (ctx, TRUE);
+
+  data.loaded_sigid =
+      g_signal_connect (project, "loaded", G_CALLBACK (project_loaded_cb),
+      &data);
+  data.error_asset_sigid =
+      g_signal_connect_after (project, "error-loading-asset",
+      G_CALLBACK (error_loading_asset_cb), &data);
+  data.error_sigid =
+      g_signal_connect_after (project, "error-loading",
+      G_CALLBACK (error_loading_cb), &data);
+
+  unused = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &data.error));
+  if (data.error) {
+    *error = data.error;
 
     goto done;
   }
-  g_mutex_unlock (&data->lock);
 
-  g_main_loop_run (data->ml);
-  g_main_loop_unref (data->ml);
+  g_main_loop_run (data.ml);
+  g_main_loop_unref (data.ml);
+  if (data.error)
+    goto done;
+
+  ges_demux_adapt_timeline_duration (self, data.timeline);
 
-done:
+  query = gst_query_new_uri ();
+  if (gst_pad_peer_query (self->sinkpad, query)) {
+    GList *assets, *tmp;
+
+    GST_OBJECT_LOCK (self);
+    g_free (self->upstream_uri);
+    gst_query_parse_uri (query, &self->upstream_uri);
+    if (gst_uri_has_protocol (self->upstream_uri, "file")) {
+      gchar *location = gst_uri_get_location (self->upstream_uri);
+
+      if (g_stat (location, &self->stats) < 0)
+        GST_INFO_OBJECT (self, "Could not stat file: %s", location);
+      g_free (location);
+    }
+
+    assets = ges_project_list_assets (project, GES_TYPE_URI_CLIP);
+    for (tmp = assets; tmp; tmp = tmp->next) {
+      const gchar *id = ges_asset_get_id (tmp->data);
 
-  g_mutex_lock (&data->lock);
+      if (!g_strcmp0 (id, self->upstream_uri)) {
+        g_set_error (error, GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX,
+            "Recursively loading uri: %s", self->upstream_uri);
+        break;
+      }
+    }
+    GST_OBJECT_UNLOCK (self);
+    g_list_free_full (assets, g_object_unref);
+  }
 
-  /* Set previous discoverer back! */
+done:
+  if (data.loaded_sigid)
+    g_signal_handler_disconnect (project, data.loaded_sigid);
 
-  if (klass->discoverer)
-    gst_object_unref (klass->discoverer);
-  klass->discoverer = previous_discoverer;
+  if (data.error_sigid)
+    g_signal_handler_disconnect (project, data.error_sigid);
 
-  if (data->timeline)
-    ges_timeline_commit (data->timeline);
+  if (data.error_asset_sigid)
+    g_signal_handler_disconnect (project, data.error_asset_sigid);
 
-  if (data->loaded_sigid)
-    g_signal_handler_disconnect (project, data->loaded_sigid);
+  g_clear_object (&project);
 
-  if (data->error_sigid)
-    g_signal_handler_disconnect (project, data->error_sigid);
+  GST_INFO_OBJECT (self, "Timeline properly loaded: %" GST_PTR_FORMAT,
+      data.timeline);
 
-  gst_clear_object (&project);
+  if (!data.error) {
+    ges_base_bin_set_timeline (GES_BASE_BIN (self), data.timeline);
+    gst_element_foreach_src_pad (GST_ELEMENT (self), ges_demux_set_srcpad_probe,
+        NULL);
+  } else {
+    *error = data.error;
+  }
 
-  g_cond_broadcast (&data->cond);
-  g_mutex_unlock (&data->lock);
+  g_main_context_pop_thread_default (ctx);
 
   return G_SOURCE_REMOVE;
 }
@@ -356,13 +521,23 @@ ges_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
 
       xges_buffer = gst_adapter_take_buffer (self->input_adapter, available);
       if (gst_buffer_map (xges_buffer, &map, GST_MAP_READ)) {
+        gint f;
         GError *err = NULL;
+        gchar *template = NULL;
         gchar *filename = NULL, *uri = NULL;
-        TimelineConstructionData data = { 0, };
-        gint f = g_file_open_tmp (NULL, &filename, &err);
-        GMainContext *main_context = g_main_context_default ();
+        GstCaps *caps = gst_pad_get_current_caps (pad);
+        GstStructure *structure = gst_caps_get_structure (caps, 0);
+        gchar *ext = ges_demux_get_extension (structure);
+
+        gst_caps_unref (caps);
+        if (ext) {
+          template = g_strdup_printf ("XXXXXX.%s", ext);
+          g_free (ext);
+        }
+
+        f = g_file_open_tmp (template, &filename, &err);
+        g_free (template);
 
-        GST_ERROR ("Loading %s", filename);
         if (err) {
           GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE,
               ("Could not open temporary file to write timeline description"),
@@ -381,43 +556,31 @@ ges_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
         }
 
         uri = gst_filename_to_uri (filename, NULL);
-        data.uri = uri;
-
-        g_main_context_invoke (main_context,
-            (GSourceFunc) ges_timeline_new_from_uri_from_main_thread, &data);
-        g_mutex_lock (&data.lock);
-        while (!data.error && !data.timeline)
-          g_cond_wait (&data.cond, &data.lock);
-        data.loaded_sigid = 0;
-        data.error_sigid = 0;
-        g_mutex_unlock (&data.lock);
-
-        if (data.error) {
-          GST_ELEMENT_ERROR (self, STREAM, DEMUX,
-              ("Could not create timeline from description"),
-              ("%s", data.error->message));
-          g_clear_error (&data.error);
+        GST_INFO_OBJECT (self, "Pre loading the timeline.");
 
+        ges_demux_create_timeline (self, uri, &err);
+        if (err)
           goto error;
-        }
 
-        GST_INFO_OBJECT (self, "Timeline properly loaded: %" GST_PTR_FORMAT,
-            data.timeline);
-        ges_demux_set_timeline (self, data.timeline);
       done:
         g_free (filename);
         g_free (uri);
         g_close (f, NULL);
         return ret;
+
       error:
         ret = FALSE;
+        gst_element_post_message (GST_ELEMENT (self),
+            gst_message_new_error (parent, err,
+                "Could not create timeline from description"));
+        g_clear_error (&err);
+
         goto done;
       } else {
         GST_ELEMENT_ERROR (self, RESOURCE, READ,
             ("Could not map buffer containing timeline description"),
             ("Not info"));
       }
-      GST_ERROR_OBJECT (xges_buffer, ":YAY");
     }
     default:
       break;
@@ -442,8 +605,15 @@ ges_demux_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
 static void
 ges_demux_init (GESDemux * self)
 {
-  ges_init ();
-  self->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
+  SUPRESS_UNUSED_WARNING (GES_DEMUX);
+  SUPRESS_UNUSED_WARNING (GES_IS_DEMUX);
+#if defined(g_autoptr)
+  SUPRESS_UNUSED_WARNING (glib_autoptr_cleanup_GESDemux);
+#endif
+
+  self->sinkpad =
+      gst_pad_new_from_template (gst_element_get_pad_template (GST_ELEMENT
+          (self), "sink"), "sink");
   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
 
   self->input_adapter = gst_adapter_new ();
diff --git a/plugins/ges/gesdemux.h b/plugins/ges/gesdemux.h
deleted file mode 100644 (file)
index eaa2b43..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/* GStreamer GES plugin
- *
- * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
- *
- * gesdemux.h
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef __GES_DEMUX_H__
-#define __GES_DEMUX_H__
-
-#include <gst/gst.h>
-#include <gst/base/gstadapter.h>
-#include <ges/ges.h>
-
-G_BEGIN_DECLS
-
-GType ges_demux_get_type (void);
-
-#define GES_DEMUX_TYPE (ges_demux_get_type ())
-#define GES_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_DEMUX_TYPE, GESDemux))
-#define GES_DEMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GES_DEMUX_TYPE, GESDemuxClass))
-#define GES_IS_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_DEMUX_TYPE))
-#define GES_IS_DEMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_DEMUX_TYPE))
-#define GES_DEMUX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_DEMUX_TYPE, GESDemuxClass))
-
-typedef struct {
-  GstBin parent;
-
-  GESTimeline *timeline;
-  GstPad *sinkpad;
-
-  GstAdapter *input_adapter;
-} GESDemux;
-
-typedef struct {
-  GstBinClass parent;
-
-} GESDemuxClass;
-
-G_END_DECLS
-#endif /* __GES_DEMUX_H__ */
-
index a12d6b7..fe5d27e 100644 (file)
 
 #include <gst/gst.h>
 
-#include "gessrc.h"
-#include "gesdemux.h"
+extern GType ges_demux_get_type ();
+extern GType ges_src_get_type ();
 
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
   gboolean res = 1;
 
-  res |= gst_element_register (plugin, "gessrc", GST_RANK_NONE, GES_SRC_TYPE);
+  res |=
+      gst_element_register (plugin, "gessrc", GST_RANK_NONE,
+      ges_src_get_type ());
 
   res |= gst_element_register (plugin, "gesdemux", GST_RANK_PRIMARY,
-      GES_DEMUX_TYPE);
+      ges_demux_get_type ());
 
   return res;
 }
index 89c9543..3e5cae8 100644 (file)
@@ -20,7 +20,7 @@
  *
 
  **
- * SECTION:gessrc
+ * SECTION:element-gessrc
  * @short_description: A GstBin subclasses use to use GESTimeline
  * as sources inside any GstPipeline.
  * @see_also: #GESTimeline
  * The gessrc is a bin that will simply expose the track src pads
  * and implements the GstUriHandler interface using a custom `ges://`
  * uri scheme.
- * 
+ *
  * NOTE: That to use it inside playbin and friends you **need** to
  * set gessrc::timeline property yourself.
- * 
+ *
  * Example with #playbin:
- * 
+ *
  * {{../../examples/c/gessrc.c}}
- * 
+ *
  * Example with #GstPlayer:
- * 
+ *
  * {{../../examples/python/gst-player.py}}
  **/
 
 #endif
 
 #include <gst/gst.h>
-#include "gessrc.h"
+#include <ges/ges.h>
+
+#include "gesbasebin.h"
 
 GST_DEBUG_CATEGORY_STATIC (gessrc);
 #define GST_CAT_DEFAULT gessrc
 
-static GstStaticPadTemplate video_src_template =
-GST_STATIC_PAD_TEMPLATE ("video_src",
-    GST_PAD_SRC,
-    GST_PAD_SOMETIMES,
-    GST_STATIC_CAPS ("video/x-raw(ANY)"));
-
-static GstStaticPadTemplate audio_src_template =
-    GST_STATIC_PAD_TEMPLATE ("audio_src",
-    GST_PAD_SRC,
-    GST_PAD_SOMETIMES,
-    GST_STATIC_CAPS ("audio/x-raw(ANY);"));
-
-enum
+G_DECLARE_FINAL_TYPE (GESSrc, ges_src, GES, SRC, GESBaseBin);
+struct _GESSrc
 {
-  PROP_0,
-  PROP_TIMELINE,
-  PROP_LAST
+  GESBaseBin parent;
 };
-
-static GParamSpec *properties[PROP_LAST];
-
-static gboolean
-ges_src_set_timeline (GESSrc * self, GESTimeline * timeline)
-{
-  GList *tmp;
-  guint naudiopad = 0, nvideopad = 0;
-  GstBin *sbin = GST_BIN (self);
-
-  g_return_val_if_fail (GES_IS_TIMELINE (timeline), FALSE);
-
-  if (self->timeline) {
-    GST_FIXME_OBJECT (self, "Implement changing timeline support");
-
-    return FALSE;
-  }
-
-  self->timeline = timeline;
-
-  gst_bin_add (sbin, GST_ELEMENT (self->timeline));
-  for (tmp = self->timeline->tracks; tmp; tmp = tmp->next) {
-    GstPad *gpad;
-    gchar *name = NULL;
-    GstElement *queue;
-    GESTrack *track = GES_TRACK (tmp->data);
-    GstPad *tmppad, *pad =
-        ges_timeline_get_pad_for_track (self->timeline, track);
-    GstStaticPadTemplate *template;
-
-    if (!pad) {
-      GST_INFO_OBJECT (self, "No pad for track: %" GST_PTR_FORMAT, track);
-
-      continue;
-    }
-
-    if (track->type == GES_TRACK_TYPE_AUDIO) {
-      name = g_strdup_printf ("audio_%u", naudiopad++);
-      template = &audio_src_template;
-    } else if (track->type == GES_TRACK_TYPE_VIDEO) {
-      name = g_strdup_printf ("video_%u", nvideopad++);
-      template = &video_src_template;
-    } else {
-      GST_INFO_OBJECT (self, "Track type not handled: %" GST_PTR_FORMAT, track);
-      continue;
-    }
-
-    queue = gst_element_factory_make ("queue", NULL);
-    /* Add queues the same way as in GESPipeline */
-    g_object_set (G_OBJECT (queue), "max-size-buffers", 0,
-        "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND, NULL);
-    gst_bin_add (GST_BIN (self), queue);
-
-    tmppad = gst_element_get_static_pad (queue, "sink");
-    if (gst_pad_link (pad, tmppad) != GST_PAD_LINK_OK) {
-      GST_ERROR ("Could not link %s:%s and %s:%s",
-          GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (tmppad));
-
-      gst_object_unref (tmppad);
-      gst_object_unref (queue);
-      continue;
-    }
-
-    tmppad = gst_element_get_static_pad (queue, "src");
-    gpad = gst_ghost_pad_new_from_template (name, tmppad,
-        gst_static_pad_template_get (template));
-
-    gst_pad_set_active (gpad, TRUE);
-    gst_element_add_pad (GST_ELEMENT (self), gpad);
-  }
-
-  gst_element_sync_state_with_parent (GST_ELEMENT (self->timeline));
-
-  return TRUE;
-}
+#define GES_SRC(obj) ((GESSrc*) obj)
 
 /*** GSTURIHANDLER INTERFACE *************************************************/
 
@@ -165,16 +80,37 @@ static gchar *
 ges_src_uri_get_uri (GstURIHandler * handler)
 {
   GESSrc *self = GES_SRC (handler);
+  GESTimeline *timeline = ges_base_bin_get_timeline (GES_BASE_BIN (self));
 
-  return self->timeline ? g_strdup_printf ("ges://%s",
-      GST_OBJECT_NAME (self->timeline)) : NULL;
+  return ges_command_line_formatter_get_timeline_uri (timeline);
 }
 
 static gboolean
-ges_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
+ges_src_uri_set_uri (GstURIHandler * handler, const gchar * uristr,
     GError ** error)
 {
-  return TRUE;
+  gboolean res = FALSE;
+  GstUri *uri = gst_uri_from_string (uristr);
+  GESProject *project = NULL;
+  GESTimeline *timeline = NULL;
+
+  if (!gst_uri_get_path (uri)) {
+    GST_INFO_OBJECT (handler, "User need to specify the timeline");
+    res = TRUE;
+    goto done;
+  }
+
+  project = ges_project_new (uristr);
+  timeline = (GESTimeline *) ges_asset_extract (GES_ASSET (project), NULL);
+
+  if (timeline)
+    res = ges_base_bin_set_timeline (GES_BASE_BIN (handler), timeline);
+
+done:
+  gst_uri_unref (uri);
+  gst_clear_object (&project);
+
+  return res;
 }
 
 static void
@@ -188,68 +124,27 @@ ges_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
   iface->set_uri = ges_src_uri_set_uri;
 }
 
-G_DEFINE_TYPE_WITH_CODE (GESSrc, ges_src, GST_TYPE_BIN,
+G_DEFINE_TYPE_WITH_CODE (GESSrc, ges_src, ges_base_bin_get_type (),
     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, ges_src_uri_handler_init));
 
 static void
-ges_src_get_property (GObject * object, guint property_id,
-    GValue * value, GParamSpec * pspec)
-{
-  GESSrc *self = GES_SRC (object);
-
-  switch (property_id) {
-    case PROP_TIMELINE:
-      g_value_set_object (value, self->timeline);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-  }
-}
-
-static void
-ges_src_set_property (GObject * object, guint property_id,
-    const GValue * value, GParamSpec * pspec)
-{
-  GESSrc *self = GES_SRC (object);
-
-  switch (property_id) {
-    case PROP_TIMELINE:
-      ges_src_set_timeline (self, g_value_get_object (value));
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-  }
-}
-
-static void
 ges_src_class_init (GESSrcClass * self_class)
 {
-  GObjectClass *gclass = G_OBJECT_CLASS (self_class);
   GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (self_class);
 
   GST_DEBUG_CATEGORY_INIT (gessrc, "gessrc", 0, "ges src element");
-
-  gclass->get_property = ges_src_get_property;
-  gclass->set_property = ges_src_set_property;
-
-  /**
-   * GESSrc:timeline:
-   *
-   * Timeline to use in this src.
-   */
-  properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
-      "Timeline to use in this src.",
-      GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-
-  g_object_class_install_properties (gclass, PROP_LAST, properties);
-
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&video_src_template));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&audio_src_template));
+  gst_element_class_set_static_metadata (gstelement_klass,
+      "GStreamer Editing Services based 'source'",
+      "Codec/Source/Editing",
+      "Source for GESTimeline.", "Thibault Saunier <tsaunier@igalia.com");
 }
 
 static void
 ges_src_init (GESSrc * self)
 {
+  SUPRESS_UNUSED_WARNING (GES_SRC);
+  SUPRESS_UNUSED_WARNING (GES_IS_SRC);
+#if defined(g_autoptr)
+  SUPRESS_UNUSED_WARNING (glib_autoptr_cleanup_GESSrc);
+#endif
 }
index bbad6ab..14a1712 100644 (file)
@@ -1,4 +1,4 @@
-gstges_sources = ['gesplugin.c', 'gessrc.c', 'gesdemux.c']
+gstges_sources = ['gesplugin.c', 'gessrc.c', 'gesdemux.c', 'gesbasebin.c']
 
 gstges = library('gstges', gstges_sources,
   dependencies : [gst_dep, ges_dep],
@@ -8,3 +8,4 @@ gstges = library('gstges', gstges_sources,
   install_dir : plugins_install_dir,
 )
 pkgconfig.generate(gstges, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstges]
\ No newline at end of file
index 7fa5b4b..7190450 100644 (file)
@@ -1,2 +1,3 @@
+plugins = []
 subdir('nle')
 subdir('ges')
\ No newline at end of file
diff --git a/plugins/nle/.gitignore b/plugins/nle/.gitignore
deleted file mode 100644 (file)
index 3ac440b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Makefile
-Makefile.in
-*.o
-*.lo
-*.la
-.deps
-.libs
-nleversion.h
diff --git a/plugins/nle/Makefile.am b/plugins/nle/Makefile.am
deleted file mode 100644 (file)
index 5805235..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-plugin_LTLIBRARIES = libgstnle.la
-
-libgstnle_la_SOURCES = gstnle.c \
-       nleobject.c             \
-       nlecomposition.c        \
-       nleghostpad.c           \
-       nleoperation.c          \
-       nlesource.c             \
-       nleurisource.c
-
-libgstnle_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \
-       $(GST_BASE_CFLAGS) $(GST_CFLAGS) \
-       -I$(top_srcdir)
-
-libgstnle_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \
-       $(GST_BASE_LIBS) $(GST_LIBS)
-
-libgstnle_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
-
-noinst_HEADERS = \
-       nle.h                   \
-       nleobject.h             \
-       nlecomposition.h        \
-       nletypes.h              \
-       nleghostpad.h           \
-       nleoperation.h          \
-       nlesource.h             \
-       nletypes.h              \
-       nleurisource.h
index 29b41a5..74f0938 100644 (file)
@@ -15,3 +15,4 @@ nle = library('gstnle', nle_sources,
   install_dir : plugins_install_dir,
 )
 pkgconfig.generate(nle, install_dir : plugins_pkgconfig_install_dir)
+plugins += [nle]
index 85be290..97fa1b9 100644 (file)
@@ -43,16 +43,19 @@ GST_DEBUG_CATEGORY_STATIC (nlecomposition_debug);
 #define GST_CAT_DEFAULT nlecomposition_debug
 
 #define _do_init              \
-  GST_DEBUG_CATEGORY_INIT (nlecomposition_debug,"nlecomposition", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Composition");
+  GST_DEBUG_CATEGORY_INIT (nlecomposition_debug,"nlecomposition", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "NLE Composition");
 #define nle_composition_parent_class parent_class
 
 enum
 {
   PROP_0,
-  PROP_DEACTIVATED_ELEMENTS_STATE,
+  PROP_ID,
+  PROP_DROP_TAGS,
   PROP_LAST,
 };
 
+#define DEFAULT_DROP_TAGS TRUE
+
 /* Properties from NleObject */
 enum
 {
@@ -79,7 +82,7 @@ typedef enum
 } NleUpdateStackReason;
 
 static const char *UPDATE_PIPELINE_REASONS[] = {
-  "Initialize", "Commit", "EOS", "Seek"
+  "Initialize", "Commit", "EOS", "Seek", "None"
 };
 
 typedef struct
@@ -194,11 +197,19 @@ struct _NleCompositionPrivate
   /* 0 means that we already received the right caps or segment */
   gint seqnum_to_restart_task;
   gboolean waiting_serialized_query_or_buffer;
+  GstEvent *stack_initialization_seek;
+  gboolean stack_initialization_seek_sent;
 
   gboolean tearing_down_stack;
   gboolean suppress_child_error;
 
   NleUpdateStackReason updating_reason;
+
+  guint seek_seqnum;
+
+  /* Both protected with object lock */
+  gchar *id;
+  gboolean drop_tags;
 };
 
 #define ACTION_CALLBACK(__action) (((GCClosure*) (__action))->callback)
@@ -206,6 +217,7 @@ struct _NleCompositionPrivate
 static guint _signals[LAST_SIGNAL] = { 0 };
 
 static GParamSpec *nleobject_properties[NLEOBJECT_PROP_LAST];
+static GParamSpec *properties[PROP_LAST];
 
 G_DEFINE_TYPE_WITH_CODE (NleComposition, nle_composition, NLE_TYPE_OBJECT,
     G_ADD_PRIVATE (NleComposition)
@@ -253,14 +265,14 @@ static void _update_pipeline_func (NleComposition * comp,
 static void _commit_func (NleComposition * comp,
     UpdateCompositionData * ucompo);
 static GstEvent *get_new_seek_event (NleComposition * comp, gboolean initial,
-    gboolean updatestoponly);
+    gboolean updatestoponly, NleUpdateStackReason reason);
 static gboolean _nle_composition_add_object (NleComposition * comp,
     NleObject * object);
 static gboolean _nle_composition_remove_object (NleComposition * comp,
     NleObject * object);
 static void _deactivate_stack (NleComposition * comp,
-    gboolean flush_downstream);
-static gboolean _set_real_eos_seqnum_from_seek (NleComposition * comp,
+    NleUpdateStackReason reason);
+static void _set_real_eos_seqnum_from_seek (NleComposition * comp,
     GstEvent * event);
 static void _emit_commited_signal_func (NleComposition * comp, gpointer udata);
 static void _restart_task (NleComposition * comp);
@@ -439,6 +451,8 @@ _start_task (NleComposition * comp)
     gst_task_set_lock (task, GET_TASK_LOCK (comp));
     GST_DEBUG_OBJECT (comp, "created task %p", task);
     comp->task = task;
+    gst_object_set_parent (GST_OBJECT (task), GST_OBJECT (comp));
+    gst_object_unref (task);
     g_free (taskname);
   }
 
@@ -447,6 +461,22 @@ _start_task (NleComposition * comp)
 }
 
 static gboolean
+_pause_task (NleComposition * comp)
+{
+  GST_OBJECT_LOCK (comp);
+  if (comp->task == NULL) {
+    GST_INFO_OBJECT (comp, "No task set, it must have been stopped, returning");
+    GST_OBJECT_UNLOCK (comp);
+    return FALSE;
+  }
+
+  gst_task_pause (comp->task);
+  GST_OBJECT_UNLOCK (comp);
+
+  return TRUE;
+}
+
+static gboolean
 _stop_task (NleComposition * comp)
 {
   gboolean res = TRUE;
@@ -474,7 +504,7 @@ _stop_task (NleComposition * comp)
   if (!gst_task_join (task))
     goto join_failed;
 
-  gst_object_unref (task);
+  gst_object_unparent (GST_OBJECT (task));
 
   return res;
 
@@ -539,16 +569,36 @@ _seek_pipeline_func (NleComposition * comp, SeekData * seekd)
   GstSeekType cur_type, stop_type;
   gint64 cur, stop;
   NleCompositionPrivate *priv = comp->priv;
+  gboolean initializing_stack = priv->stack_initialization_seek == seekd->event;
+  NleUpdateStackReason reason =
+      initializing_stack ? COMP_UPDATE_STACK_NONE : COMP_UPDATE_STACK_ON_SEEK;
+  GstClockTime segment_start, segment_stop;
+  gboolean reverse;
 
   gst_event_parse_seek (seekd->event, &rate, &format, &flags,
       &cur_type, &cur, &stop_type, &stop);
 
+  reverse = rate < 0;
+
   GST_DEBUG_OBJECT (seekd->comp,
       "start:%" GST_TIME_FORMAT " -- stop:%" GST_TIME_FORMAT "  flags:%d",
       GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), flags);
 
+  if (!initializing_stack) {
+    segment_start = cur;
+    segment_stop = stop;
+  } else {
+    /* During plain playback (no seek), the segment->stop doesn't
+     * evolve when going from stack to stack, only the start does
+     * (in reverse playback, the logic is reversed) */
+    segment_start = reverse ? priv->segment->start : cur;
+    segment_stop = reverse ? stop : priv->segment->stop;
+  }
+
   gst_segment_do_seek (priv->segment,
-      rate, format, flags, cur_type, cur, stop_type, stop, NULL);
+      rate, format, flags, cur_type, segment_start, stop_type, segment_stop,
+      NULL);
+
   gst_segment_do_seek (priv->seek_segment,
       rate, format, flags, cur_type, cur, stop_type, stop, NULL);
 
@@ -569,8 +619,9 @@ _seek_pipeline_func (NleComposition * comp, SeekData * seekd)
   }
 #endif
 
-  _post_start_composition_update (seekd->comp,
-      gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK);
+  if (!initializing_stack)
+    _post_start_composition_update (seekd->comp,
+        gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK);
 
   /* crop the segment start/stop values */
   /* Only crop segment start value if we don't have a default object */
@@ -580,18 +631,26 @@ _seek_pipeline_func (NleComposition * comp, SeekData * seekd)
   priv->segment->stop =
       MIN (priv->segment->stop, NLE_OBJECT_STOP (seekd->comp));
 
-  priv->next_base_time = 0;
 
-  seek_handling (seekd->comp, gst_event_get_seqnum (seekd->event),
-      COMP_UPDATE_STACK_ON_SEEK);
+  if (initializing_stack) {
+    GST_INFO_OBJECT (seekd->comp, "Pausing task to run initializing seek.");
+    _pause_task (seekd->comp);
+  } else {
+    priv->next_base_time = 0;
+    comp->priv->flush_seqnum = comp->priv->seek_seqnum =
+        gst_event_get_seqnum (seekd->event);
+  }
+
+  seek_handling (seekd->comp, gst_event_get_seqnum (seekd->event), reason);
 
-  _post_start_composition_update_done (seekd->comp,
-      gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK);
+  if (!initializing_stack)
+    _post_start_composition_update_done (seekd->comp,
+        gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK);
 }
 
 /*  Must be called with OBJECTS_LOCK taken */
 static void
-_process_pending_entries (NleComposition * comp)
+_process_pending_entries (NleComposition * comp, NleUpdateStackReason reason)
 {
   NleObject *object;
   GHashTableIter iter;
@@ -607,7 +666,7 @@ _process_pending_entries (NleComposition * comp)
           deactivated_stack == FALSE) {
         deactivated_stack = TRUE;
 
-        _deactivate_stack (comp, TRUE);
+        _deactivate_stack (comp, reason);
       }
 
       _nle_composition_remove_object (comp, object);
@@ -641,13 +700,13 @@ _commit_values (NleComposition * comp)
 }
 
 static gboolean
-_commit_all_values (NleComposition * comp)
+_commit_all_values (NleComposition * comp, NleUpdateStackReason reason)
 {
   NleCompositionPrivate *priv = comp->priv;
 
   priv->next_base_time = 0;
 
-  _process_pending_entries (comp);
+  _process_pending_entries (comp, reason);
 
   if (_commit_values (comp) == FALSE) {
 
@@ -671,7 +730,7 @@ _initialize_stack_func (NleComposition * comp, UpdateCompositionData * ucompo)
 
   _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason);
 
-  _commit_all_values (comp);
+  _commit_all_values (comp, ucompo->reason);
   update_start_stop_duration (comp);
   comp->priv->next_base_time = 0;
   /* set ghostpad target */
@@ -829,7 +888,7 @@ _add_action_locked (NleComposition * comp, GCallback func,
   GST_INFO_OBJECT (comp, "Adding Action for function: %p:%s",
       action, GST_DEBUG_FUNCPTR_NAME (func));
 
-  if (func == G_CALLBACK (_emit_commited_signal_func))
+  if (priority == G_PRIORITY_HIGH)
     priv->actions = g_list_prepend (priv->actions, action);
   else
     priv->actions = g_list_append (priv->actions, action);
@@ -849,6 +908,17 @@ _add_action (NleComposition * comp, GCallback func,
   ACTIONS_UNLOCK (comp);
 }
 
+static SeekData *
+create_seek_data (NleComposition * comp, GstEvent * event)
+{
+  SeekData *seekd = g_slice_new0 (SeekData);
+
+  seekd->comp = comp;
+  seekd->event = event;
+
+  return seekd;
+}
+
 static void
 _add_seek_action (NleComposition * comp, GstEvent * event)
 {
@@ -895,14 +965,12 @@ _add_seek_action (NleComposition * comp, GstEvent * event)
     }
   }
 
-  GST_DEBUG_OBJECT (comp, "Adding Action");
-
-  seekd = g_slice_new0 (SeekData);
-  seekd->comp = comp;
-  seekd->event = event;
+  GST_DEBUG_OBJECT (comp, "Adding seek Action");
+  seekd = create_seek_data (comp, event);
 
   comp->priv->next_eos_seqnum = 0;
   comp->priv->real_eos_seqnum = 0;
+  comp->priv->seek_seqnum = 0;
   _add_action_locked (comp, G_CALLBACK (_seek_pipeline_func), seekd,
       G_PRIORITY_DEFAULT);
 
@@ -967,6 +1035,51 @@ drop:
 }
 
 static void
+nle_composition_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
+{
+  NleComposition *comp = (NleComposition *) object;
+
+  switch (property_id) {
+    case PROP_ID:
+      GST_OBJECT_LOCK (comp);
+      g_value_set_string (value, comp->priv->id);
+      GST_OBJECT_UNLOCK (comp);
+      break;
+    case PROP_DROP_TAGS:
+      GST_OBJECT_LOCK (comp);
+      g_value_set_boolean (value, comp->priv->drop_tags);
+      GST_OBJECT_UNLOCK (comp);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (comp, property_id, pspec);
+  }
+}
+
+static void
+nle_composition_set_property (GObject * object, guint property_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  NleComposition *comp = (NleComposition *) object;
+
+  switch (property_id) {
+    case PROP_ID:
+      GST_OBJECT_LOCK (comp);
+      g_free (comp->priv->id);
+      comp->priv->id = g_value_dup_string (value);
+      GST_OBJECT_UNLOCK (comp);
+      break;
+    case PROP_DROP_TAGS:
+      GST_OBJECT_LOCK (comp);
+      comp->priv->drop_tags = g_value_get_boolean (value);
+      GST_OBJECT_UNLOCK (comp);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (comp, property_id, pspec);
+  }
+}
+
+static void
 nle_composition_class_init (NleCompositionClass * klass)
 {
   GObjectClass *gobject_class;
@@ -987,6 +1100,10 @@ nle_composition_class_init (NleCompositionClass * klass)
 
   gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_composition_dispose);
   gobject_class->finalize = GST_DEBUG_FUNCPTR (nle_composition_finalize);
+  gobject_class->get_property =
+      GST_DEBUG_FUNCPTR (nle_composition_get_property);
+  gobject_class->set_property =
+      GST_DEBUG_FUNCPTR (nle_composition_set_property);
 
   gstelement_class->change_state = nle_composition_change_state;
 
@@ -1008,10 +1125,29 @@ nle_composition_class_init (NleCompositionClass * klass)
   nleobject_properties[NLEOBJECT_PROP_DURATION] =
       g_object_class_find_property (gobject_class, "duration");
 
+  properties[PROP_ID] =
+      g_param_spec_string ("id", "Id", "The stream-id of the composition",
+      NULL,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_DOC_SHOW_DEFAULT);
+
+  /**
+   * NleComposition:drop-tags:
+   *
+   * Whether the composition should drop tags from its children
+   *
+   * Since: 1.20
+   */
+  properties[PROP_DROP_TAGS] =
+      g_param_spec_boolean ("drop-tags", "Drop tags",
+      "Whether the composition should drop tags from its children",
+      DEFAULT_DROP_TAGS,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_DOC_SHOW_DEFAULT |
+      GST_PARAM_MUTABLE_PLAYING);
+  g_object_class_install_properties (gobject_class, PROP_LAST, properties);
+
   _signals[COMMITED_SIGNAL] =
       g_signal_new ("commited", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
-      0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
-      G_TYPE_BOOLEAN);
+      0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
 
   GST_DEBUG_REGISTER_FUNCPTR (_seek_pipeline_func);
   GST_DEBUG_REGISTER_FUNCPTR (_remove_object_func);
@@ -1059,6 +1195,9 @@ nle_composition_init (NleComposition * comp)
 
   nle_composition_reset (comp);
 
+  priv->id = gst_pad_create_stream_id (NLE_OBJECT_SRC (comp),
+      GST_ELEMENT (comp), NULL);
+  priv->drop_tags = DEFAULT_DROP_TAGS;
   priv->nle_event_pad_func = GST_PAD_EVENTFUNC (NLE_OBJECT_SRC (comp));
   gst_pad_set_event_function (NLE_OBJECT_SRC (comp),
       GST_DEBUG_FUNCPTR (nle_composition_event_handler));
@@ -1105,6 +1244,7 @@ nle_composition_dispose (GObject * object)
   g_list_free (priv->objects_stop);
 
   g_list_free_full (priv->actions, (GDestroyNotify) _remove_each_action);
+  g_clear_object (&priv->stack_initialization_seek);
 
   nle_composition_reset_target_pad (comp);
 
@@ -1137,6 +1277,7 @@ nle_composition_finalize (GObject * object)
 
   g_mutex_clear (&priv->actions_lock);
   g_cond_clear (&priv->actions_cond);
+  g_free (priv->id);
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -1205,8 +1346,8 @@ nle_composition_reset (NleComposition * comp)
   nle_composition_reset_target_pad (comp);
 
   priv->initialized = FALSE;
-  priv->send_stream_start = TRUE;
   priv->real_eos_seqnum = 0;
+  priv->seek_seqnum = 0;
   priv->next_eos_seqnum = 0;
   priv->flush_seqnum = 0;
 
@@ -1223,8 +1364,33 @@ ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
   NleCompositionPrivate *priv = comp->priv;
   GstEvent *event;
 
-  if (GST_IS_BUFFER (info->data) ||
-      (GST_IS_QUERY (info->data) && GST_QUERY_IS_SERIALIZED (info->data))) {
+  if (GST_IS_BUFFER (info->data) || (GST_IS_QUERY (info->data)
+          && GST_QUERY_IS_SERIALIZED (info->data))) {
+
+    if (priv->stack_initialization_seek) {
+      if (g_atomic_int_compare_and_exchange
+          (&priv->stack_initialization_seek_sent, FALSE, TRUE)) {
+        _add_action (comp, G_CALLBACK (_seek_pipeline_func),
+            create_seek_data (comp,
+                gst_event_ref (priv->stack_initialization_seek)),
+            G_PRIORITY_HIGH);
+
+        GST_OBJECT_LOCK (comp);
+        if (comp->task)
+          gst_task_start (comp->task);
+        GST_OBJECT_UNLOCK (comp);
+
+        priv->send_stream_start =
+            priv->updating_reason == COMP_UPDATE_STACK_INITIALIZE;
+      }
+
+      GST_DEBUG_OBJECT (comp,
+          "Dropping %" GST_PTR_FORMAT " while sending initializing stack seek",
+          info->data);
+
+      return GST_PAD_PROBE_DROP;
+    }
+
     if (priv->waiting_serialized_query_or_buffer) {
       GST_INFO_OBJECT (comp, "update_pipeline DONE");
       _restart_task (comp);
@@ -1235,46 +1401,82 @@ ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
 
   event = GST_PAD_PROBE_INFO_EVENT (info);
 
-  GST_DEBUG_OBJECT (comp, "event: %s", GST_EVENT_TYPE_NAME (event));
+  GST_LOG_OBJECT (comp, "event: %s", GST_EVENT_TYPE_NAME (event));
 
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_FLUSH_STOP:
       if (_is_ready_to_restart_task (comp, event))
         _restart_task (comp);
 
+      if (g_atomic_int_compare_and_exchange
+          (&priv->stack_initialization_seek_sent, TRUE, FALSE)) {
+        GST_INFO_OBJECT (comp, "Done seeking initialization stack.");
+        gst_clear_event (&priv->stack_initialization_seek);
+      }
+
       if (gst_event_get_seqnum (event) != comp->priv->flush_seqnum) {
-        GST_INFO_OBJECT (comp, "Dropping flush stop %d -- %d",
-            gst_event_get_seqnum (event), priv->seqnum_to_restart_task);
+        GST_INFO_OBJECT (comp, "Dropping FLUSH_STOP %d -- %d",
+            gst_event_get_seqnum (event), priv->flush_seqnum);
         retval = GST_PAD_PROBE_DROP;
       } else {
-        GST_INFO_OBJECT (comp, "Forwarding our flush stop with seqnum %i",
+        GST_INFO_OBJECT (comp, "Forwarding FLUSH_STOP with seqnum %i",
             comp->priv->flush_seqnum);
         gst_event_unref (event);
         event = gst_event_new_flush_stop (TRUE);
         GST_PAD_PROBE_INFO_DATA (info) = event;
-        gst_event_set_seqnum (event, comp->priv->flush_seqnum);
+        if (comp->priv->seek_seqnum) {
+          GST_EVENT_SEQNUM (event) = comp->priv->seek_seqnum;
+        } else {
+          GST_EVENT_SEQNUM (event) = comp->priv->flush_seqnum;
+        }
+        GST_INFO_OBJECT (comp, "Set FLUSH_STOP seqnum: %d",
+            GST_EVENT_SEQNUM (event));
         comp->priv->flush_seqnum = 0;
       }
       break;
     case GST_EVENT_FLUSH_START:
       if (gst_event_get_seqnum (event) != comp->priv->flush_seqnum) {
-        GST_INFO_OBJECT (comp, "Dropping flush start");
+        GST_INFO_OBJECT (comp, "Dropping FLUSH_START %d != %d",
+            gst_event_get_seqnum (event), comp->priv->flush_seqnum);
         retval = GST_PAD_PROBE_DROP;
       } else {
-        GST_INFO_OBJECT (comp, "Forwarding our flush start with seqnum %i",
+        GST_INFO_OBJECT (comp, "Forwarding FLUSH_START with seqnum %d",
             comp->priv->flush_seqnum);
+        if (comp->priv->seek_seqnum) {
+          GST_EVENT_SEQNUM (event) = comp->priv->seek_seqnum;
+          GST_INFO_OBJECT (comp, "Setting FLUSH_START seqnum: %d",
+              comp->priv->seek_seqnum);
+        }
       }
       break;
     case GST_EVENT_STREAM_START:
       if (g_atomic_int_compare_and_exchange (&priv->send_stream_start, TRUE,
               FALSE)) {
-        /* FIXME: Do we want to create a new stream ID here? */
-        GST_DEBUG_OBJECT (comp, "forward stream-start %p", event);
+
+        gst_event_unref (event);
+        event = info->data = gst_event_new_stream_start (priv->id);
+        GST_INFO_OBJECT (comp, "forward stream-start %p (%s)", event, priv->id);
       } else {
         GST_DEBUG_OBJECT (comp, "dropping stream-start %p", event);
         retval = GST_PAD_PROBE_DROP;
       }
       break;
+    case GST_EVENT_STREAM_GROUP_DONE:
+      if (GST_EVENT_SEQNUM (event) != comp->priv->real_eos_seqnum) {
+        GST_INFO_OBJECT (comp, "Dropping STREAM_GROUP_DONE %d != %d",
+            GST_EVENT_SEQNUM (event), comp->priv->real_eos_seqnum);
+        retval = GST_PAD_PROBE_DROP;
+      }
+      break;
+    case GST_EVENT_CAPS:
+    {
+      if (priv->stack_initialization_seek) {
+        GST_INFO_OBJECT (comp,
+            "Waiting for preroll to send initializing seek, dropping caps.");
+        return GST_PAD_PROBE_DROP;
+      }
+      break;
+    }
     case GST_EVENT_SEGMENT:
     {
       guint64 rstart, rstop;
@@ -1283,6 +1485,11 @@ ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
       GstEvent *event2;
       /* next_base_time */
 
+      if (priv->stack_initialization_seek) {
+        GST_INFO_OBJECT (comp, "Waiting for preroll to send initializing seek");
+        return GST_PAD_PROBE_DROP;
+      }
+
       if (_is_ready_to_restart_task (comp, event))
         _restart_task (comp);
 
@@ -1302,7 +1509,10 @@ ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
       comp->priv->next_base_time += rstop - rstart;
 
       event2 = gst_event_new_segment (&copy);
-      GST_EVENT_SEQNUM (event2) = GST_EVENT_SEQNUM (event);
+      if (comp->priv->seek_seqnum)
+        GST_EVENT_SEQNUM (event2) = comp->priv->seek_seqnum;
+      else
+        GST_EVENT_SEQNUM (event2) = GST_EVENT_SEQNUM (event);
 
       GST_PAD_PROBE_INFO_DATA (info) = event2;
       gst_event_unref (event);
@@ -1310,7 +1520,10 @@ ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
       break;
     case GST_EVENT_TAG:
       GST_DEBUG_OBJECT (comp, "Dropping tag: %" GST_PTR_FORMAT, info->data);
-      retval = GST_PAD_PROBE_DROP;
+      GST_OBJECT_LOCK (comp);
+      if (comp->priv->drop_tags)
+        retval = GST_PAD_PROBE_DROP;
+      GST_OBJECT_UNLOCK (comp);
       break;
     case GST_EVENT_EOS:
     {
@@ -1332,6 +1545,9 @@ ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
         GST_INFO_OBJECT (comp, "Got EOS for real, seq ID is %i, fowarding it",
             seqnum);
 
+        if (comp->priv->seek_seqnum)
+          GST_EVENT_SEQNUM (event) = comp->priv->seek_seqnum;
+
         return GST_PAD_PROBE_OK;
       }
 
@@ -1411,7 +1627,7 @@ nle_composition_commit_func (NleObject * object, gboolean recurse)
  */
 static GstEvent *
 get_new_seek_event (NleComposition * comp, gboolean initial,
-    gboolean updatestoponly)
+    gboolean updatestoponly, NleUpdateStackReason reason)
 {
   GstSeekFlags flags = GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH;
   gint64 start, stop;
@@ -1433,12 +1649,18 @@ get_new_seek_event (NleComposition * comp, gboolean initial,
       GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->stop),
       GST_TIME_ARGS (priv->current_stack_stop));
 
-  start = GST_CLOCK_TIME_IS_VALID (priv->segment->start)
-      ? MAX (priv->segment->start, priv->current_stack_start)
-      : priv->current_stack_start;
-  stop = GST_CLOCK_TIME_IS_VALID (priv->segment->stop)
-      ? MIN (priv->segment->stop, priv->current_stack_stop)
-      : priv->current_stack_stop;
+  if (reason == COMP_UPDATE_STACK_INITIALIZE
+      || reason == COMP_UPDATE_STACK_ON_EOS) {
+    start = priv->current_stack_start;
+    stop = priv->current_stack_stop;
+  } else {
+    start = GST_CLOCK_TIME_IS_VALID (priv->segment->start)
+        ? MAX (priv->segment->start, priv->current_stack_start)
+        : priv->current_stack_start;
+    stop = GST_CLOCK_TIME_IS_VALID (priv->segment->stop)
+        ? MIN (priv->segment->stop, priv->current_stack_stop)
+        : priv->current_stack_stop;
+  }
 
   if (updatestoponly) {
     starttype = GST_SEEK_TYPE_NONE;
@@ -1454,7 +1676,29 @@ get_new_seek_event (NleComposition * comp, gboolean initial,
       priv->segment->format, flags, starttype, start, GST_SEEK_TYPE_SET, stop);
 }
 
-/* OBJECTS LOCK must be taken when calling this ! */
+static gboolean
+nle_composition_needs_topelevel_initializing_seek (NleComposition * comp)
+{
+  GstObject *parent;
+
+  parent = gst_object_get_parent (GST_OBJECT (comp));
+  while (parent) {
+    if (NLE_IS_COMPOSITION (parent)
+        && NLE_COMPOSITION (parent)->priv->stack_initialization_seek) {
+      gst_object_unref (parent);
+      GST_INFO_OBJECT (comp,
+          "Not sending an initializing seek as %" GST_PTR_FORMAT
+          "is gonna seek anyway!", parent);
+      return FALSE;
+    }
+
+    gst_object_unref (parent);
+    parent = gst_object_get_parent (parent);
+  }
+
+  return TRUE;
+}
+
 static GstClockTime
 get_current_position (NleComposition * comp)
 {
@@ -1554,31 +1798,6 @@ beach:
 
 /* WITH OBJECTS LOCK TAKEN */
 static gboolean
-update_base_time (GNode * node, GstClockTime * timestamp)
-{
-  if (NLE_IS_OPERATION (node->data))
-    nle_operation_update_base_time (NLE_OPERATION (node->data), *timestamp);
-
-  return FALSE;
-}
-
-/* WITH OBJECTS LOCK TAKEN */
-static void
-update_operations_base_time (NleComposition * comp, gboolean reverse)
-{
-  GstClockTime timestamp;
-
-  if (reverse)
-    timestamp = comp->priv->segment->stop;
-  else
-    timestamp = comp->priv->segment->start;
-
-  g_node_traverse (comp->priv->current, G_IN_ORDER, G_TRAVERSE_ALL, -1,
-      (GNodeTraverseFunc) update_base_time, &timestamp);
-}
-
-/* WITH OBJECTS LOCK TAKEN */
-static gboolean
 _seek_current_stack (NleComposition * comp, GstEvent * event,
     gboolean flush_downstream)
 {
@@ -1589,6 +1808,7 @@ _seek_current_stack (NleComposition * comp, GstEvent * event,
   GST_INFO_OBJECT (comp, "Seeking itself %" GST_PTR_FORMAT, event);
 
   if (!peer) {
+    gst_event_unref (event);
     GST_ERROR_OBJECT (comp, "Can't seek because no pad available - "
         "no children in the composition ready to be used, the duration is 0, "
         "or not committed yet");
@@ -1635,13 +1855,13 @@ seek_handling (NleComposition * comp, gint32 seqnum,
       update_pipeline (comp, comp->priv->segment->stop, seqnum,
           update_stack_reason);
   } else {
-    GstEvent *toplevel_seek = get_new_seek_event (comp, FALSE, FALSE);
+    GstEvent *toplevel_seek = get_new_seek_event (comp, FALSE, FALSE,
+        update_stack_reason);
 
     gst_event_set_seqnum (toplevel_seek, seqnum);
     _set_real_eos_seqnum_from_seek (comp, toplevel_seek);
 
     _remove_update_actions (comp);
-    update_operations_base_time (comp, !(comp->priv->segment->rate >= 0.0));
     _seek_current_stack (comp, toplevel_seek,
         _have_to_flush_downstream (update_stack_reason));
   }
@@ -1789,7 +2009,6 @@ nle_composition_reset_target_pad (NleComposition * comp)
 
   nle_object_ghost_pad_set_target (NLE_OBJECT (comp),
       NLE_OBJECT_SRC (comp), NULL);
-  priv->send_stream_start = TRUE;
 }
 
 /* nle_composition_ghost_pad_set_target:
@@ -2044,8 +2263,6 @@ get_stack_list (NleComposition * comp, GstClockTime timestamp,
               GST_OBJECT_NAME (object));
           stack = g_list_insert_sorted (stack, object,
               (GCompareFunc) priority_comp);
-          if (NLE_IS_OPERATION (object))
-            nle_operation_update_base_time (NLE_OPERATION (object), timestamp);
         }
       } else {
         GST_LOG_OBJECT (comp, "too far, stopping iteration");
@@ -2071,8 +2288,6 @@ get_stack_list (NleComposition * comp, GstClockTime timestamp,
               GST_OBJECT_NAME (object));
           stack = g_list_insert_sorted (stack, object,
               (GCompareFunc) priority_comp);
-          if (NLE_IS_OPERATION (object))
-            nle_operation_update_base_time (NLE_OPERATION (object), timestamp);
         }
       } else {
         GST_LOG_OBJECT (comp, "too far, stopping iteration");
@@ -2089,8 +2304,6 @@ get_stack_list (NleComposition * comp, GstClockTime timestamp,
           GST_OBJECT_NAME (tmp->data));
       stack = g_list_insert_sorted (stack, tmp->data,
           (GCompareFunc) priority_comp);
-      if (NLE_IS_OPERATION (tmp->data))
-        nle_operation_update_base_time (NLE_OPERATION (tmp->data), timestamp);
     }
 
   /* convert that list to a stack */
@@ -2159,7 +2372,7 @@ get_clean_toplevel_stack (NleComposition * comp, GstClockTime * timestamp,
     return NULL;
   }
 
-  GST_DEBUG ("start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT,
+  GST_DEBUG_OBJECT (comp, "start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT,
       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
 
   if (stack) {
@@ -2202,7 +2415,7 @@ _drop_all_cb (GstPad * pad G_GNUC_UNUSED,
 
 /*  Must be called with OBJECTS_LOCK taken */
 static void
-_set_current_bin_to_ready (NleComposition * comp, gboolean flush_downstream)
+_set_current_bin_to_ready (NleComposition * comp, NleUpdateStackReason reason)
 {
   gint probe_id = -1;
   GstPad *ptarget = NULL;
@@ -2210,7 +2423,7 @@ _set_current_bin_to_ready (NleComposition * comp, gboolean flush_downstream)
   GstEvent *flush_event;
 
   comp->priv->tearing_down_stack = TRUE;
-  if (flush_downstream) {
+  if (_have_to_flush_downstream (reason)) {
     ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT_SRC (comp)));
     if (ptarget) {
 
@@ -2225,7 +2438,11 @@ _set_current_bin_to_ready (NleComposition * comp, gboolean flush_downstream)
       GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe);
 
       flush_event = gst_event_new_flush_start ();
-      priv->flush_seqnum = gst_event_get_seqnum (flush_event);
+      if (reason != COMP_UPDATE_STACK_ON_SEEK)
+        priv->flush_seqnum = gst_event_get_seqnum (flush_event);
+      else
+        gst_event_set_seqnum (flush_event, priv->seek_seqnum);
+
       GST_INFO_OBJECT (comp, "sending flushes downstream with seqnum %d",
           priv->flush_seqnum);
       gst_pad_push_event (ptarget, flush_event);
@@ -2238,8 +2455,9 @@ _set_current_bin_to_ready (NleComposition * comp, gboolean flush_downstream)
   gst_element_set_state (priv->current_bin, GST_STATE_READY);
 
   if (ptarget) {
-    if (flush_downstream) {
+    if (_have_to_flush_downstream (reason)) {
       flush_event = gst_event_new_flush_stop (TRUE);
+
       gst_event_set_seqnum (flush_event, priv->flush_seqnum);
 
       /* Force ad activation so that the event can actually travel.
@@ -2277,6 +2495,7 @@ _restart_task (NleComposition * comp)
 
   comp->priv->seqnum_to_restart_task = 0;
   comp->priv->waiting_serialized_query_or_buffer = FALSE;
+  gst_clear_event (&comp->priv->stack_initialization_seek);
 
   comp->priv->updating_reason = COMP_UPDATE_STACK_NONE;
   GST_OBJECT_LOCK (comp);
@@ -2335,7 +2554,7 @@ _commit_func (NleComposition * comp, UpdateCompositionData * ucompo)
    * before commiting children */
   curpos = get_current_position (comp);
 
-  if (!_commit_all_values (comp)) {
+  if (!_commit_all_values (comp, ucompo->reason)) {
     GST_DEBUG_OBJECT (comp, "Nothing to commit, leaving");
 
     g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, FALSE);
@@ -2427,6 +2646,16 @@ _update_pipeline_func (NleComposition * comp, UpdateCompositionData * ucompo)
   _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason);
 }
 
+/* Never call when ->task runs! */
+static void
+_set_all_children_state (NleComposition * comp, GstState state)
+{
+  GList *tmp;
+
+  for (tmp = comp->priv->objects_start; tmp; tmp = tmp->next)
+    gst_element_set_state (tmp->data, state);
+}
+
 static GstStateChangeReturn
 nle_composition_change_state (GstElement * element, GstStateChange transition)
 {
@@ -2439,16 +2668,9 @@ nle_composition_change_state (GstElement * element, GstStateChange transition)
 
   switch (transition) {
     case GST_STATE_CHANGE_NULL_TO_READY:
+      _set_all_children_state (comp, GST_STATE_READY);
       _start_task (comp);
       break;
-    case GST_STATE_CHANGE_READY_TO_PAUSED:
-      /* state-lock all elements */
-      GST_DEBUG_OBJECT (comp,
-          "Setting all children to READY and locking their state");
-
-      _add_update_compo_action (comp, G_CALLBACK (_initialize_stack_func),
-          COMP_UPDATE_STACK_INITIALIZE);
-      break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       _stop_task (comp);
 
@@ -2462,6 +2684,7 @@ nle_composition_change_state (GstElement * element, GstStateChange transition)
 
       _remove_update_actions (comp);
       _remove_seek_actions (comp);
+      _set_all_children_state (comp, GST_STATE_NULL);
       comp->priv->tearing_down_stack = TRUE;
       break;
     default:
@@ -2485,6 +2708,14 @@ nle_composition_change_state (GstElement * element, GstStateChange transition)
   }
 
   switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      /* state-lock all elements */
+      GST_DEBUG_OBJECT (comp,
+          "Setting all children to READY and locking their state");
+
+      _add_update_compo_action (comp, G_CALLBACK (_initialize_stack_func),
+          COMP_UPDATE_STACK_INITIALIZE);
+      break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       comp->priv->tearing_down_stack = FALSE;
       nle_composition_reset (comp);
@@ -2543,6 +2774,8 @@ update_start_stop_duration (NleComposition * comp)
 {
   NleObject *obj;
   NleObject *cobj = (NleObject *) comp;
+  gboolean reverse = (comp->priv->segment->rate < 0);
+  GstClockTime prev_stop = NLE_OBJECT_STOP (comp);
   NleCompositionPrivate *priv = comp->priv;
 
   _assert_proper_thread (comp);
@@ -2614,7 +2847,9 @@ update_start_stop_duration (NleComposition * comp)
       }
     }
 
-    priv->segment->stop = obj->stop;
+    if (reverse || priv->segment->stop == prev_stop
+        || obj->stop < priv->segment->stop)
+      priv->segment->stop = obj->stop;
     cobj->stop = obj->stop;
     g_object_notify_by_pspec (G_OBJECT (cobj),
         nleobject_properties[NLEOBJECT_PROP_STOP]);
@@ -2714,9 +2949,7 @@ _relink_single_node (NleComposition * comp, GNode * node,
 {
   NleObject *newobj;
   NleObject *newparent;
-  GNode *node_it;
   GstPad *srcpad = NULL, *sinkpad = NULL;
-  GstEvent *translated_seek;
 
   if (G_UNLIKELY (!node))
     return;
@@ -2727,22 +2960,11 @@ _relink_single_node (NleComposition * comp, GNode * node,
   GST_DEBUG_OBJECT (comp, "newobj:%s",
       GST_ELEMENT_NAME ((GstElement *) newobj));
 
-  newobj->recursive_media_duration_factor = 1.0f;
-  for (node_it = node; node_it != NULL; node_it = node_it->parent) {
-    NleObject *object = (NleObject *) node_it->data;
-    newobj->recursive_media_duration_factor *= object->media_duration_factor;
-  }
-
   srcpad = NLE_OBJECT_SRC (newobj);
 
   gst_bin_add (GST_BIN (comp->priv->current_bin), GST_ELEMENT (newobj));
   gst_element_sync_state_with_parent (GST_ELEMENT_CAST (newobj));
 
-  translated_seek = nle_object_translate_incoming_seek (newobj,
-      gst_event_ref (toplevel_seek));
-
-  gst_element_send_event (GST_ELEMENT (newobj), translated_seek);
-
   /* link to parent if needed.  */
   if (newparent) {
     _link_to_parent (comp, newobj, newparent);
@@ -2778,13 +3000,13 @@ _relink_single_node (NleComposition * comp, GNode * node,
  */
 
 static void
-_deactivate_stack (NleComposition * comp, gboolean flush_downstream)
+_deactivate_stack (NleComposition * comp, NleUpdateStackReason reason)
 {
   GstPad *ptarget;
 
-  GST_INFO_OBJECT (comp, "Deactivating current stack (flushing downstream: %d)",
-      flush_downstream);
-  _set_current_bin_to_ready (comp, flush_downstream);
+  GST_INFO_OBJECT (comp, "Deactivating current stack (reason: %s)",
+      UPDATE_PIPELINE_REASONS[reason]);
+  _set_current_bin_to_ready (comp, reason);
 
   ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT_SRC (comp)));
   _empty_bin (GST_BIN_CAST (comp->priv->current_bin));
@@ -2875,7 +3097,7 @@ beach:
 }
 
 static inline gboolean
-_activate_new_stack (NleComposition * comp)
+_activate_new_stack (NleComposition * comp, GstEvent * toplevel_seek)
 {
   GstPad *pad;
   GstElement *topelement;
@@ -2891,10 +3113,23 @@ _activate_new_stack (NleComposition * comp)
 
     GST_DEBUG_OBJECT (comp, "Nothing else in the composition"
         ", update 'worked'");
+    gst_event_unref (toplevel_seek);
     goto resync_state;
   }
 
-  /* The stack is entirely ready, send seek out synchronously */
+  /* The stack is entirely ready, stack initializing seek once ready */
+  GST_INFO_OBJECT (comp, "Activating stack with seek: %" GST_PTR_FORMAT,
+      toplevel_seek);
+
+  if (!toplevel_seek) {
+    GST_INFO_OBJECT (comp,
+        "This is a sub composition, not seeking to initialize stack");
+    g_atomic_int_set (&priv->send_stream_start, TRUE);
+  } else {
+    GST_INFO_OBJECT (comp, "Needs seeking to initialize stack");
+    comp->priv->stack_initialization_seek = toplevel_seek;
+  }
+
   topelement = GST_ELEMENT (priv->current->data);
   /* Get toplevel object source pad */
   pad = NLE_OBJECT_SRC (topelement);
@@ -2907,6 +3142,8 @@ _activate_new_stack (NleComposition * comp)
   GST_DEBUG_OBJECT (comp, "New stack activated!");
 
 resync_state:
+  if (toplevel_seek)
+    g_atomic_int_set (&priv->stack_initialization_seek_sent, FALSE);
   gst_element_set_locked_state (priv->current_bin, FALSE);
 
   GST_DEBUG ("going back to parent state");
@@ -2928,42 +3165,52 @@ resync_state:
   return TRUE;
 }
 
-/* WITH OBJECTS LOCK TAKEN */
-static gboolean
+static void
 _set_real_eos_seqnum_from_seek (NleComposition * comp, GstEvent * event)
 {
   GList *tmp;
 
-  gboolean should_check_objects = FALSE;
   NleCompositionPrivate *priv = comp->priv;
   gboolean reverse = (priv->segment->rate < 0);
   gint stack_seqnum = gst_event_get_seqnum (event);
 
-  if (reverse && GST_CLOCK_TIME_IS_VALID (priv->current_stack_start))
-    should_check_objects = TRUE;
-  else if (!reverse && GST_CLOCK_TIME_IS_VALID (priv->current_stack_stop))
-    should_check_objects = TRUE;
+  if (reverse) {
+    if (!GST_CLOCK_TIME_IS_VALID (priv->current_stack_start))
+      goto done;
 
-  if (should_check_objects) {
-    for (tmp = priv->objects_stop; tmp; tmp = g_list_next (tmp)) {
-      NleObject *object = (NleObject *) tmp->data;
+    if (priv->segment->start != 0 &&
+        priv->current_stack_start <= priv->segment->start
+        && priv->current_stack_stop > priv->segment->start) {
+      goto done;
+    }
+  } else {
+    if (!GST_CLOCK_TIME_IS_VALID (priv->current_stack_stop))
+      goto done;
 
-      if (!NLE_IS_SOURCE (object))
-        continue;
+    if (GST_CLOCK_TIME_IS_VALID (priv->seek_segment->stop) &&
+        priv->current_stack_start <= priv->segment->stop
+        && priv->current_stack_stop >= priv->segment->stop) {
+      goto done;
+    }
+  }
 
-      if ((!reverse && priv->current_stack_stop < object->stop) ||
-          (reverse && priv->current_stack_start > object->start)) {
-        priv->next_eos_seqnum = stack_seqnum;
-        g_atomic_int_set (&priv->real_eos_seqnum, 0);
-        return FALSE;
-      }
+  for (tmp = priv->objects_stop; tmp; tmp = g_list_next (tmp)) {
+    NleObject *object = (NleObject *) tmp->data;
+
+    if (!NLE_IS_SOURCE (object))
+      continue;
+
+    if ((!reverse && priv->current_stack_stop < object->stop) ||
+        (reverse && priv->current_stack_start > object->start)) {
+      priv->next_eos_seqnum = stack_seqnum;
+      g_atomic_int_set (&priv->real_eos_seqnum, 0);
+      return;
     }
   }
 
+done:
   priv->next_eos_seqnum = stack_seqnum;
   g_atomic_int_set (&priv->real_eos_seqnum, stack_seqnum);
-
-  return TRUE;
 }
 
 #ifndef GST_DISABLE_GST_DEBUG
@@ -2971,10 +3218,14 @@ static gboolean
 _print_stack (GNode * node, gpointer res)
 {
   NleObject *obj = NLE_OBJECT (node->data);
+  gint i;
+
+  for (i = 0; i < (g_node_depth (node) - 1) * 4; ++i)
+    g_string_append_c ((GString *) res, ' ');
 
   g_string_append_printf ((GString *) res,
-      "%*s [s=%" GST_TIME_FORMAT " - d=%" GST_TIME_FORMAT "] prio=%d\n",
-      g_node_depth (node) * 4, GST_OBJECT_NAME (obj),
+      "%s [s=%" GST_TIME_FORMAT " - d=%" GST_TIME_FORMAT "] prio=%d\n",
+      GST_OBJECT_NAME (obj),
       GST_TIME_ARGS (NLE_OBJECT_START (obj)),
       GST_TIME_ARGS (NLE_OBJECT_STOP (obj)), obj->priority);
 
@@ -2983,7 +3234,8 @@ _print_stack (GNode * node, gpointer res)
 #endif
 
 static void
-_dump_stack (NleComposition * comp, GNode * stack)
+_dump_stack (NleComposition * comp, NleUpdateStackReason update_reason,
+    GNode * stack)
 {
 #ifndef GST_DISABLE_GST_DEBUG
   GString *res;
@@ -2995,10 +3247,11 @@ _dump_stack (NleComposition * comp, GNode * stack)
     return;
 
   res = g_string_new (NULL);
-  g_string_append_printf (res, " ====> dumping stack [%" GST_TIME_FORMAT " - %"
-      GST_TIME_FORMAT "]:\n",
-      GST_TIME_ARGS (comp->priv->current_stack_start),
-      GST_TIME_ARGS (comp->priv->current_stack_stop));
+  g_string_append_printf (res,
+      " ====> dumping stack [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT
+      "] (%s):\n", GST_TIME_ARGS (comp->priv->current_stack_start),
+      GST_TIME_ARGS (comp->priv->current_stack_stop),
+      UPDATE_PIPELINE_REASONS[update_reason]);
   g_node_traverse (stack, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, _print_stack, res);
 
   GST_INFO_OBJECT (comp, "%s", res->str);
@@ -3006,6 +3259,23 @@ _dump_stack (NleComposition * comp, GNode * stack)
 #endif
 }
 
+static gboolean
+nle_composition_query_needs_teardown (NleComposition * comp,
+    NleUpdateStackReason reason)
+{
+  gboolean res = FALSE;
+  GstStructure *structure =
+      gst_structure_new ("NleCompositionQueryNeedsTearDown", "reason",
+      G_TYPE_STRING, UPDATE_PIPELINE_REASONS[reason], NULL);
+  GstQuery *query = gst_query_new_custom (GST_QUERY_CUSTOM, structure);
+
+  gst_pad_query (NLE_OBJECT_SRC (comp), query);
+  gst_structure_get_boolean (structure, "result", &res);
+
+  gst_query_unref (query);
+  return res;
+}
+
 /*
  * update_pipeline:
  * @comp: The #NleComposition
@@ -3027,7 +3297,7 @@ update_pipeline (NleComposition * comp, GstClockTime currenttime, gint32 seqnum,
   GstEvent *toplevel_seek;
 
   GNode *stack = NULL;
-  gboolean samestack = FALSE;
+  gboolean tear_down = FALSE;
   gboolean updatestoponly = FALSE;
   GstState state = GST_STATE (comp);
   NleCompositionPrivate *priv = comp->priv;
@@ -3065,7 +3335,8 @@ update_pipeline (NleComposition * comp, GstClockTime currenttime, gint32 seqnum,
 
   /* Get new stack and compare it to current one */
   stack = get_clean_toplevel_stack (comp, &currenttime, &new_start, &new_stop);
-  samestack = are_same_stacks (priv->current, stack);
+  tear_down = !are_same_stacks (priv->current, stack)
+      || nle_composition_query_needs_teardown (comp, update_reason);
 
   /* set new current_stack_start/stop (the current zone over which the new stack
    * is valid) */
@@ -3092,7 +3363,7 @@ update_pipeline (NleComposition * comp, GstClockTime currenttime, gint32 seqnum,
     stopchanged = priv->current_stack_stop != currenttime;
   }
 
-  if (samestack) {
+  if (!tear_down) {
     if (startchanged || stopchanged) {
       /* Update seek events need to be flushing if not in PLAYING,
        * else we will encounter deadlocks. */
@@ -3101,17 +3372,18 @@ update_pipeline (NleComposition * comp, GstClockTime currenttime, gint32 seqnum,
   }
 #endif
 
-  toplevel_seek = get_new_seek_event (comp, TRUE, updatestoponly);
+  toplevel_seek =
+      get_new_seek_event (comp, TRUE, updatestoponly, update_reason);
   gst_event_set_seqnum (toplevel_seek, seqnum);
   _set_real_eos_seqnum_from_seek (comp, toplevel_seek);
 
   _remove_update_actions (comp);
 
   /* If stacks are different, unlink/relink objects */
-  if (!samestack) {
-    _dump_stack (comp, stack);
-    _deactivate_stack (comp, _have_to_flush_downstream (update_reason));
-    _relink_new_stack (comp, stack, toplevel_seek);
+  if (tear_down) {
+    _dump_stack (comp, update_reason, stack);
+    _deactivate_stack (comp, update_reason);
+    _relink_new_stack (comp, stack, gst_event_ref (toplevel_seek));
   }
 
   /* Unlock all elements in new stack */
@@ -3133,24 +3405,31 @@ update_pipeline (NleComposition * comp, GstClockTime currenttime, gint32 seqnum,
     comp->priv->updating_reason = update_reason;
     comp->priv->seqnum_to_restart_task = seqnum;
 
-    GST_OBJECT_LOCK (comp);
-    if (comp->task == NULL) {
-      GST_INFO_OBJECT (comp,
-          "No task set, it must have been stopped, returning");
-      GST_OBJECT_UNLOCK (comp);
-      return FALSE;
-    }
+    /* Subcomposition can preroll without sending initializing seeks
+     * as the toplevel composition will send it anyway.
+     *
+     * This avoid seeking round trips (otherwise we get 1 extra seek
+     * per level of nesting)
+     */
 
-    gst_task_pause (comp->task);
-    GST_OBJECT_UNLOCK (comp);
+    if (tear_down && !nle_composition_needs_topelevel_initializing_seek (comp))
+      gst_clear_event (&toplevel_seek);
+
+    if (toplevel_seek) {
+      if (!_pause_task (comp)) {
+        gst_event_unref (toplevel_seek);
+        return FALSE;
+      }
+    } else {
+      GST_INFO_OBJECT (comp, "Not pausing composition when first initializing");
+    }
   }
 
   /* Activate stack */
-  if (!samestack)
-    return _activate_new_stack (comp);
-  else
-    return _seek_current_stack (comp, toplevel_seek,
-        _have_to_flush_downstream (update_reason));
+  if (tear_down)
+    return _activate_new_stack (comp, toplevel_seek);
+  return _seek_current_stack (comp, toplevel_seek,
+      _have_to_flush_downstream (update_reason));
 }
 
 static gboolean
@@ -3273,7 +3552,6 @@ nle_composition_remove_object (GstBin * bin, GstElement * element)
 
   object = NLE_OBJECT (element);
 
-  object->in_composition = FALSE;
   _add_remove_object_action (comp, object);
 
   return TRUE;
index cda0b0d..cb1e5ec 100644 (file)
@@ -98,6 +98,12 @@ nle_object_translate_incoming_seek (NleObject * object, GstEvent * event)
     GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT,
         GST_TIME_ARGS (nstop));
   } else {
+    /* NOTE: for an element that is upstream from a time effect we do not
+     * want to limit the seek to the object->stop because a time effect
+     * can reasonably increase the final time.
+     * In such situations, the seek stop should be set once by the
+     * source pad of the most downstream object (highest priority in the
+     * nlecomposition). */
     GST_DEBUG_OBJECT (object, "Limiting end of seek to media_stop");
     nle_object_to_media_time (object, object->stop, &nstop);
     if (nstop > G_MAXINT64)
@@ -190,6 +196,9 @@ translate_outgoing_seek (NleObject * object, GstEvent * event)
     GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT,
         GST_TIME_ARGS (nstop));
   } else {
+    /* NOTE: when we have time effects, the object stop is not the
+     * correct stop limit. Therefore, the seek stop time should already
+     * be set at this point */
     GST_DEBUG_OBJECT (object, "Limiting end of seek to stop");
     nstop = object->stop;
     if (nstop > G_MAXINT64)
@@ -423,6 +432,9 @@ translate_incoming_duration_query (NleObject * object, GstQuery * query)
     return FALSE;
   }
 
+  /* NOTE: returns the duration of the object, but this is not the same
+   * as the source duration when time effects are used. Nor is it the
+   * duration of the current nlecomposition stack */
   gst_query_set_duration (query, GST_FORMAT_TIME, object->duration);
 
   return TRUE;
@@ -492,7 +504,7 @@ ghostpad_event_function (GstPad * ghostpad, GstObject * parent,
 
           event = nle_object_translate_incoming_seek (object, event);
           if (!(target = gst_ghost_pad_get_target (GST_GHOST_PAD (ghostpad)))) {
-            g_assert ("Seeked a pad with not target SHOULD NOT HAPPEND");
+            g_assert ("Seeked a pad with no target SHOULD NOT HAPPEN");
             ret = FALSE;
             gst_event_unref (event);
             event = NULL;
index 46694a4..e746dd1 100644 (file)
  * SECTION:nleobject
  * @short_description: Base class for GNonLin elements
  *
- * <refsect2>
- * <para>
  * NleObject encapsulates default behaviour and implements standard
  * properties provided by all the GNonLin elements.
- * </para>
- * </refsect2>
- *
  */
 
 
@@ -253,11 +248,15 @@ nle_object_class_init (NleObjectClass * klass)
    * relation between the rate of media entering and leaving this object. I.e.
    * if object pulls data at twice the speed it sends it (e.g. `pitch
    * tempo=2.0`), this value is set to 2.0.
+   *
+   * Deprecated: 1.18: This property is ignored since the wrapped
+   * #GstElement-s themselves should internally perform any additional time
+   * translations.
    */
   properties[PROP_MEDIA_DURATION_FACTOR] =
       g_param_spec_double ("media-duration-factor", "Media duration factor",
       "The relative rate caused by this object", 0.01, G_MAXDOUBLE,
-      1.0, G_PARAM_READWRITE);
+      1.0, G_PARAM_READWRITE | G_PARAM_DEPRECATED);
   g_object_class_install_property (gobject_class, PROP_MEDIA_DURATION_FACTOR,
       properties[PROP_MEDIA_DURATION_FACTOR]);
 
@@ -279,6 +278,8 @@ nle_object_class_init (NleObjectClass * klass)
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (NleObjectClass, commit_signal_handler), NULL, NULL, NULL,
       G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+
+  gst_type_mark_as_plugin_api (NLE_TYPE_OBJECT, 0);
 }
 
 static void
@@ -297,8 +298,6 @@ nle_object_init (NleObject * object, NleObjectClass * klass)
   object->segment_rate = 1.0;
   object->segment_start = -1;
   object->segment_stop = -1;
-  object->media_duration_factor = 1.0;
-  object->recursive_media_duration_factor = 1.0;
 
   object->srcpad = nle_object_ghost_pad_no_target (object,
       "src", GST_PAD_SRC,
@@ -331,15 +330,41 @@ nle_object_dispose (GObject * object)
  * @objecttime: The #GstClockTime we want to convert
  * @mediatime: A pointer on a #GstClockTime to fill
  *
- * Converts a #GstClockTime from the object (container) context to the media context
+ * Converts a #GstClockTime timestamp received from another nleobject pad
+ * in the same nlecomposition, or from the nlecomposition itself, to an
+ * internal source time.
+ *
+ * If the object is furthest downstream in the nlecomposition (highest
+ * priority in the current stack), this will convert the timestamp from
+ * the composition coordinate time to the internal source coordinate time
+ * of the object.
+ *
+ * If the object is upstream from another nleobject, then this can convert
+ * the timestamp received from the downstream sink pad to the internal
+ * source coordinates of the object, to be passed to its internal
+ * elements.
+ *
+ * If the object is downstream from another nleobject, then this can
+ * convert the timestamp received from the upstream source pad to the
+ * internal sink coordinates of the object, to be passed to its internal
+ * elements.
+ *
+ * In these latter two cases, the timestamp should have been converted
+ * by the peer pad using nle_media_to_object_time().
  *
- * Returns: TRUE if @objecttime was within the limits of the @object start/stop time,
- * FALSE otherwise
+ * Note, if an object introduces a time effect, it must have a 0 in-point
+ * and the same #nleobject:start and #nleobject:duration as all the other
+ * objects that are further upstream.
+ *
+ * Returns: TRUE if @objecttime was below the @object start time,
+ * FALSE otherwise.
  */
 gboolean
 nle_object_to_media_time (NleObject * object, GstClockTime otime,
     GstClockTime * mtime)
 {
+  gboolean ret = TRUE;
+
   g_return_val_if_fail (mtime, FALSE);
 
   GST_DEBUG_OBJECT (object, "ObjectTime : %" GST_TIME_FORMAT,
@@ -350,39 +375,61 @@ nle_object_to_media_time (NleObject * object, GstClockTime otime,
       "Media start: %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start),
       GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint));
 
-  /* limit check */
-  if (G_UNLIKELY ((otime < object->start))) {
-    GST_DEBUG_OBJECT (object, "ObjectTime is before start");
-    *mtime = (object->inpoint == GST_CLOCK_TIME_NONE) ? 0 : object->inpoint;
-    return FALSE;
+  if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (otime))) {
+    GST_DEBUG_OBJECT (object, "converting none object time to none");
+    *mtime = GST_CLOCK_TIME_NONE;
+    return TRUE;
   }
 
-  if (G_UNLIKELY ((otime >= object->stop))) {
-    GST_DEBUG_OBJECT (object, "ObjectTime is after stop");
-    if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint)))
-      *mtime =
-          object->inpoint +
-          object->duration * object->recursive_media_duration_factor;
-    else
-      *mtime =
-          (object->stop -
-          object->start) * object->recursive_media_duration_factor;
-    return FALSE;
+  /* We do not allow @otime to be below the start of the object.
+   * If it was below, then the object would have a negative external
+   * source/sink time.
+   *
+   * Note that ges only supports time effects that map the time 0 to
+   * 0. Such time effects would also not produce an external timestamp
+   * below start, nor can they receive such a timestamp. */
+  if (G_UNLIKELY ((otime < object->start))) {
+    GST_DEBUG_OBJECT (object, "ObjectTime is before start");
+    otime = object->start;
+    ret = FALSE;
   }
+  /* NOTE: if an nlecomposition contains time effect operations, then
+   * @otime can reasonably exceed the stop time of the object. So we
+   * do not limit it here. */
+
+  /* first we convert the timestamp to the object's external source/sink
+   * coordinates:
+   * + For an object that is furthest downstream, we translate from the
+   *   composition coordinates to the external source coordinates by
+   *   subtracting the object start.
+   * + For an object that is upstream from d_object, we need to
+   *   translate from its external sink coordinates to our external
+   *   source coordinates. This is done by adding
+   *     (d_object->start - object->start)
+   *   However, the sink pad of d_object should have already added the
+   *   d_object->start to the timestamp (see nle_media_to_object_time)
+   *   so we also only need to subtract the object start.
+   * + For an object that is downstream from u_object, we need to
+   *   translate from its external source coordinates to our external
+   *   sink coordinates. This is similarly done by adding
+   *     (u_object->start - object->start)
+   *   However, the source pad of u_object should have already added the
+   *   u_object->start to the timestamp (see nle_media_to_object_time)
+   *   so we also only need to subtract the object start.
+   */
+  *mtime = otime - object->start;
 
-  if (G_UNLIKELY (object->inpoint == GST_CLOCK_TIME_NONE)) {
-    /* no time shifting, for live sources ? */
-    *mtime = (otime - object->start) * object->recursive_media_duration_factor;
-  } else {
-    *mtime =
-        (otime - object->start) * object->recursive_media_duration_factor +
-        object->inpoint;
-  }
+  /* we then convert the timestamp from the object's external source/sink
+   * coordinates to its internal source/sink coordinates, to be used by
+   * internal elements that the object wraps. This is done by adding
+   * the object in-point. */
+  if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint)))
+    *mtime += object->inpoint;
 
   GST_DEBUG_OBJECT (object, "Returning MediaTime : %" GST_TIME_FORMAT,
       GST_TIME_ARGS (*mtime));
 
-  return TRUE;
+  return ret;
 }
 
 /**
@@ -391,10 +438,28 @@ nle_object_to_media_time (NleObject * object, GstClockTime otime,
  * @mediatime: The #GstClockTime we want to convert
  * @objecttime: A pointer on a #GstClockTime to fill
  *
- * Converts a #GstClockTime from the media context to the object (container) context
+ * Converts a #GstClockTime timestamp from an internal time to an
+ * nleobject pad time.
+ *
+ * If the object is furthest downstream in an nlecomposition (highest
+ * priority in the current stack), this will convert the timestamp from
+ * the internal source coordinate time of the object to the composition
+ * coordinate time.
  *
- * Returns: TRUE if @objecttime was within the limits of the @object media start/stop time,
- * FALSE otherwise
+ * If the object is upstream from another nleobject, then this can convert
+ * the timestamp from the internal source coordinates of the object to be
+ * sent to the downstream sink pad.
+ *
+ * If the object is downstream from another nleobject, then this can
+ * convert the timestamp from the internal sink coordinates of the object
+ * to be sent to the upstream source pad.
+ *
+ * Note, if an object introduces a time effect, it must have a 0 in-point
+ * and the same #nleobject:start and #nleobject:duration as all the other
+ * objects that are further upstream.
+ *
+ * Returns: TRUE if @objecttime was below the @object in-point time,
+ * FALSE otherwise.
  */
 
 gboolean
@@ -411,19 +476,35 @@ nle_media_to_object_time (NleObject * object, GstClockTime mtime,
       "inpoint  %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start),
       GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint));
 
+  if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (mtime))) {
+    GST_DEBUG_OBJECT (object, "converting none media time to none");
+    *otime = GST_CLOCK_TIME_NONE;
+    return TRUE;
+  }
 
-  /* limit check */
-  if (G_UNLIKELY ((object->inpoint != GST_CLOCK_TIME_NONE)
+  /* the internal time should never go below the in-point! */
+  if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint)
           && (mtime < object->inpoint))) {
     GST_DEBUG_OBJECT (object, "media time is before inpoint, forcing to start");
     *otime = object->start;
     return FALSE;
   }
 
-  if (G_LIKELY (object->inpoint != GST_CLOCK_TIME_NONE)) {
-    *otime = mtime - object->inpoint + object->start;
-  } else
-    *otime = mtime + object->start;
+  /* first we convert the timestamp to the object's external source/sink
+   * coordinates by removing the in-point */
+  if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint)))
+    *otime = mtime - object->inpoint;
+  else
+    *otime = mtime;
+
+  /* then we convert the timestamp by adding start.
+   * If the object is furthest downstream, this will translate it from
+   * the external source coordinates to the composition coordinates.
+   * Otherwise, this will perform part of the conversion from the object's
+   * source/sink coordinates to the downstream/upstream sink/source
+   * coordinates (the conversion is completed in
+   * nle_object_to_media_time). */
+  *otime += object->start;
 
   GST_DEBUG_OBJECT (object, "Returning ObjectTime : %" GST_TIME_FORMAT,
       GST_TIME_ARGS (*otime));
@@ -568,7 +649,12 @@ nle_object_set_property (GObject * object, guint prop_id,
         GST_OBJECT_FLAG_UNSET (nleobject, NLE_OBJECT_EXPANDABLE);
       break;
     case PROP_MEDIA_DURATION_FACTOR:
-      nleobject->media_duration_factor = g_value_get_double (value);
+    {
+      gdouble val = g_value_get_double (value);
+      if (val != 1.0)
+        g_warning ("Ignoring media-duration-factor value of %g since the "
+            "property is deprecated", val);
+    }
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -609,7 +695,7 @@ nle_object_get_property (GObject * object, guint prop_id,
       g_value_set_boolean (value, NLE_OBJECT_IS_EXPANDABLE (object));
       break;
     case PROP_MEDIA_DURATION_FACTOR:
-      g_value_set_double (value, nleobject->media_duration_factor);
+      g_value_set_double (value, 1.0);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -743,12 +829,13 @@ nle_object_reset (NleObject * object)
   object->inpoint = GST_CLOCK_TIME_NONE;
   object->priority = 0;
   object->active = TRUE;
+  object->in_composition = FALSE;
 }
 
 GType
 nle_object_get_type (void)
 {
-  static volatile gsize type = 0;
+  static gsize type = 0;
 
   if (g_once_init_enter (&type)) {
     GType _type;
index 5af9717..1e0272e 100644 (file)
@@ -126,12 +126,6 @@ struct _NleObject
   gint64 segment_start;
   gint64 segment_stop;
 
-  /* the rate at which this object speeds up or slows down media */
-  gdouble media_duration_factor;
-  /* the rate at which this object and all of its children speed up or slow
-   * down media */
-  gdouble recursive_media_duration_factor;
-
   gboolean in_composition;
 };
 
index 5a9801b..b39fdd7 100644 (file)
 /**
  * SECTION:element-nleoperation
  *
- * <refsect2>
- * <para>
  * A NleOperation performs a transformation or mixing operation on the
- * data from one or more #NleSources, which is used to implement filters or 
+ * data from one or more #NleSources, which is used to implement filters or
  * effects.
- * </para>
- * </refsect2>
+ *
+ * ## Time Effects
+ *
+ * An #nleoperation that wraps a #GstElement that transforms seek and
+ * segment times is considered a time effect. Nle only tries to support
+ * time effect's whose overall seek transformation:
+ *
+ * + Maps the time `0` to `0`. So initial time-shifting effects are
+ *   excluded (the #NleObject:inpoint can sometimes be used instead).
+ * + Is monotonically increasing. So reversing effects, and effects that
+ *   jump backwards in the stream are excluded.
+ * + Can handle a reasonable #GstClockTime, relative to the project. So
+ *   this would exclude a time effect with an extremely large speed-up
+ *   that would cause the converted #GstClockTime seeks to overflow.
+ * + Is 'continuously reversible'. This essentially means that for every
+ *   seek position found on the sink pad of the element, we can, to 'good
+ *   enough' accuracy, calculate the corresponding seek position that was
+ *   received on the source pad. Moreover, this calculation should
+ *   correspond to how the element transforms its #GstSegment
+ *   @time field. This is needed so that a seek can result in an accurate
+ *   segment.
+ *
+ * Note that a constant-rate-change effect that is not extremely fast or
+ * slow would satisfy these conditions.
+ *
+ * For such a time effect, they should be configured in nle such that:
+ *
+ * + Their #NleObject:inpoint is `0`. Otherwise this will introduce
+ *   seeking problems in its #nlecomposition.
+ * + They must share the same #NleObject:start and
+ *   #NleObject:duration as all nleobjects of lower priority in its
+ *   #nlecomposition. Otherwise this will introduce jumps in playback.
+ *
+ * Note that, at the moment, nle only converts the #GstSegment
+ * @time field which means the other fields, such as @start and @stop, can
+ * end up with non-meaningful values when time effects are involved.
+ * Moreover, it does not convert #GstBuffer times either, which can result
+ * in them having non-meaningful values. In particular, this means that
+ * #GstControlBinding-s will not work well with #nlecomposition-s when
+ * they include time effects.
  */
 
 static GstStaticPadTemplate nle_operation_src_template =
@@ -135,7 +171,7 @@ nle_operation_class_init (NleOperationClass * klass)
    *
    * Specifies the number of sink pads the operation should provide.
    * If the sinks property is -1 (the default) pads are only created as
-   * demanded via get_request_pad() calls on the element.
+   * demanded via `get_request_pad()` calls on the element.
    */
   g_object_class_install_property (gobject_class, ARG_SINKS,
       g_param_spec_int ("sinks", "Sinks",
@@ -153,7 +189,7 @@ nle_operation_class_init (NleOperationClass * klass)
   nle_operation_signals[INPUT_PRIORITY_CHANGED] =
       g_signal_new ("input-priority-changed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NleOperationClass,
-          input_priority_changed), NULL, NULL, g_cclosure_marshal_generic,
+          input_priority_changed), NULL, NULL, NULL,
       G_TYPE_NONE, 2, GST_TYPE_PAD, G_TYPE_UINT);
 
   gstelement_class->request_new_pad =
@@ -199,7 +235,6 @@ nle_operation_reset (NleOperation * operation)
 {
   operation->num_sinks = 1;
   operation->realsinks = 0;
-  operation->next_base_time = 0;
 }
 
 static void
@@ -310,7 +345,7 @@ get_src_pad (GstElement * element)
 }
 
 /* get_nb_static_sinks:
- * 
+ *
  * Returns : The number of static sink pads of the controlled element.
  */
 static guint
@@ -604,7 +639,7 @@ get_request_sink_pad (NleOperation * operation)
     if ((GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SINK) &&
         (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST)) {
       pad =
-          gst_element_get_request_pad (operation->element,
+          gst_element_request_pad_simple (operation->element,
           GST_PAD_TEMPLATE_NAME_TEMPLATE (templ));
       if (pad)
         break;
@@ -831,19 +866,3 @@ nle_operation_signal_input_priority_changed (NleOperation * operation,
   g_signal_emit (operation, nle_operation_signals[INPUT_PRIORITY_CHANGED],
       0, pad, priority);
 }
-
-void
-nle_operation_update_base_time (NleOperation * operation,
-    GstClockTime timestamp)
-{
-  if (!nle_object_to_media_time (NLE_OBJECT (operation),
-          timestamp, &operation->next_base_time)) {
-    GST_WARNING_OBJECT (operation, "Trying to set a basetime outside of "
-        "ourself");
-
-    return;
-  }
-
-  GST_INFO_OBJECT (operation, "Setting next_basetime to %"
-      GST_TIME_FORMAT, GST_TIME_ARGS (operation->next_base_time));
-}
index 3f68f5d..c2db095 100644 (file)
@@ -60,8 +60,6 @@ G_BEGIN_DECLS
   GList * sinks;               /* The sink ghostpads */
   
   GstElement *element;         /* controlled element */
-
-  GstClockTime next_base_time;
 };
 
 struct _NleOperationClass
index 439dc38..db5316c 100644 (file)
@@ -60,6 +60,7 @@ struct _NleSourcePrivate
 
   GMutex seek_lock;
   GstEvent *seek_event;
+  guint32 flush_seqnum;
   gulong probeid;
 };
 
@@ -116,6 +117,24 @@ nle_source_class_init (NleSourceClass * klass)
 
 }
 
+static GstPadProbeReturn
+srcpad_probe_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source)
+{
+  GstEvent *event = info->data;
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+      GST_OBJECT_LOCK (source);
+      source->priv->flush_seqnum = GST_EVENT_SEQNUM (event);
+      GST_DEBUG_OBJECT (pad, "Seek seqnum: %d", source->priv->flush_seqnum);
+      GST_OBJECT_UNLOCK (source);
+      break;
+    default:
+      break;
+  }
+
+  return GST_PAD_PROBE_OK;
+}
 
 static void
 nle_source_init (NleSource * source)
@@ -125,6 +144,10 @@ nle_source_init (NleSource * source)
   source->priv = nle_source_get_instance_private (source);
   g_mutex_init (&source->priv->seek_lock);
 
+  gst_pad_add_probe (NLE_OBJECT_SRC (source),
+      GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, (GstPadProbeCallback) srcpad_probe_cb,
+      source, NULL);
+
   GST_DEBUG_OBJECT (source, "Setting GstBin async-handling to TRUE");
   g_object_set (G_OBJECT (source), "async-handling", TRUE, NULL);
 }
@@ -322,8 +345,8 @@ nle_source_control_element_func (NleSource * source, GstElement * element)
 
   g_return_val_if_fail (source->element == NULL, FALSE);
 
-  GST_DEBUG_OBJECT (source, "element:%s, source->element:%p",
-      GST_ELEMENT_NAME (element), source->element);
+  GST_DEBUG_OBJECT (source, "element: %" GST_PTR_FORMAT ", source->element:%"
+      GST_PTR_FORMAT, element, source->element);
 
   source->element = element;
   gst_object_ref (element);
@@ -420,7 +443,7 @@ nle_source_send_event (GstElement * element, GstEvent * event)
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_SEEK:
       g_mutex_lock (&source->priv->seek_lock);
-      source->priv->seek_event = event;
+      gst_event_replace (&source->priv->seek_event, event);
       g_mutex_unlock (&source->priv->seek_lock);
       break;
     default:
@@ -436,39 +459,52 @@ ghost_seek_pad (GstElement * source, gpointer user_data)
 {
   NleSourcePrivate *priv = NLE_SOURCE (source)->priv;
 
+  g_assert (!NLE_OBJECT (source)->in_composition);
   g_mutex_lock (&priv->seek_lock);
   if (priv->seek_event) {
     GstEvent *seek_event = priv->seek_event;
     priv->seek_event = NULL;
 
+    GST_INFO_OBJECT (source, "Sending seek: %" GST_PTR_FORMAT, seek_event);
+    GST_OBJECT_LOCK (source);
+    priv->flush_seqnum = GST_EVENT_SEQNUM (seek_event);
+    GST_OBJECT_UNLOCK (source);
     if (!(gst_pad_send_event (priv->ghostedpad, seek_event)))
       GST_ELEMENT_ERROR (source, RESOURCE, SEEK,
           (NULL), ("Sending initial seek to upstream element failed"));
   }
   g_mutex_unlock (&priv->seek_lock);
-
-  GST_OBJECT_LOCK (source);
-  if (priv->probeid) {
-    GST_DEBUG_OBJECT (source, "Removing blocking probe! %lu", priv->probeid);
-    priv->areblocked = FALSE;
-    gst_pad_remove_probe (priv->ghostedpad, priv->probeid);
-    priv->probeid = 0;
-  }
-  GST_OBJECT_UNLOCK (source);
 }
 
 static GstPadProbeReturn
-pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source)
+pad_brobe_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source)
 {
+  NleSourcePrivate *priv = source->priv;
+  GstPadProbeReturn res = GST_PAD_PROBE_OK;
+
   GST_OBJECT_LOCK (source);
-  if (!source->priv->areblocked) {
+  if (!priv->areblocked && priv->seek_event) {
     GST_INFO_OBJECT (pad, "Blocked now, launching seek");
+    priv->areblocked = TRUE;
     gst_element_call_async (GST_ELEMENT (source), ghost_seek_pad, NULL, NULL);
-    source->priv->areblocked = TRUE;
+    GST_OBJECT_UNLOCK (source);
+
+    return GST_PAD_PROBE_OK;
+  }
+
+  if (priv->probeid && GST_EVENT_SEQNUM (info->data) == priv->flush_seqnum) {
+    priv->flush_seqnum = GST_SEQNUM_INVALID;
+    priv->areblocked = FALSE;
+    priv->probeid = 0;
+    res = GST_PAD_PROBE_REMOVE;
+  } else {
+    res = GST_PAD_PROBE_DROP;
+    GST_DEBUG_OBJECT (source, "Dropping %" GST_PTR_FORMAT " - %d ? %d",
+        info->data, GST_EVENT_SEQNUM (info->data), priv->flush_seqnum);
   }
   GST_OBJECT_UNLOCK (source);
 
-  return GST_PAD_PROBE_OK;
+  return res;
 }
 
 static gboolean
@@ -488,32 +524,42 @@ nle_source_prepare (NleObject * object)
     return FALSE;
   }
 
-  if (object->in_composition == FALSE) {
-    gst_element_send_event (GST_ELEMENT_CAST (parent),
-        gst_event_new_seek (1.0, GST_FORMAT_TIME,
-            GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH,
-            GST_SEEK_TYPE_SET, object->start, GST_SEEK_TYPE_SET, object->stop));
-  }
-
-  GST_LOG_OBJECT (source, "srcpad:%p, dynamicpads:%d",
-      object->srcpad, priv->dynamicpads);
-
   if (!priv->staticpad && !(get_valid_src_pad (source, source->element, &pad))) {
     GST_DEBUG_OBJECT (source, "Couldn't find a valid source pad");
     gst_object_unref (parent);
     return FALSE;
-  } else {
-    if (priv->staticpad)
-      pad = gst_object_ref (priv->staticpad);
-    priv->ghostedpad = pad;
+  }
+
+  if (priv->staticpad)
+    pad = gst_object_ref (priv->staticpad);
+  priv->ghostedpad = pad;
+
+  if (object->in_composition == FALSE) {
+    GstClockTime start =
+        GST_CLOCK_TIME_IS_VALID (object->inpoint) ? object->inpoint : 0;
+    GstClockTime stop = GST_CLOCK_TIME_NONE;
+
+    if (GST_CLOCK_TIME_IS_VALID (object->inpoint)
+        && GST_CLOCK_TIME_IS_VALID (object->duration) && object->duration)
+      stop = object->inpoint + object->duration;
+
+    g_mutex_lock (&source->priv->seek_lock);
+    source->priv->seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME,
+        GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH,
+        GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, stop);
+    g_mutex_unlock (&source->priv->seek_lock);
     GST_OBJECT_LOCK (source);
     priv->probeid = gst_pad_add_probe (pad,
-        GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
-        (GstPadProbeCallback) pad_blocked_cb, source, NULL);
+        GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH |
+        GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) pad_brobe_cb,
+        source, NULL);
     GST_OBJECT_UNLOCK (source);
-    gst_object_unref (pad);
   }
 
+  GST_LOG_OBJECT (source, "srcpad:%p, dynamicpads:%d",
+      object->srcpad, priv->dynamicpads);
+
+  gst_object_unref (pad);
   gst_object_unref (parent);
 
   return TRUE;
diff --git a/scripts/extract-release-date-from-doap-file.py b/scripts/extract-release-date-from-doap-file.py
new file mode 100755 (executable)
index 0000000..f09b60e
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+#
+# extract-release-date-from-doap-file.py VERSION DOAP-FILE
+#
+# Extract release date for the given release version from a DOAP file
+#
+# Copyright (C) 2020 Tim-Philipp Müller <tim centricular com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+import sys
+import xml.etree.ElementTree as ET
+
+if len(sys.argv) != 3:
+  sys.exit('Usage: {} VERSION DOAP-FILE'.format(sys.argv[0]))
+
+release_version = sys.argv[1]
+doap_fn = sys.argv[2]
+
+tree = ET.parse(doap_fn)
+root = tree.getroot()
+
+namespaces = {'doap': 'http://usefulinc.com/ns/doap#'}
+
+for v in root.findall('doap:release/doap:Version', namespaces=namespaces):
+  if v.findtext('doap:revision', namespaces=namespaces) == release_version:
+    release_date = v.findtext('doap:created', namespaces=namespaces)
+    if release_date:
+      print(release_date)
+      sys.exit(0)
+
+sys.exit('Could not find a release with version {} in {}'.format(release_version, doap_fn))
diff --git a/tests/.gitignore b/tests/.gitignore
deleted file mode 100644 (file)
index d838da9..0000000
+++ /dev/null
@@ -1 +0,0 @@
-examples/
diff --git a/tests/Makefile.am b/tests/Makefile.am
deleted file mode 100644 (file)
index 349ec3c..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-if HAVE_GST_CHECK
-CHECK_SUBDIRS= check
-else
-CHECK_SUBDIRS=
-endif
-
-if BUILD_BENCHMARKS
-BENCHMARKS_SUBDIR=benchmarks
-else
-BENCHMARKS_SUBDIR=
-endif
-
-if HAVE_GST_VALIDATE
-VALIDATE_SUBDIRS= validate
-else
-VALIDATE_SUBDIRS=
-endif
-
-SUBDIRS= $(CHECK_SUBDIRS) $(BENCHMARKS_SUBDIR) $(VALIDATE_SUBDIRS)
-
-DIST_SUBDIRS = check benchmarks validate
-
diff --git a/tests/benchmarks/Makefile.am b/tests/benchmarks/Makefile.am
deleted file mode 100644 (file)
index 42b5397..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-noinst_PROGRAMS = timeline
-
-AM_CFLAGS =  -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) $(GST_CFLAGS)
-AM_LDFLAGS = -export-dynamic
-LDADD = $(top_builddir)/ges/libges-@GST_API_VERSION@.la $(GST_PBUTILS_LIBS) $(GST_LIBS)
index 6eac030..1daa8ee 100644 (file)
@@ -50,7 +50,7 @@ main (gint argc, gchar * argv[])
     ges_layer_add_asset (layer, asset, i * 1000, 0,
         1000, GES_TRACK_TYPE_UNKNOWN);
   end = gst_util_get_timestamp ();
-  g_print ("%" GST_TIME_FORMAT " - adding %d clip to the timeline\n",
+  gst_print ("%" GST_TIME_FORMAT " - adding %d clip to the timeline\n",
       GST_TIME_ARGS (end - start), i);
 
   start_ripple = gst_util_get_timestamp ();
@@ -63,7 +63,7 @@ main (gint argc, gchar * argv[])
     min_rippling_time = MIN (min_rippling_time, end - start);
   }
   end_ripple = gst_util_get_timestamp ();
-  g_print ("%" GST_TIME_FORMAT " - rippling %d times, max: %"
+  gst_print ("%" GST_TIME_FORMAT " - rippling %d times, max: %"
       GST_TIME_FORMAT " min: %" GST_TIME_FORMAT "\n",
       GST_TIME_ARGS (end_ripple - start_ripple), i - 1,
       GST_TIME_ARGS (max_rippling_time), GST_TIME_ARGS (min_rippling_time));
@@ -82,7 +82,7 @@ main (gint argc, gchar * argv[])
     min_rippling_time = MIN (min_rippling_time, end - start);
   }
   end_ripple = gst_util_get_timestamp ();
-  g_print ("%" GST_TIME_FORMAT " - rippling %d times, max: %"
+  gst_print ("%" GST_TIME_FORMAT " - rippling %d times, max: %"
       GST_TIME_FORMAT " min: %" GST_TIME_FORMAT " (with auto-transition on)\n",
       GST_TIME_ARGS (end_ripple - start_ripple), i - 1,
       GST_TIME_ARGS (max_rippling_time), GST_TIME_ARGS (min_rippling_time));
@@ -90,7 +90,7 @@ main (gint argc, gchar * argv[])
   start = gst_util_get_timestamp ();
   gst_object_unref (timeline);
   end = gst_util_get_timestamp ();
-  g_print ("%" GST_TIME_FORMAT " - freeing the timeline\n",
+  gst_print ("%" GST_TIME_FORMAT " - freeing the timeline\n",
       GST_TIME_ARGS (end - start));
 
   return 0;
diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am
deleted file mode 100644 (file)
index 413b8f2..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-include $(top_srcdir)/common/check.mak
-
-CHECK_REGISTRY = $(top_builddir)/tests/check/test-registry.reg
-TEST_FILES_DIRECTORY = $(top_srcdir)/tests/check/ges
-
-REGISTRY_ENVIRONMENT = \
-       GST_REGISTRY_1_0=$(CHECK_REGISTRY)
-
-# GST_PLUGINS_XYZ_DIR is only set in an uninstalled setup
-AM_TESTS_ENVIRONMENT += \
-       $(REGISTRY_ENVIRONMENT)                                 \
-       GST_PLUGIN_SYSTEM_PATH_1_0=                             \
-       GST_PLUGIN_PATH_1_0=$(top_builddir)/plugins:$(GST_PLUGINS_BAD_DIR):$(GST_PLUGINS_LIBAV_DIR):$(GST_PLUGINS_UGLY_DIR):$(GST_PLUGINS_GOOD_DIR):$(GST_PLUGINS_BASE_DIR):$(GST_PLUGINS_DIR)
-
-plugindir = $(libdir)/gstreamer-@GST_API_VERSION@
-
-# override to _not_ install the test plugins
-install-pluginLTLIBRARIES:
-
-# the core dumps of some machines have PIDs appended
-CLEANFILES = core.* test-registry.* *.gcno *.gcda
-
-common_cflags=-I$(top_srcdir) $(GST_PLUGINS_BASE_CFLAGS) $(GST_OBJ_CFLAGS) \
-       -DGES_TEST_FILES_PATH="\"$(TEST_FILES_DIRECTORY)\"" \
-       $(GST_CHECK_CFLAGS) $(GST_OPTION_CFLAGS) $(GST_CFLAGS)
-common_ldadd=$(top_builddir)/ges/libges-@GST_API_VERSION@.la \
-       $(GST_PLUGINS_BASE_LIBS) -lgstpbutils-$(GST_API_VERSION) \
-       $(GST_OBJ_LIBS) $(GST_CHECK_LIBS)
-
-testutils_noisnt_libraries=libtestutils.la
-testutils_noinst_headers=ges/test-utils.h nle/common.h
-libtestutils_la_LIBADD=$(common_ldadd)
-libtestutils_la_CFLAGS=$(common_cflags)
-libtestutils_la_SOURCES=ges/test-utils.c nle/common.c
-
-SUPPRESSIONS = $(top_srcdir)/common/gst.supp # $(srcdir)/gst-plugins-bad.supp
-
-
-clean-local: clean-local-check
-
-check_PROGRAMS = \
-       ges/asset       \
-       ges/backgroundsource\
-       ges/basic       \
-       ges/layer       \
-       ges/effects     \
-       ges/uriclip     \
-       ges/clip        \
-       ges/timelineedition     \
-       ges/titles\
-       ges/transition  \
-       ges/overlays\
-       ges/mixers\
-       ges/group\
-       ges/project\
-       ges/track\
-       ges/tempochange \
-       ges/negative\
-       nle/simple      \
-       nle/complex     \
-       nle/nleoperation        \
-       nle/nlecomposition      \
-       nle/tempochange
-
-# We do not support sources outside composition
-#      nle/nlesource
-
-noinst_LTLIBRARIES=$(testutils_noisnt_libraries)
-noinst_HEADERS=$(testutils_noinst_headers)
-
-TESTS = $(check_PROGRAMS)
-
-AM_CFLAGS =  $(common_cflags) -UG_DISABLE_ASSERT -UG_DISABLE_CAST_CHECKS
-LDADD = $(common_ldadd) libtestutils.la
-
-EXTRA_DIST = \
-       ges/test-project.xges \
-       ges/test-auto-transition.xges \
-       ges/audio_only.ogg \
-       ges/test-properties.xges \
-       ges/image.png \
-       ges/audio_video.ogg
-
-COVERAGE_DIRS = \
-       ges
-COVERAGE_FILES = $(foreach dir,$(COVERAGE_DIRS),$(wildcard $(top_builddir)/$(dir)/*.gcov))
-COVERAGE_FILES_REL = $(subst $(top_builddir)/,,$(COVERAGE_FILES))
-COVERAGE_OUT_FILES = $(foreach dir,$(COVERAGE_DIRS),$(wildcard $(top_builddir)/$(dir)/*.gcov.out))
-COVERAGE_OUT_FILES_REL = $(subst $(top_builddir)/,,$(COVERAGE_OUT_FILES))
-
-debug:
-       echo $(COVERAGE_FILES)
-       echo $(COVERAGE_FILES_REL)
-
-.PHONY: coverage
-if GST_GCOV_ENABLED
-# we rebuild a registry and do gst-inspect so that all the get/set codepaths
-# are also covered
-coverage:
-       make check
-       make coverage-report
-else
-coverage:
-       echo "You need to configure with --enable-gcov to get coverage data"
-       exit 1
-endif
-
-coverage-report:
-       if test ! -e coverage; then
-               rm -r coverage
-       fi
-       for dir in $(COVERAGE_DIRS); do                                 \
-         mkdir -p coverage/$$dir;                                      \
-         make -C $(top_builddir)/$$dir gcov;                           \
-        done
-       for dir in $(COVERAGE_DIRS); do                                 \
-           files="`ls $(top_builddir)/$$dir/*.gcov.out 2> /dev/null`"; \
-          if test ! -z "$$files"; then                                 \
-           perl $(top_srcdir)/common/coverage/coverage-report.pl       \
-             $(top_builddir)/$$dir/*.gcov.out >                        \
-             coverage/$$dir/index.xml;                                 \
-           xsltproc $(top_srcdir)/common/coverage/coverage-report.xsl  \
-             coverage/$$dir/index.xml > coverage/$$dir/index.html;     \
-         fi; \
-        done
-       for file in $(COVERAGE_FILES_REL); do                           \
-         echo Generating coverage/$$file.html;                         \
-         perl $(top_srcdir)/common/coverage/coverage-report-entry.pl   \
-           $(top_builddir)/$$file > coverage/$$file.html;              \
-       done
-
-check:
-if HAVE_GST_VALIDATE_LAUNCHER
-if WITH_PYTHON
-       GI_TYPELIB_PATH=$(top_srcdir)/ges/:${GI_TYPELIB_PATH} LD_LIBRARY_PATH=$(top_srcdir)/ges/.libs:${LD_LIBRARY_PATH} gst-validate-launcher --pyunittest-dir  $(top_srcdir)/tests/check/ pyunittest --dump-on-failure
-
-check-python:
-       GI_TYPELIB_PATH=$(top_srcdir)/ges/:${GI_TYPELIB_PATH} LD_LIBRARY_PATH=$(top_srcdir)/ges/.libs:${LD_LIBRARY_PATH} gst-validate-launcher --pyunittest-dir  $(top_srcdir)/tests/check/ pyunittest --dump-on-failure
-endif
-endif
similarity index 98%
rename from tests/check/ges/test-properties.xges
rename to tests/check/assets/test-properties.xges
index 1fde741..ff74b63 100644 (file)
@@ -14,7 +14,7 @@
       <track track-type="2" caps="audio/x-raw" track-id="0"/>
       <track track-type="4" caps="video/x-raw" track-id="1"/>
       <layer priority="0" properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, a=(guint)3'>
-        <clip id="0" layer-priority='0' asset-id="file:///test/not/exisiting" type-name="GESUriClip" track-types="6" start="0" duration="10000000000">
+        <clip id="0" layer-priority='0' asset-id="file:///test/not/exisiting" type-name="GESUriClip" track-types="6" start="0" duration="1000000000">
         <effect asset-id='agingtv' clip-id='0' type-name='GESEffect' track-type='4' track-id='1' metadatas='metadatas;' children-properties='properties, scratch-lines=(uint)12;'/>
         </clip>
       </layer>
diff --git a/tests/check/ges/.gitignore b/tests/check/ges/.gitignore
deleted file mode 100644 (file)
index 4c81c31..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-backgroundsource
-basic
-effects
-filesource
-layer
-overlays
-save_and_load
-simplelayer
-text_properties
-clip
-timelineedition
-titles
-transition
-track
-asset
index 89ebdfd..1e1188a 100644 (file)
@@ -20,7 +20,7 @@
  */
 
 #include "test-utils.h"
-#include "../../../ges/ges-internal.h"
+/* #include "../../../ges/ges-internal.h" */
 #include <ges/ges.h>
 #include <gst/check/gstcheck.h>
 
@@ -228,9 +228,6 @@ GST_START_TEST (test_uri_clip_change_asset)
   asset1 = GES_ASSET (ges_uri_clip_asset_request_sync (uri1, NULL));
   fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)),
       2);
-  fail_if (ges_extractable_set_asset (extractable, asset1));
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (extractable),
-      ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset1)));
   fail_unless (ges_extractable_set_asset (extractable, asset1));
   fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)),
       1);
@@ -240,6 +237,9 @@ GST_START_TEST (test_uri_clip_change_asset)
   g_free (uri);
   g_free (uri1);
 
+  gst_object_unref (asset1);
+  gst_object_unref (asset);
+
   ges_deinit ();
 }
 
@@ -267,6 +267,20 @@ GST_START_TEST (test_list_asset)
 
 GST_END_TEST;
 
+/*
+ * NOTE: this test is commented out because it requires the internal
+ * ges_asset_finish_proxy method. This method replaces the behaviour of
+ * ges_asset_set_proxy (NULL, proxy), which is no longer supported.
+ *
+ * ges_asset_cache_lookup and ges_asset_try_proxy are similarly internal,
+ * but they are marked with GES_API in ges-internal.h
+ * The newer ges_asset_finish_proxy is not marked as GES_API, because it
+ * would add it to the symbols list, and could therefore not be easily
+ * removed.
+ *
+ * Once we have a nice way to call internal methods for tests, we should
+ * uncomment this.
+ *
 GST_START_TEST (test_proxy_asset)
 {
   GESAsset *identity, *nothing, *nothing_at_all;
@@ -283,7 +297,7 @@ GST_START_TEST (test_proxy_asset)
   fail_unless (nothing != NULL);
 
   fail_unless (ges_asset_try_proxy (nothing, "video identity"));
-  fail_unless (ges_asset_set_proxy (NULL, identity));
+  fail_unless (ges_asset_finish_proxy (identity));
 
   nothing_at_all = ges_asset_request (GES_TYPE_EFFECT, "nothing_at_all", NULL);
   fail_if (nothing_at_all);
@@ -291,16 +305,16 @@ GST_START_TEST (test_proxy_asset)
   nothing_at_all = ges_asset_cache_lookup (GES_TYPE_EFFECT, "nothing_at_all");
   fail_unless (nothing_at_all != NULL);
 
-  /* Now we proxy nothing_at_all to nothing which is itself proxied to identity */
+  // Now we proxy nothing_at_all to nothing which is itself proxied to identity
   fail_unless (ges_asset_try_proxy (nothing_at_all, "nothing"));
-  fail_unless (ges_asset_set_proxy (NULL, nothing));
+  fail_unless (ges_asset_finish_proxy (nothing));
   fail_unless_equals_int (g_list_length (ges_asset_list_proxies
           (nothing_at_all)), 1);
 
   fail_unless_equals_pointer (ges_asset_get_proxy_target (nothing),
       nothing_at_all);
 
-  /* If we request nothing_at_all we should get the good proxied identity */
+  // If we request nothing_at_all we should get the good proxied identity
   nothing_at_all = ges_asset_request (GES_TYPE_EFFECT, "nothing_at_all", NULL);
   fail_unless (nothing_at_all == identity);
 
@@ -311,6 +325,414 @@ GST_START_TEST (test_proxy_asset)
 }
 
 GST_END_TEST;
+*/
+
+static void
+_count_cb (GObject * obj, GParamSpec * pspec, gpointer key)
+{
+  guint count = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (obj), key));
+  g_object_set_data (G_OBJECT (obj), key, GUINT_TO_POINTER (count + 1));
+}
+
+#define _CONNECT_PROXY_SIGNALS(asset) \
+  g_signal_connect (asset, "notify::proxy", G_CALLBACK (_count_cb), \
+      (gchar *)"test-data-proxy-count"); \
+  g_signal_connect (asset, "notify::proxy-target", G_CALLBACK (_count_cb), \
+      (gchar *)"test-data-target-count");
+
+/* test that @asset has the properties proxy = @proxy and
+ * proxy-target = @proxy_target
+ * Also check that the callback for "notify::proxy" (set up in
+ * _CONNECT_PROXY_SIGNALS) has been called @p_count times, and the
+ * callback for "notify::target-proxy" has been called @t_count times.
+ */
+#define _assert_proxy_state(asset, proxy, proxy_target, p_count, t_count) \
+{ \
+  const gchar *id = ges_asset_get_id (asset); \
+  guint found_p_count = GPOINTER_TO_UINT (g_object_get_data ( \
+        G_OBJECT (asset), "test-data-proxy-count")); \
+  guint found_t_count = GPOINTER_TO_UINT (g_object_get_data ( \
+        G_OBJECT (asset), "test-data-target-count")); \
+  GESAsset *found_proxy = ges_asset_get_proxy (asset); \
+  GESAsset *found_target = ges_asset_get_proxy_target (asset); \
+  fail_unless (found_proxy == proxy, "Asset '%s' has the proxy '%s' " \
+      "rather than the expected '%s'", id, \
+      found_proxy ? ges_asset_get_id (found_proxy) : NULL, \
+      proxy ? ges_asset_get_id (proxy) : NULL); \
+  fail_unless (found_target == proxy_target, "Asset '%s' has the proxy " \
+      "target '%s' rather than the expected '%s'", id, \
+      found_target ? ges_asset_get_id (found_target) : NULL, \
+      proxy_target ? ges_asset_get_id (proxy_target) : NULL); \
+  fail_unless (p_count == found_p_count, "notify::proxy for asset '%s' " \
+      "was called %u times, rather than the expected %u times", \
+      id, found_p_count, p_count); \
+  fail_unless (t_count == found_t_count, "notify::target-proxy for " \
+      "asset '%s' was called %u times, rather than the expected %u times", \
+      id, found_t_count, t_count); \
+}
+
+#define _assert_proxy_list(asset, cmp_list) \
+{ \
+  const gchar * id = ges_asset_get_id (asset); \
+  int i; \
+  GList *tmp; \
+  for (i = 0, tmp = ges_asset_list_proxies (asset); cmp_list[i] && tmp; \
+      i++, tmp = tmp->next) { \
+    GESAsset *proxy = tmp->data; \
+    fail_unless (proxy == cmp_list[i], "The asset '%s' has '%s' as its " \
+        "%ith proxy, rather than the expected '%s'", id, \
+        ges_asset_get_id (proxy), i, ges_asset_get_id (cmp_list[i])); \
+  } \
+  fail_unless (tmp == NULL, "Found more proxies for '%s' than expected", \
+      id); \
+  fail_unless (cmp_list[i] == NULL, "Found less proxies (%i) for '%s' " \
+      "than expected", i, id); \
+}
+
+#define _assert_effect_asset_request(req_id, expect) \
+{ \
+  GESAsset *requested = ges_asset_request (GES_TYPE_EFFECT, req_id, NULL); \
+  fail_unless (requested == expect, "Requested asset for id '%s' is " \
+      "'%s' rather than the expected '%s'", req_id, \
+      requested ? ges_asset_get_id (requested) : NULL, \
+      ges_asset_get_id (expect)); \
+  gst_object_unref (requested); \
+}
+
+GST_START_TEST (test_proxy_setters)
+{
+  GESAsset *proxies[] = { NULL, NULL, NULL, NULL };
+  GESAsset *asset, *alt_asset;
+  GESAsset *proxy0, *proxy1, *proxy2;
+  gchar asset_id[] = "video agingtv ! videobalance";
+  gchar alt_asset_id[] = "video gamma";
+  gchar proxy0_id[] = "video videobalance contrast=0.0";
+  gchar proxy1_id[] = "video videobalance contrast=1.0";
+  gchar proxy2_id[] = "video videobalance contrast=2.0";
+
+  ges_init ();
+
+  asset = ges_asset_request (GES_TYPE_EFFECT, asset_id, NULL);
+  alt_asset = ges_asset_request (GES_TYPE_EFFECT, alt_asset_id, NULL);
+
+  proxy0 = ges_asset_request (GES_TYPE_EFFECT, proxy0_id, NULL);
+  proxy1 = ges_asset_request (GES_TYPE_EFFECT, proxy1_id, NULL);
+  proxy2 = ges_asset_request (GES_TYPE_EFFECT, proxy2_id, NULL);
+
+  /* make sure our assets are unique */
+  fail_unless (asset);
+  fail_unless (alt_asset);
+  fail_unless (proxy0);
+  fail_unless (proxy1);
+  fail_unless (proxy2);
+  fail_unless (asset != alt_asset);
+  fail_unless (asset != proxy0);
+  fail_unless (asset != proxy1);
+  fail_unless (asset != proxy2);
+  fail_unless (alt_asset != proxy0);
+  fail_unless (alt_asset != proxy1);
+  fail_unless (alt_asset != proxy2);
+  fail_unless (proxy0 != proxy1);
+  fail_unless (proxy0 != proxy1);
+  fail_unless (proxy0 != proxy2);
+  fail_unless (proxy1 != proxy2);
+
+  _CONNECT_PROXY_SIGNALS (asset);
+  _CONNECT_PROXY_SIGNALS (alt_asset);
+  _CONNECT_PROXY_SIGNALS (proxy0);
+  _CONNECT_PROXY_SIGNALS (proxy1);
+  _CONNECT_PROXY_SIGNALS (proxy2);
+
+  /* no proxies to start with */
+  _assert_proxy_state (asset, NULL, NULL, 0, 0);
+  _assert_proxy_state (alt_asset, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy0, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+  _assert_proxy_list (asset, proxies);
+  _assert_proxy_list (alt_asset, proxies);
+  _assert_proxy_list (proxy0, proxies);
+  _assert_proxy_list (proxy1, proxies);
+  _assert_proxy_list (proxy2, proxies);
+
+  /* id for an asset with no proxy returns itself */
+  _assert_effect_asset_request (asset_id, asset);
+  _assert_effect_asset_request (alt_asset_id, alt_asset);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* set a proxy */
+  fail_unless (ges_asset_set_proxy (asset, proxy0));
+  _assert_proxy_state (asset, proxy0, NULL, 1, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  proxies[0] = proxy0;
+  _assert_proxy_list (asset, proxies);
+
+  /* requesting the same asset should return the proxy instead */
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* can't proxy a different asset */
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (alt_asset, proxy0) == FALSE);
+  _assert_proxy_state (alt_asset, NULL, NULL, 0, 0);
+  _assert_proxy_state (asset, proxy0, NULL, 1, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  _assert_proxy_list (asset, proxies);
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+
+  /* set the same proxy again is safe */
+  fail_unless (ges_asset_set_proxy (asset, proxy0));
+  /* notify::proxy callback count increases, even though we set the same
+   * proxy. This is the default behaviour for setters. */
+  _assert_proxy_state (asset, proxy0, NULL, 2, 0);
+  /* but the notify::target-proxy has not increased for the proxy */
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  _assert_proxy_list (asset, proxies);
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+
+  /* replace the proxy with a new one */
+  fail_unless (ges_asset_set_proxy (asset, proxy1));
+  _assert_proxy_state (asset, proxy1, NULL, 3, 0);
+  /* first proxy still keeps its target */
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  proxies[0] = proxy1;
+  proxies[1] = proxy0;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy1);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* replace again */
+  fail_unless (ges_asset_set_proxy (asset, proxy2));
+  _assert_proxy_state (asset, proxy2, NULL, 4, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, asset, 0, 1);
+
+  proxies[0] = proxy2;
+  proxies[1] = proxy1;
+  proxies[2] = proxy0;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy2);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* move proxy0 back to being the default */
+  fail_unless (ges_asset_set_proxy (asset, proxy0));
+  _assert_proxy_state (asset, proxy0, NULL, 5, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, asset, 0, 1);
+
+  proxies[0] = proxy0;
+  proxies[1] = proxy2;
+  proxies[2] = proxy1;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* remove proxy2 */
+  fail_unless (ges_asset_unproxy (asset, proxy2));
+  /* notify::proxy not released since we have not switched defaults */
+  _assert_proxy_state (asset, proxy0, NULL, 5, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 2);
+
+  proxies[0] = proxy0;
+  proxies[1] = proxy1;
+  proxies[2] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* make proxy2 a proxy for proxy0 */
+  fail_unless (ges_asset_set_proxy (proxy0, proxy2));
+  _assert_proxy_state (asset, proxy0, NULL, 5, 0);
+  _assert_proxy_state (proxy0, proxy2, asset, 1, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, proxy0, 0, 3);
+
+  proxies[0] = proxy0;
+  proxies[1] = proxy1;
+  proxies[2] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = NULL;
+  _assert_proxy_list (proxy0, proxies);
+
+  /* original id will now follows two proxy links to get proxy2 */
+  _assert_effect_asset_request (asset_id, proxy2);
+  _assert_effect_asset_request (proxy0_id, proxy2);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* remove proxy0 from asset, should now default to proxy1 */
+  fail_unless (ges_asset_unproxy (asset, proxy0));
+  /* notify::proxy released since we have switched defaults */
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, proxy2, NULL, 1, 2);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, proxy0, 0, 3);
+
+  proxies[0] = proxy1;
+  proxies[1] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = NULL;
+  _assert_proxy_list (proxy0, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy1);
+  _assert_effect_asset_request (proxy0_id, proxy2);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* remove proxy2 from proxy0 */
+  fail_unless (ges_asset_unproxy (proxy0, proxy2));
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, NULL, 2, 2);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 4);
+
+  proxies[0] = proxy1;
+  proxies[1] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = NULL;
+  _assert_proxy_list (proxy0, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy1);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* make both proxy0 and proxy2 proxies of proxy1 */
+  fail_unless (ges_asset_set_proxy (proxy1, proxy0));
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy0, asset, 1, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 4);
+  fail_unless (ges_asset_set_proxy (proxy1, proxy2));
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+
+  proxies[0] = proxy1;
+  proxies[1] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = proxy0;
+  proxies[2] = NULL;
+  _assert_proxy_list (proxy1, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy2);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy2);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* should not be able to set up any circular proxies */
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (proxy1, asset) == FALSE);
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (proxy0, asset) == FALSE);
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (proxy2, asset) == FALSE);
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+
+  /* remove last proxy from asset, should set its proxy to NULL */
+  fail_unless (ges_asset_unproxy (asset, proxy1));
+  _assert_proxy_state (asset, NULL, NULL, 7, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, NULL, 2, 2);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+
+  proxies[0] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = proxy0;
+  proxies[2] = NULL;
+  _assert_proxy_list (proxy1, proxies);
+
+  /* get asset back */
+  _assert_effect_asset_request (asset_id, asset);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy2);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* set the proxy property to NULL for proxy1, should remove all of
+   * its proxies */
+  fail_unless (ges_asset_set_proxy (proxy1, NULL));
+  _assert_proxy_state (asset, NULL, NULL, 7, 0);
+  /* only one notify for proxy1, but two separate ones for ex-proxies */
+  _assert_proxy_state (proxy0, NULL, NULL, 2, 4);
+  _assert_proxy_state (proxy1, NULL, NULL, 3, 2);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 6);
+
+  proxies[0] = NULL;
+  _assert_proxy_list (asset, proxies);
+  _assert_proxy_list (proxy0, proxies);
+  _assert_proxy_list (proxy1, proxies);
+  _assert_proxy_list (proxy2, proxies);
+
+  _assert_effect_asset_request (asset_id, asset);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  gst_object_unref (asset);
+  gst_object_unref (alt_asset);
+  gst_object_unref (proxy0);
+  gst_object_unref (proxy1);
+  gst_object_unref (proxy2);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
 
 static Suite *
 ges_suite (void)
@@ -325,7 +747,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_transition_change_asset);
   tcase_add_test (tc_chain, test_uri_clip_change_asset);
   tcase_add_test (tc_chain, test_list_asset);
-  tcase_add_test (tc_chain, test_proxy_asset);
+  tcase_add_test (tc_chain, test_proxy_setters);
 
   return s;
 }
index c9681c0..09bdabf 100644 (file)
@@ -395,6 +395,7 @@ GST_START_TEST (test_gap_filling_empty_track)
   gap_object_check (gap, 0, 10, 1);
   fail_unless (ges_timeline_commit (timeline));
 
+  gst_object_unref (asset);
   gst_object_unref (timeline);
 }
 
index d37d9c1..d34593e 100644 (file)
@@ -54,8 +54,7 @@ GST_START_TEST (test_ges_scenario)
 
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
 
   /* Give the Timeline a Track */
   GST_DEBUG ("Create a Track");
@@ -101,6 +100,7 @@ GST_START_TEST (test_ges_scenario)
    * 3 by the timeline
    * 1 by the track */
   ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
+  fail_unless (ges_track_element_get_track (trackelement) == track);
 
   GST_DEBUG ("Remove the Clip from the layer");
 
@@ -108,6 +108,10 @@ GST_START_TEST (test_ges_scenario)
   gst_object_ref (source);
   ASSERT_OBJECT_REFCOUNT (layer, "layer", 1);
   fail_unless (ges_layer_remove_clip (layer, GES_CLIP (source)));
+  /* track elements emptied from the track, but stay in clip */
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
+      GES_TIMELINE_ELEMENT (source));
+  fail_unless (ges_track_element_get_track (trackelement) == NULL);
   ASSERT_OBJECT_REFCOUNT (source, "source", 1);
   ASSERT_OBJECT_REFCOUNT (layer, "layer", 1);
   tmp_layer = ges_clip_get_layer (GES_CLIP (source));
@@ -118,7 +122,7 @@ GST_START_TEST (test_ges_scenario)
   /* Remove the track from the timeline */
   gst_object_ref (track);
   fail_unless (ges_timeline_remove_track (timeline, track));
-  fail_unless (ges_track_get_timeline (track) == NULL);
+  assert_num_in_track (track, 0);
 
   tracks = ges_timeline_get_tracks (timeline);
   fail_unless (tracks == NULL);
@@ -149,12 +153,23 @@ GST_END_TEST;
  * and then add it to the timeline.
  */
 
+#define _CREATE_SOURCE(layer, clip, start, duration) \
+{ \
+  GESAsset *asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); \
+  GST_DEBUG ("Creating a source"); \
+  fail_unless (clip = ges_layer_add_asset (layer, asset, start, 0, \
+        duration, GES_TRACK_TYPE_UNKNOWN)); \
+  assert_layer(clip, layer); \
+  ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); \
+  gst_object_unref (asset); \
+}
+
 GST_START_TEST (test_ges_timeline_add_layer)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track;
-  GESTestClip *s1, *s2, *s3;
+  GESClip *s1, *s2, *s3;
   GList *trackelements, *layers;
   GESTrackElement *trackelement;
 
@@ -180,34 +195,6 @@ GST_START_TEST (test_ges_timeline_add_layer)
   fail_unless (ges_track_get_timeline (track) == timeline);
   fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
-  gst_object_unref (tmp_layer);
-
   GST_DEBUG ("Add the layer to the timeline");
   fail_unless (ges_timeline_add_layer (timeline, layer));
   /* The timeline steals our reference to the layer */
@@ -215,8 +202,14 @@ GST_START_TEST (test_ges_timeline_add_layer)
   fail_unless (layer->timeline == timeline);
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
+
+  _CREATE_SOURCE (layer, s1, 0, 10);
+  ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
+  _CREATE_SOURCE (layer, s2, 20, 10);
+  ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
+  _CREATE_SOURCE (layer, s3, 40, 10);
+  ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
 
   /* Make sure the associated TrackElements are in the Track */
   trackelements = GES_CONTAINER_CHILDREN (s1);
@@ -263,13 +256,68 @@ GST_END_TEST;
 
 /* this time we add the layer before we add the track. */
 
+#define _assert_child_in_track(clip, child_type, track) \
+{ \
+  GESTrackElement *el; \
+  GList *tmp = ges_clip_find_track_elements (clip, NULL, \
+      GES_TRACK_TYPE_UNKNOWN, child_type); \
+  fail_unless (g_list_length (tmp), 1); \
+  el = tmp->data; \
+  g_list_free_full (tmp, gst_object_unref); \
+  fail_unless (ges_track_element_get_track (el) == track); \
+  if (track) \
+    ASSERT_OBJECT_REFCOUNT (el, "1 clip + 1 track + 1 timeline", 3); \
+  else \
+    ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1); \
+}
+
+#define _assert_no_child_in_track(clip, track) \
+  fail_if (ges_clip_find_track_elements (clip, track, \
+        GES_TRACK_TYPE_UNKNOWN, G_TYPE_NONE));
+
+#define _assert_add_track(timeline, track) \
+{ \
+  GList *tmp; \
+  GST_DEBUG ("Adding " #track " to the timeline"); \
+  fail_unless (ges_timeline_add_track (timeline, track)); \
+  ASSERT_OBJECT_REFCOUNT (track, #track, 1); \
+  fail_unless (ges_track_get_timeline (track) == timeline); \
+  fail_unless ((gpointer) GST_ELEMENT_PARENT (track) \
+      == (gpointer) timeline); \
+  tmp = ges_timeline_get_tracks (timeline); \
+  fail_unless (g_list_find (tmp, track), #track " not found in tracks"); \
+  g_list_free_full (tmp, gst_object_unref); \
+}
+
+#define _remove_sources(clip, expect_num) \
+{ \
+  GList *tmp, *els; \
+  els = ges_clip_find_track_elements (clip, NULL, GES_TRACK_TYPE_UNKNOWN, \
+      GES_TYPE_SOURCE); \
+  assert_equals_int (g_list_length (els), expect_num); \
+  for (tmp = els; tmp; tmp = tmp->next) \
+    fail_unless (ges_container_remove (GES_CONTAINER (clip), tmp->data)); \
+  g_list_free_full (els, gst_object_unref); \
+}
+
+#define _remove_from_track(clip, track, expect_num) \
+{ \
+  GList *tmp, *els; \
+  els = ges_clip_find_track_elements (clip, track, GES_TRACK_TYPE_UNKNOWN, \
+      G_TYPE_NONE); \
+  assert_equals_int (g_list_length (els), expect_num); \
+  for (tmp = els; tmp; tmp = tmp->next) \
+    fail_unless (ges_track_remove_element (track, tmp->data)); \
+  g_list_free_full (els, gst_object_unref); \
+}
+
 GST_START_TEST (test_ges_timeline_add_layer_first)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
-  GESTrack *track;
-  GESTestClip *s1, *s2, *s3;
-  GList *trackelements, *tmp, *layers;
+  GESLayer *layer;
+  GESTrack *track, *track1, *track2, *track3;
+  GESClip *s1, *s2, *s3;
+  GList *layers;
 
   ges_init ();
 
@@ -286,30 +334,15 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
   track = GES_TRACK (ges_video_track_new ());
   fail_unless (track != NULL);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s1, 0, 10);
+  _CREATE_SOURCE (layer, s2, 20, 10);
+  _CREATE_SOURCE (layer, s3, 40, 10);
 
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  fail_unless (ges_container_add (GES_CONTAINER (s1),
+          GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"))));
+  assert_num_children (s1, 1);
+  assert_num_children (s2, 0);
+  assert_num_children (s3, 0);
 
   GST_DEBUG ("Add the layer to the timeline");
   fail_unless (ges_timeline_add_layer (timeline, layer));
@@ -318,45 +351,147 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
   fail_unless (layer->timeline == timeline);
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
 
-  GST_DEBUG ("Add the track to the timeline");
-  fail_unless (ges_timeline_add_track (timeline, track));
-  ASSERT_OBJECT_REFCOUNT (track, "track", 1);
-  fail_unless (ges_track_get_timeline (track) == timeline);
-  fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline);
+  /* core children not created yet since no tracks */
+  assert_num_children (s1, 1);
+  assert_num_children (s2, 0);
+  assert_num_children (s3, 0);
+
+  _assert_add_track (timeline, track);
+  /* 3 sources, 1 effect */
+  assert_num_in_track (track, 4);
 
   /* Make sure the associated TrackElements are in the Track */
-  trackelements = GES_CONTAINER_CHILDREN (s1);
-  fail_unless (trackelements != NULL);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* Each object has 3 references:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-  }
+  assert_num_children (s1, 2);
+  _assert_child_in_track (s1, GES_TYPE_EFFECT, track);
+  _assert_child_in_track (s1, GES_TYPE_VIDEO_TEST_SOURCE, track);
 
-  trackelements = GES_CONTAINER_CHILDREN (s2);
-  fail_unless (trackelements != NULL);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* Each object has 3 references:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-  }
+  assert_num_children (s2, 1);
+  _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, track);
 
-  trackelements = GES_CONTAINER_CHILDREN (s3);
-  fail_unless (trackelements != NULL);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* Each object has 3 references:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-  }
+  assert_num_children (s3, 1);
+  _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track);
+
+  /* adding an audio track should create new audio sources */
+
+  track1 = GES_TRACK (ges_audio_track_new ());
+  _assert_add_track (timeline, track1);
+  /* other track stays the same */
+  assert_num_in_track (track, 4);
+  /* 3 sources */
+  assert_num_in_track (track1, 3);
+
+  /* one new core child */
+  assert_num_children (s1, 3);
+  _assert_child_in_track (s1, GES_TYPE_EFFECT, track);
+  _assert_child_in_track (s1, GES_TYPE_VIDEO_TEST_SOURCE, track);
+  _assert_child_in_track (s1, GES_TYPE_AUDIO_TEST_SOURCE, track1);
+
+  assert_num_children (s2, 2);
+  _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, track);
+  _assert_child_in_track (s2, GES_TYPE_AUDIO_TEST_SOURCE, track1);
+
+  assert_num_children (s3, 2);
+  _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track);
+  _assert_child_in_track (s3, GES_TYPE_AUDIO_TEST_SOURCE, track1);
+
+  /* adding another track should not prompt the change anything
+   * unrelated to the new track */
+
+  /* remove the core children from s1 */
+  _remove_sources (s1, 2);
+
+  /* only have effect left, and not in any track */
+  assert_num_children (s1, 1);
+  /* effect is emptied from its track, since the corresponding core child
+   * was removed */
+  _assert_child_in_track (s1, GES_TYPE_EFFECT, NULL);
+
+  assert_num_in_track (track, 2);
+  assert_num_in_track (track1, 2);
+
+  track2 = GES_TRACK (ges_video_track_new ());
+  _assert_add_track (timeline, track2);
+  /* other tracks stay the same */
+  assert_num_in_track (track, 2);
+  assert_num_in_track (track1, 2);
+  /* 1 sources + 1 effect */
+  assert_num_in_track (track2, 2);
+
+  /* s1 only has a child created for the new track, not the other two */
+  assert_num_children (s1, 2);
+  _assert_child_in_track (s1, GES_TYPE_EFFECT, track2);
+  _assert_child_in_track (s1, GES_TYPE_VIDEO_TEST_SOURCE, track2);
+  _assert_no_child_in_track (s1, track);
+  _assert_no_child_in_track (s1, track1);
+
+  /* other clips stay the same since their children were already created
+   * with set tracks */
+  assert_num_children (s2, 2);
+  _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, track);
+  _assert_child_in_track (s2, GES_TYPE_AUDIO_TEST_SOURCE, track1);
+  _assert_no_child_in_track (s2, track2);
+
+  assert_num_children (s3, 2);
+  _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track);
+  _assert_child_in_track (s3, GES_TYPE_AUDIO_TEST_SOURCE, track1);
+  _assert_no_child_in_track (s3, track2);
+
+  /* same with an audio track */
+
+  /* remove the core child from s1 */
+  _remove_sources (s1, 1);
+
+  assert_num_children (s1, 1);
+  _assert_child_in_track (s1, GES_TYPE_EFFECT, NULL);
+
+  assert_num_in_track (track, 2);
+  assert_num_in_track (track1, 2);
+  assert_num_in_track (track2, 0);
+
+  /* unset the core tracks for s2 */
+  _remove_from_track (s2, track, 1);
+  _remove_from_track (s2, track1, 1);
+  /* but keep children in clip */
+  assert_num_children (s2, 2);
+
+  assert_num_in_track (track, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 0);
+
+  track3 = GES_TRACK (ges_audio_track_new ());
+  _assert_add_track (timeline, track3);
+  /* other tracks stay the same */
+  assert_num_in_track (track, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 0);
+  /* 2 sources */
+  assert_num_in_track (track3, 2);
+
+  /* s1 creates core for the new track, but effect does not have a track
+   * set since the new track is not a video track */
+  assert_num_children (s1, 2);
+  _assert_child_in_track (s1, GES_TYPE_AUDIO_TEST_SOURCE, track3);
+  _assert_child_in_track (s1, GES_TYPE_EFFECT, NULL);
+  _assert_no_child_in_track (s1, track);
+  _assert_no_child_in_track (s1, track1);
+  _assert_no_child_in_track (s1, track2);
+
+  /* s2 audio core is in the new track, but video remains trackless */
+  assert_num_children (s2, 2);
+  _assert_child_in_track (s2, GES_TYPE_AUDIO_TEST_SOURCE, track3);
+  _assert_child_in_track (s2, GES_TYPE_VIDEO_TEST_SOURCE, NULL);
+  _assert_no_child_in_track (s1, track);
+  _assert_no_child_in_track (s1, track1);
+  _assert_no_child_in_track (s1, track2);
+
+  /* s3 remains the same since core already had tracks */
+  assert_num_children (s3, 2);
+  _assert_child_in_track (s3, GES_TYPE_VIDEO_TEST_SOURCE, track);
+  _assert_child_in_track (s3, GES_TYPE_AUDIO_TEST_SOURCE, track1);
+  _assert_no_child_in_track (s3, track2);
+  _assert_no_child_in_track (s3, track3);
 
   /* theoretically this is all we need to do to ensure cleanup */
   gst_object_unref (timeline);
@@ -369,9 +504,9 @@ GST_END_TEST;
 GST_START_TEST (test_ges_timeline_remove_track)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track;
-  GESTestClip *s1, *s2, *s3;
+  GESClip *s1, *s2, *s3;
   GESTrackElement *t1, *t2, *t3;
   GList *trackelements, *tmp, *layers;
 
@@ -390,32 +525,11 @@ GST_START_TEST (test_ges_timeline_remove_track)
   track = GES_TRACK (ges_video_track_new ());
   fail_unless (track != NULL);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s1, 0, 10);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
-
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s2, 20, 10);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s3, 40, 10);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
 
   GST_DEBUG ("Add the layer to the timeline");
@@ -426,8 +540,7 @@ GST_START_TEST (test_ges_timeline_remove_track)
 
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
 
   GST_DEBUG ("Add the track to the timeline");
@@ -485,8 +598,18 @@ GST_START_TEST (test_ges_timeline_remove_track)
    * 1 by the timeline */
   ASSERT_OBJECT_REFCOUNT (t3, "t3", 3);
 
+  fail_unless (ges_track_element_get_track (t1) == track);
+  fail_unless (ges_track_element_get_track (t2) == track);
+  fail_unless (ges_track_element_get_track (t3) == track);
+
   /* remove the track and check that the track elements have been released */
+  gst_object_ref (track);
   fail_unless (ges_timeline_remove_track (timeline, track));
+  assert_num_in_track (track, 0);
+  gst_object_unref (track);
+  fail_unless (ges_track_element_get_track (t1) == NULL);
+  fail_unless (ges_track_element_get_track (t2) == NULL);
+  fail_unless (ges_track_element_get_track (t3) == NULL);
 
   ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1);
   ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1);
@@ -505,24 +628,174 @@ GST_START_TEST (test_ges_timeline_remove_track)
 
 GST_END_TEST;
 
+GST_START_TEST (test_ges_timeline_remove_layer)
+{
+  GESTimeline *timeline;
+  GESLayer *layer0, *layer1, *layer2, *layer3;
+  GESTrack *track;
+  GESClip *s1, *s2, *s3, *s4, *s5;
+  GList *tmp, *clips, *clip, *layers;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+
+  layer0 = ges_timeline_append_layer (timeline);
+  layer1 = ges_timeline_append_layer (timeline);
+  layer2 = ges_timeline_append_layer (timeline);
+
+  assert_equals_int (ges_layer_get_priority (layer0), 0);
+  assert_equals_int (ges_layer_get_priority (layer1), 1);
+  assert_equals_int (ges_layer_get_priority (layer2), 2);
+
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  _CREATE_SOURCE (layer0, s1, 0, 10);
+  _CREATE_SOURCE (layer1, s2, 0, 10);
+  _CREATE_SOURCE (layer1, s3, 10, 20);
+  _CREATE_SOURCE (layer2, s4, 0, 10);
+  _CREATE_SOURCE (layer2, s5, 10, 20);
+
+  assert_num_in_track (track, 5);
+
+  gst_object_ref (layer1);
+  fail_unless (ges_timeline_remove_layer (timeline, layer1));
+  /* check removed, and rest of the layers stay */
+  layers = ges_timeline_get_layers (timeline);
+  fail_if (g_list_find (layers, layer1));
+  fail_unless (g_list_find (layers, layer0));
+  fail_unless (g_list_find (layers, layer2));
+  g_list_free_full (layers, gst_object_unref);
+  /* keeps its layer priority */
+  assert_equals_int (ges_layer_get_priority (layer1), 1);
+
+  /* Rest also keep their layer priority */
+  /* NOTE: it may be better to resync the layer priorities to plug the
+   * gap, but this way we leave the gap open to add the layer back in */
+  assert_equals_int (ges_layer_get_priority (layer0), 0);
+  assert_equals_int (ges_layer_get_priority (layer2), 2);
+  /* clip children removed from track */
+  assert_num_in_track (track, 3);
+
+  fail_unless (ges_layer_get_timeline (layer1) == NULL);
+  clips = ges_layer_get_clips (layer1);
+  for (clip = clips; clip; clip = clip->next) {
+    fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip->data) == NULL);
+    for (tmp = GES_CONTAINER_CHILDREN (clip->data); tmp; tmp = tmp->next) {
+      GESTrackElement *el = GES_TRACK_ELEMENT (tmp->data);
+      fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (el) == NULL);
+      fail_unless (ges_track_element_get_track (el) == NULL);
+    }
+  }
+  g_list_free_full (clips, gst_object_unref);
+
+  /* layer2 children have same layer priority */
+  clips = ges_layer_get_clips (layer2);
+  for (clip = clips; clip; clip = clip->next) {
+    fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip->data) == timeline);
+    assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip->data), 2);
+    for (tmp = GES_CONTAINER_CHILDREN (clip->data); tmp; tmp = tmp->next) {
+      GESTrackElement *el = GES_TRACK_ELEMENT (tmp->data);
+      fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (el) == timeline);
+      fail_unless (ges_track_element_get_track (el) == track);
+      assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), 2);
+    }
+  }
+  g_list_free_full (clips, gst_object_unref);
+
+  /* layer0 stays the same */
+  clips = ges_layer_get_clips (layer0);
+  for (clip = clips; clip; clip = clip->next) {
+    fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip->data) == timeline);
+    assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip->data), 0);
+    for (tmp = GES_CONTAINER_CHILDREN (clip->data); tmp; tmp = tmp->next) {
+      GESTrackElement *el = GES_TRACK_ELEMENT (tmp->data);
+      fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (el) == timeline);
+      fail_unless (ges_track_element_get_track (el) == track);
+      assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), 0);
+    }
+  }
+  g_list_free_full (clips, gst_object_unref);
+
+  /* can add a new layer with the correct priority */
+  layer3 = ges_timeline_append_layer (timeline);
+
+  assert_equals_int (ges_layer_get_priority (layer0), 0);
+  assert_equals_int (ges_layer_get_priority (layer2), 2);
+  assert_equals_int (ges_layer_get_priority (layer3), 3);
+
+  gst_object_unref (layer1);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 typedef struct
 {
-  GESTestClip **o1, **o2, **o3;
-  GESTrack **tr1, **tr2;
+  GESClip *clips[4];
+  guint num_calls[4];
+  GESTrackElement *effects[3];
+  GESTrack *tr1, *tr2;
+  guint num_unrecognised;
 } SelectTracksData;
 
 static GPtrArray *
 select_tracks_cb (GESTimeline * timeline, GESClip * clip,
-    GESTrackElement * track_element, SelectTracksData * st_data)
+    GESTrackElement * track_element, SelectTracksData * data)
 {
-  GESTrack *track;
-
   GPtrArray *ret = g_ptr_array_new ();
-  track = (clip == (GESClip *) * st_data->o2) ? *st_data->tr2 : *st_data->tr1;
+  gboolean track1 = FALSE;
+  gboolean track2 = FALSE;
+  guint i;
+  gboolean recognise_clip = FALSE;
+
+  for (i = 0; i < 4; i++) {
+    if (clip == data->clips[i]) {
+      data->num_calls[i]++;
+      recognise_clip = TRUE;
+    }
+  }
 
-  gst_object_ref (track);
+  if (!recognise_clip) {
+    GST_DEBUG_OBJECT (timeline, "unrecognised clip %" GES_FORMAT " for "
+        "track element %" GES_FORMAT, GES_ARGS (clip),
+        GES_ARGS (track_element));
+    data->num_unrecognised++;
+    return ret;
+  }
+
+  if (GES_IS_BASE_EFFECT (track_element)) {
+    if (track_element == data->effects[0]) {
+      track1 = TRUE;
+    } else if (track_element == data->effects[1]) {
+      track1 = TRUE;
+      track2 = TRUE;
+    } else if (track_element == data->effects[2]) {
+      track2 = TRUE;
+    } else {
+      GST_DEBUG_OBJECT (timeline, "unrecognised effect %" GES_FORMAT,
+          GES_ARGS (track_element));
+      data->num_unrecognised++;
+    }
+  } else if (GES_IS_SOURCE (track_element)) {
+    if (clip == data->clips[0] || clip == data->clips[1])
+      track1 = TRUE;
+    if (clip == data->clips[1] || clip == data->clips[2])
+      track2 = TRUE;
+    /* clips[3] has no tracks selected */
+  } else {
+    GST_DEBUG_OBJECT (timeline, "unrecognised track element %" GES_FORMAT,
+        GES_ARGS (track_element));
+    data->num_unrecognised++;
+  }
 
-  g_ptr_array_add (ret, track);
+  if (track1)
+    g_ptr_array_add (ret, gst_object_ref (data->tr1));
+  if (track2)
+    g_ptr_array_add (ret, gst_object_ref (data->tr2));
 
   return ret;
 }
@@ -530,12 +803,14 @@ select_tracks_cb (GESTimeline * timeline, GESClip * clip,
 GST_START_TEST (test_ges_timeline_multiple_tracks)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track1, *track2;
-  GESTestClip *s1, *s2, *s3;
-  GESTrackElement *t1, *t2, *t3;
+  GESClip *s1, *s2, *s3, *s4, *transition;
+  GESTrackElement *e1, *e2, *e3, *el, *el2, *e_copy;
+  gboolean found_e1 = FALSE, found_e2 = FALSE, found_e3 = FALSE;
   GList *trackelements, *tmp, *layers;
-  SelectTracksData st_data = { &s1, &s2, &s3, &track1, &track2 };
+  GstControlSource *ctrl_source;
+  SelectTracksData st_data;
 
   ges_init ();
 
@@ -543,9 +818,7 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
   GST_DEBUG ("Create a timeline");
   timeline = ges_timeline_new ();
   fail_unless (timeline != NULL);
-
-  g_signal_connect (timeline, "select-tracks-for-object",
-      G_CALLBACK (select_tracks_cb), &st_data);
+  ges_timeline_set_auto_transition (timeline, TRUE);
 
   GST_DEBUG ("Create a layer");
   layer = ges_layer_new ();
@@ -570,31 +843,71 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
   fail_unless (ges_track_get_timeline (track2) == timeline);
   fail_unless ((gpointer) GST_ELEMENT_PARENT (track2) == (gpointer) timeline);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  /* adding to the layer before it is part of the timeline does not
+   * trigger track selection */
+  /* s1 and s3 can overlap since they are destined for different tracks */
+  /* s2 will overlap both */
+  /* s4 destined for no track */
+  _CREATE_SOURCE (layer, s1, 0, 12);
+  _CREATE_SOURCE (layer, s2, 5, 10);
+  _CREATE_SOURCE (layer, s3, 0, 10);
+  _CREATE_SOURCE (layer, s4, 0, 20);
+
+  e1 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
+  fail_unless (ges_container_add (GES_CONTAINER (s2),
+          GES_TIMELINE_ELEMENT (e1)));
+  e2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv ! vertigotv"));
+  fail_unless (ges_container_add (GES_CONTAINER (s2),
+          GES_TIMELINE_ELEMENT (e2)));
+  e3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha"));
+  fail_unless (ges_container_add (GES_CONTAINER (s2),
+          GES_TIMELINE_ELEMENT (e3)));
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3)));
+
+  assert_num_children (s1, 0);
+  assert_num_children (s2, 3);
+  assert_num_children (s3, 0);
+
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (s2),
+      "scratch-lines", 2, "speed", 50.0, NULL);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_NONE, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0));
+  fail_unless (ges_track_element_set_control_source (e2, ctrl_source,
+          "scratch-lines", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  st_data.tr1 = track1;
+  st_data.tr2 = track2;
+  st_data.clips[0] = s1;
+  st_data.clips[1] = s2;
+  st_data.clips[2] = s3;
+  st_data.clips[3] = s4;
+  st_data.num_calls[0] = 0;
+  st_data.num_calls[1] = 0;
+  st_data.num_calls[2] = 0;
+  st_data.num_calls[3] = 0;
+  st_data.effects[0] = e1;
+  st_data.effects[1] = e2;
+  st_data.effects[2] = e3;
+  st_data.num_unrecognised = 0;
 
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (select_tracks_cb), &st_data);
 
+  /* adding layer to the timeline will trigger track selection, this */
   GST_DEBUG ("Add the layer to the timeline");
   fail_unless (ges_timeline_add_layer (timeline, layer));
   /* The timeline steals our reference to the layer */
@@ -603,70 +916,161 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
 
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
+
+  fail_unless (ges_layer_get_auto_transition (layer));
+
+  assert_equals_int (st_data.num_unrecognised, 0);
 
   /* Make sure the associated TrackElements are in the Track */
-  trackelements = GES_CONTAINER_CHILDREN (s1);
-  fail_unless (trackelements != NULL);
-  t1 = GES_TRACK_ELEMENT ((trackelements)->data);
+  assert_num_children (s1, 1);
+  el = GES_CONTAINER_CHILDREN (s1)->data;
+  fail_unless (GES_IS_SOURCE (el));
+  fail_unless (ges_track_element_get_track (el) == track1);
+  ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
+  /* called once for source */
+  assert_equals_int (st_data.num_calls[0], 1);
+
+  /* 2 sources + 4 effects */
+  assert_num_children (s2, 6);
+  trackelements = GES_CONTAINER_CHILDREN (s2);
+  /* sources at the end */
+  el = g_list_nth_data (trackelements, 5);
+  fail_unless (GES_IS_SOURCE (el));
+  el2 = g_list_nth_data (trackelements, 4);
+  fail_unless (GES_IS_SOURCE (el2));
+
+  /* font-desc is originally "", but on setting switches to Normal, so we
+   * set it explicitly */
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (el),
+      "font-desc", "Normal", NULL);
+  assert_equal_children_properties (el, el2);
+  assert_equal_bindings (el, el2);
+
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (el),
+      GES_TIMELINE_ELEMENT_PRIORITY (el2));
+
+  /* check one in each track */
+  fail_unless (ges_track_element_get_track (el)
+      != ges_track_element_get_track (el2));
+  fail_unless (ges_track_element_get_track (el) == track1
+      || ges_track_element_get_track (el2) == track1);
+  fail_unless (ges_track_element_get_track (el) == track2
+      || ges_track_element_get_track (el2) == track2);
+
+  /* effects */
+  e_copy = NULL;
   for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* There are 3 references held:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-    fail_unless (ges_track_element_get_track (tmp->data) == track1);
+    el = tmp->data;
+    ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
+    if (GES_IS_BASE_EFFECT (el)) {
+      if (el == e1) {
+        fail_if (found_e1);
+        found_e1 = TRUE;
+      } else if (el == e2) {
+        fail_if (found_e2);
+        found_e2 = TRUE;
+      } else if (el == e3) {
+        fail_if (found_e3);
+        found_e3 = TRUE;
+      } else {
+        fail_if (e_copy);
+        e_copy = el;
+      }
+    }
   }
-  gst_object_ref (t1);
-  /* There are 3 references held:
-   * 1 by the container
-   * 1 by the track
-   * 1 by the timeline
-   * 1 added by ourselves above (gst_object_ref (t1)) */
-  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4);
-
-  trackelements = GES_CONTAINER_CHILDREN (s2);
-  fail_unless (trackelements != NULL);
-  t2 = GES_TRACK_ELEMENT (trackelements->data);
+  fail_unless (found_e1);
+  fail_unless (found_e2);
+  fail_unless (found_e3);
+  fail_unless (e_copy);
+
+  fail_unless (ges_track_element_get_track (e1) == track1);
+  fail_unless (ges_track_element_get_track (e3) == track2);
+
+  assert_equal_children_properties (e2, e_copy);
+  assert_equal_bindings (e2, e_copy);
+
+  /* check one in each track */
+  fail_unless (ges_track_element_get_track (e2)
+      != ges_track_element_get_track (e_copy));
+  fail_unless (ges_track_element_get_track (e2) == track1
+      || ges_track_element_get_track (e_copy) == track1);
+  fail_unless (ges_track_element_get_track (e2) == track2
+      || ges_track_element_get_track (e_copy) == track2);
+
+  /* e2 copy placed next to e2 in top effect list */
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e_copy)));
+  assert_equals_int (3,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3)));
+
+  /* called 4 times: 1 for source, and 1 for each effect (3) */
+  assert_equals_int (st_data.num_calls[1], 4);
+
+  assert_num_children (s3, 1);
+  el = GES_CONTAINER_CHILDREN (s3)->data;
+  fail_unless (GES_IS_SOURCE (el));
+  fail_unless (ges_track_element_get_track (el) == track2);
+  ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
+  /* called once for source */
+  assert_equals_int (st_data.num_calls[2], 1);
+
+  /* one child but no track */
+  assert_num_children (s4, 1);
+  el = GES_CONTAINER_CHILDREN (s4)->data;
+  fail_unless (GES_IS_SOURCE (el));
+  fail_unless (ges_track_element_get_track (el) == NULL);
+  ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1);
+  /* called once for source (where no track was selected) */
+  assert_equals_int (st_data.num_calls[0], 1);
+
+  /* 2 sources + 1 transition + 2 effects */
+  assert_num_in_track (track1, 5);
+  assert_num_in_track (track2, 5);
+
+  el = NULL;
+  trackelements = ges_track_get_elements (track1);
   for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* There are 3 references held:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-    fail_unless (ges_track_element_get_track (tmp->data) == track2);
+    if (GES_IS_VIDEO_TRANSITION (tmp->data)) {
+      fail_if (el);
+      el = tmp->data;
+    }
   }
-  gst_object_ref (t2);
-  /* There are 3 references held:
-   * 1 by the container
-   * 1 by the track
-   * 1 by the timeline
-   * 1 added by ourselves above (gst_object_ref (t2)) */
-  ASSERT_OBJECT_REFCOUNT (t2, "t2", 4);
-
-  trackelements = GES_CONTAINER_CHILDREN (s3);
-  fail_unless (trackelements != NULL);
-  t3 = GES_TRACK_ELEMENT (trackelements->data);
+  g_list_free_full (trackelements, gst_object_unref);
+  fail_unless (GES_IS_CLIP (GES_TIMELINE_ELEMENT_PARENT (el)));
+  transition = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (el));
+  assert_layer (transition, layer);
+
+  CHECK_OBJECT_PROPS (transition, 5, 0, 7);
+  CHECK_OBJECT_PROPS (el, 5, 0, 7);
+  fail_unless (ges_track_element_get_track (el) == track1);
+  /* make sure we can change the transition type */
+  fail_unless (ges_video_transition_set_transition_type (GES_VIDEO_TRANSITION
+          (el), GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H));
+
+  el = NULL;
+  trackelements = ges_track_get_elements (track2);
   for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* There are 3 references held:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-    fail_unless (ges_track_element_get_track (tmp->data) == track1);
+    if (GES_IS_VIDEO_TRANSITION (tmp->data)) {
+      fail_if (el);
+      el = tmp->data;
+    }
   }
-  gst_object_ref (t3);
-  /* There are 3 references held:
-   * 1 by the container
-   * 1 by the track
-   * 1 by the timeline
-   * 1 added by ourselves above (gst_object_ref (t3)) */
-  ASSERT_OBJECT_REFCOUNT (t3, "t3", 4);
-
-  gst_object_unref (t1);
-  gst_object_unref (t2);
-  gst_object_unref (t3);
+  g_list_free_full (trackelements, gst_object_unref);
+  fail_unless (GES_IS_CLIP (GES_TIMELINE_ELEMENT_PARENT (el)));
+  transition = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (el));
+  assert_layer (transition, layer);
+
+  CHECK_OBJECT_PROPS (transition, 5, 0, 5);
+  CHECK_OBJECT_PROPS (el, 5, 0, 5);
+  fail_unless (ges_track_element_get_track (el) == track2);
+  /* make sure we can change the transition type */
+  fail_unless (ges_video_transition_set_transition_type (GES_VIDEO_TRANSITION
+          (el), GES_VIDEO_STANDARD_TRANSITION_TYPE_BARNDOOR_H));
 
   gst_object_unref (timeline);
 
@@ -789,6 +1193,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_ges_timeline_add_layer);
   tcase_add_test (tc_chain, test_ges_timeline_add_layer_first);
   tcase_add_test (tc_chain, test_ges_timeline_remove_track);
+  tcase_add_test (tc_chain, test_ges_timeline_remove_layer);
   tcase_add_test (tc_chain, test_ges_timeline_multiple_tracks);
   tcase_add_test (tc_chain, test_ges_pipeline_change_state);
   tcase_add_test (tc_chain, test_ges_timeline_element_name);
index 0720840..f2a9d23 100644 (file)
 #include <ges/ges.h>
 #include <gst/check/gstcheck.h>
 
+#define _assert_add(clip, child) \
+  fail_unless (ges_container_add (GES_CONTAINER (clip), \
+        GES_TIMELINE_ELEMENT (child)))
+
+#define _assert_remove(clip, child) \
+  fail_unless (ges_container_remove (GES_CONTAINER (clip), \
+        GES_TIMELINE_ELEMENT (child)))
+
 GST_START_TEST (test_object_properties)
 {
   GESClip *clip;
@@ -54,7 +62,7 @@ GST_START_TEST (test_object_properties)
 
   ges_layer_add_clip (layer, GES_CLIP (clip));
   ges_timeline_commit (timeline);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
+  assert_num_children (clip, 1);
   trackelement = GES_CONTAINER_CHILDREN (clip)->data;
   fail_unless (trackelement != NULL);
   fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
@@ -95,8 +103,7 @@ GST_START_TEST (test_object_properties)
   nle_object_check (ges_track_element_get_nleobject (trackelement), 400, 510,
       120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE);
 
-  ges_container_remove (GES_CONTAINER (clip),
-      GES_TIMELINE_ELEMENT (trackelement));
+  _assert_remove (clip, trackelement);
 
   gst_object_unref (timeline);
 
@@ -133,7 +140,7 @@ GST_START_TEST (test_split_direct_bindings)
   g_object_unref (asset);
 
   CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
+  assert_num_children (clip, 1);
   check_layer (clip, 0);
 
   source = gst_interpolation_control_source_new ();
@@ -196,7 +203,10 @@ GST_START_TEST (test_split_direct_bindings)
 
   CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 5 * GST_SECOND);
   check_layer (clip, 0);
+
   gst_object_unref (timeline);
+  gst_object_unref (source);
+  gst_object_unref (splitsource);
 
   ges_deinit ();
 }
@@ -231,7 +241,7 @@ GST_START_TEST (test_split_direct_absolute_bindings)
   g_object_unref (asset);
 
   CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
+  assert_num_children (clip, 1);
   check_layer (clip, 0);
 
   source = gst_interpolation_control_source_new ();
@@ -296,19 +306,176 @@ GST_START_TEST (test_split_direct_absolute_bindings)
   check_layer (clip, 0);
 
   gst_object_unref (timeline);
+  gst_object_unref (source);
+  gst_object_unref (splitsource);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+static GESTimelineElement *
+_find_auto_transition (GESTrack * track, GESClip * from_clip, GESClip * to_clip)
+{
+  GstClockTime start, end;
+  GList *tmp, *track_els;
+  GESTimelineElement *ret = NULL;
+  GESLayer *layer0, *layer1;
+
+  layer0 = ges_clip_get_layer (from_clip);
+  layer1 = ges_clip_get_layer (to_clip);
+
+  fail_unless (layer0 == layer1, "%" GES_FORMAT " and %" GES_FORMAT " do not "
+      "share the same layer", GES_ARGS (from_clip), GES_ARGS (to_clip));
+  gst_object_unref (layer1);
+
+  start = GES_TIMELINE_ELEMENT_START (to_clip);
+  end = GES_TIMELINE_ELEMENT_START (from_clip)
+      + GES_TIMELINE_ELEMENT_DURATION (from_clip);
+
+  fail_if (end <= start, "%" GES_FORMAT " starts after %" GES_FORMAT " ends",
+      GES_ARGS (to_clip), GES_ARGS (from_clip));
+
+  track_els = ges_track_get_elements (track);
+
+  for (tmp = track_els; tmp; tmp = tmp->next) {
+    GESTimelineElement *el = tmp->data;
+    if (GES_IS_TRANSITION (el) && el->start == start
+        && (el->start + el->duration) == end) {
+      fail_if (ret, "Found two transitions %" GES_FORMAT " and %" GES_FORMAT
+          " between %" GES_FORMAT " and %" GES_FORMAT " in track %"
+          GST_PTR_FORMAT, GES_ARGS (el), GES_ARGS (ret), GES_ARGS (from_clip),
+          GES_ARGS (to_clip), track);
+      ret = el;
+    }
+  }
+  fail_unless (ret, "Found no transitions between %" GES_FORMAT " and %"
+      GES_FORMAT " in track %" GST_PTR_FORMAT, GES_ARGS (from_clip),
+      GES_ARGS (to_clip), track);
+
+  g_list_free_full (track_els, gst_object_unref);
+
+  fail_unless (GES_IS_CLIP (ret->parent), "Transition %" GES_FORMAT
+      " between %" GES_FORMAT " and %" GES_FORMAT " in track %"
+      GST_PTR_FORMAT " has no parent clip", GES_ARGS (ret),
+      GES_ARGS (from_clip), GES_ARGS (to_clip), track);
+
+  layer1 = ges_clip_get_layer (GES_CLIP (ret->parent));
+
+  fail_unless (layer0 == layer1, "Transition %" GES_FORMAT " between %"
+      GES_FORMAT " and %" GES_FORMAT " in track %" GST_PTR_FORMAT
+      " belongs to layer %" GST_PTR_FORMAT " rather than %" GST_PTR_FORMAT,
+      GES_ARGS (ret), GES_ARGS (from_clip), GES_ARGS (to_clip), track,
+      layer1, layer0);
+
+  gst_object_unref (layer0);
+  gst_object_unref (layer1);
+
+  return ret;
+}
+
+GST_START_TEST (test_split_with_auto_transitions)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTrack *tracks[3];
+  GESTimelineElement *found;
+  GESTimelineElement *prev_trans[3];
+  GESTimelineElement *post_trans[3];
+  GESAsset *asset;
+  GESClip *clip, *split, *prev, *post;
+  guint i;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+
+  ges_timeline_set_auto_transition (timeline, TRUE);
+
+  tracks[0] = GES_TRACK (ges_audio_track_new ());
+  tracks[1] = GES_TRACK (ges_audio_track_new ());
+  tracks[2] = GES_TRACK (ges_video_track_new ());
+
+  for (i = 0; i < 3; i++)
+    fail_unless (ges_timeline_add_track (timeline, tracks[i]));
+
+  layer = ges_timeline_append_layer (timeline);
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
+
+  prev = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
+  clip = ges_layer_add_asset (layer, asset, 5, 0, 20, GES_TRACK_TYPE_UNKNOWN);
+  post = ges_layer_add_asset (layer, asset, 20, 0, 10, GES_TRACK_TYPE_UNKNOWN);
+
+  fail_unless (prev);
+  fail_unless (clip);
+  fail_unless (post);
+
+  for (i = 0; i < 3; i++) {
+    prev_trans[i] = _find_auto_transition (tracks[i], prev, clip);
+    post_trans[i] = _find_auto_transition (tracks[i], clip, post);
+    /* 3 sources, 2 auto-transitions */
+    assert_num_in_track (tracks[i], 5);
+
+  }
+
+  /* cannot split within a transition */
+  fail_if (ges_clip_split (clip, 5));
+  fail_if (ges_clip_split (clip, 20));
+
+  /* we should keep the same auto-transitions during a split */
+  split = ges_clip_split (clip, 15);
+  fail_unless (split);
+
+  for (i = 0; i < 3; i++) {
+    found = _find_auto_transition (tracks[i], prev, clip);
+    fail_unless (found == prev_trans[i], "Transition between %" GES_FORMAT
+        " and %" GES_FORMAT " changed", GES_ARGS (prev), GES_ARGS (clip));
+
+    found = _find_auto_transition (tracks[i], split, post);
+    fail_unless (found == post_trans[i], "Transition between %" GES_FORMAT
+        " and %" GES_FORMAT " changed", GES_ARGS (clip), GES_ARGS (post));
+  }
+
+  gst_object_unref (timeline);
+  gst_object_unref (asset);
+
   ges_deinit ();
 }
 
 GST_END_TEST;
 
+static GPtrArray *
+_select_none (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, guint * called_p)
+{
+  (*called_p)++;
+  return NULL;
+}
+
+static GPtrArray *
+_select_track (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, GESTrack ** track_p)
+{
+  GPtrArray *tracks = g_ptr_array_new ();
+  fail_unless (track_p);
+  fail_unless (*track_p);
+  g_ptr_array_insert (tracks, -1, gst_object_ref (*track_p));
+  *track_p = NULL;
+  return tracks;
+}
 
 GST_START_TEST (test_split_object)
 {
   GESTimeline *timeline;
+  GESTrack *track1, *track2, *effect_track;
   GESLayer *layer;
   GESClip *clip, *splitclip;
   GList *splittrackelements;
-  GESTrackElement *trackelement, *splittrackelement;
+  GESTrackElement *trackelement1, *trackelement2, *effect1, *effect2,
+      *splittrackelement;
+  guint32 priority1, priority2, effect_priority1, effect_priority2;
+  guint selection_called = 0;
+  const gchar *meta;
 
   ges_init ();
 
@@ -327,58 +494,157 @@ GST_START_TEST (test_split_object)
   g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 50,
       "in-point", (guint64) 12, NULL);
   ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1);
-  assert_equals_uint64 (_START (clip), 42);
-  assert_equals_uint64 (_DURATION (clip), 50);
-  assert_equals_uint64 (_INPOINT (clip), 12);
+  CHECK_OBJECT_PROPS (clip, 42, 12, 50);
 
   ges_layer_add_clip (layer, GES_CLIP (clip));
   ges_timeline_commit (timeline);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 2);
-  trackelement = GES_CONTAINER_CHILDREN (clip)->data;
-  fail_unless (trackelement != NULL);
-  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
+  assert_num_children (clip, 2);
+  trackelement1 = GES_CONTAINER_CHILDREN (clip)->data;
+  fail_unless (trackelement1 != NULL);
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement1) ==
+      GES_TIMELINE_ELEMENT (clip));
+  ges_meta_container_set_string (GES_META_CONTAINER (trackelement1), "test_key",
+      "test_value");
+
+  trackelement2 = GES_CONTAINER_CHILDREN (clip)->next->data;
+  fail_unless (trackelement2 != NULL);
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement2) ==
       GES_TIMELINE_ELEMENT (clip));
 
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  _assert_add (clip, effect1);
+
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
+  _assert_add (clip, effect2);
+
   /* Check that trackelement has the same properties */
-  assert_equals_uint64 (_START (trackelement), 42);
-  assert_equals_uint64 (_DURATION (trackelement), 50);
-  assert_equals_uint64 (_INPOINT (trackelement), 12);
+  CHECK_OBJECT_PROPS (trackelement1, 42, 12, 50);
+  CHECK_OBJECT_PROPS (trackelement2, 42, 12, 50);
+  CHECK_OBJECT_PROPS (effect1, 42, 0, 50);
+  CHECK_OBJECT_PROPS (effect2, 42, 0, 50);
 
   /* And let's also check that it propagated correctly to GNonLin */
-  nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 50, 12,
-      50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE);
+  nle_object_check (ges_track_element_get_nleobject (trackelement1), 42, 50, 12,
+      50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE);
+  nle_object_check (ges_track_element_get_nleobject (trackelement2), 42, 50, 12,
+      50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE);
+
+  track1 = ges_track_element_get_track (trackelement1);
+  fail_unless (track1);
+  track2 = ges_track_element_get_track (trackelement2);
+  fail_unless (track2);
+  fail_unless (track1 != track2);
+  effect_track = ges_track_element_get_track (effect1);
+  fail_unless (effect_track);
+  fail_unless (ges_track_element_get_track (effect2) == effect_track);
+
+  priority1 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement1);
+  priority2 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement2);
+  effect_priority1 = GES_TIMELINE_ELEMENT_PRIORITY (effect1);
+  effect_priority2 = GES_TIMELINE_ELEMENT_PRIORITY (effect2);
+
+  fail_unless (priority1 == priority2);
+  fail_unless (priority1 > effect_priority2);
+  fail_unless (effect_priority2 > effect_priority1);
+
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip),
+      "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1,
+      "freq", 449.0, "scratch-lines", 2, "zoom-speed", 1.05, NULL);
+
+  /* splitting should avoid track selection */
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
 
   splitclip = ges_clip_split (clip, 67);
   fail_unless (GES_IS_CLIP (splitclip));
+  fail_unless (splitclip != clip);
 
-  assert_equals_uint64 (_START (clip), 42);
-  assert_equals_uint64 (_DURATION (clip), 25);
-  assert_equals_uint64 (_INPOINT (clip), 12);
+  fail_if (selection_called);
+
+  CHECK_OBJECT_PROPS (clip, 42, 12, 25);
+  CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25);
+  CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25);
+  CHECK_OBJECT_PROPS (effect1, 42, 0, 25);
+  CHECK_OBJECT_PROPS (effect2, 42, 0, 25);
 
-  assert_equals_uint64 (_START (splitclip), 67);
-  assert_equals_uint64 (_DURATION (splitclip), 25);
-  assert_equals_uint64 (_INPOINT (splitclip), 37);
+  CHECK_OBJECT_PROPS (splitclip, 67, 37, 25);
+
+  assert_equal_children_properties (splitclip, clip);
 
   splittrackelements = GES_CONTAINER_CHILDREN (splitclip);
-  fail_unless_equals_int (g_list_length (splittrackelements), 2);
+  fail_unless_equals_int (g_list_length (splittrackelements), 4);
 
+  /* first is the effects */
   splittrackelement = GES_TRACK_ELEMENT (splittrackelements->data);
   fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
-  assert_equals_uint64 (_START (splittrackelement), 67);
-  assert_equals_uint64 (_DURATION (splittrackelement), 25);
-  assert_equals_uint64 (_INPOINT (splittrackelement), 37);
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25);
 
-  fail_unless (splittrackelement != trackelement);
-  fail_unless (splitclip != clip);
+  assert_equal_children_properties (splittrackelement, effect1);
+  fail_unless (ges_track_element_get_track (splittrackelement) == effect_track);
+  fail_unless (ges_track_element_get_track (effect1) == effect_track);
+  /* +3 priority from layer */
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      effect_priority1 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect1) == effect_priority1);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
 
   splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->data);
   fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
-  assert_equals_uint64 (_START (splittrackelement), 67);
-  assert_equals_uint64 (_DURATION (splittrackelement), 25);
-  assert_equals_uint64 (_INPOINT (splittrackelement), 37);
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25);
 
-  fail_unless (splittrackelement != trackelement);
-  fail_unless (splitclip != clip);
+  assert_equal_children_properties (splittrackelement, effect2);
+  fail_unless (ges_track_element_get_track (splittrackelement) == effect_track);
+  fail_unless (ges_track_element_get_track (effect2) == effect_track);
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      effect_priority2 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect2) == effect_priority2);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
+
+  splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->next->data);
+  fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25);
+
+  /* core elements have swapped order in the clip, this is ok since they
+   * share the same priority */
+  assert_equal_children_properties (splittrackelement, trackelement2);
+  fail_unless (ges_track_element_get_track (splittrackelement) == track2);
+  fail_unless (ges_track_element_get_track (trackelement2) == track2);
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      priority2 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement2) == priority2);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
+
+  splittrackelement =
+      GES_TRACK_ELEMENT (splittrackelements->next->next->next->data);
+  fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25);
+
+  assert_equal_children_properties (splittrackelement, trackelement1);
+  fail_unless (ges_track_element_get_track (splittrackelement) == track1);
+  fail_unless (ges_track_element_get_track (trackelement1) == track1);
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      priority1 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement1) == priority2);
+  meta = ges_meta_container_get_string (GES_META_CONTAINER (splittrackelement),
+      "test_key");
+  fail_unless_equals_string (meta, "test_value");
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
 
   /* We own the only ref */
   ASSERT_OBJECT_REFCOUNT (splitclip, "1 ref for us + 1 for the timeline", 2);
@@ -395,15 +661,194 @@ GST_START_TEST (test_split_object)
 
 GST_END_TEST;
 
+typedef struct
+{
+  gboolean duration_cb_called;
+  gboolean clip_added_cb_called;
+  gboolean track_selected_cb_called;
+  GESClip *clip;
+} SplitOrderData;
+
+static void
+_track_selected_cb (GESTimelineElement * el, GParamSpec * spec,
+    SplitOrderData * data)
+{
+  GESClip *clip = GES_CLIP (el->parent);
+
+  fail_unless (data->clip == clip, "Parent is %" GES_FORMAT " rather than %"
+      GES_FORMAT, GES_ARGS (clip), GES_ARGS (data->clip));
+
+  fail_unless (data->duration_cb_called, "notify::duration not emitted "
+      "for neighbour of %" GES_FORMAT, GES_ARGS (data->clip));
+  fail_unless (data->clip_added_cb_called, "child-added not emitted for %"
+      GES_FORMAT, GES_ARGS (data->clip));
+
+  data->track_selected_cb_called = TRUE;
+}
+
+static void
+_child_added_cb (GESClip * clip, GESTimelineElement * child,
+    SplitOrderData * data)
+{
+  fail_unless (data->clip == clip, "Received %" GES_FORMAT " rather than %"
+      GES_FORMAT, GES_ARGS (clip), GES_ARGS (data->clip));
+
+  g_signal_connect (child, "notify::track", G_CALLBACK (_track_selected_cb),
+      data);
+}
+
+static void
+_clip_added_cb (GESLayer * layer, GESClip * clip, SplitOrderData * data)
+{
+  GList *tmp;
+
+  data->clip = clip;
+
+  fail_unless (data->duration_cb_called, "notify::duration not emitted "
+      "for neighbour of %" GES_FORMAT, GES_ARGS (data->clip));
+  /* only called once */
+  fail_if (data->clip_added_cb_called, "clip-added already emitted for %"
+      GES_FORMAT, GES_ARGS (data->clip));
+  fail_if (data->track_selected_cb_called, "track selection already "
+      "occurred for %" GES_FORMAT, GES_ARGS (data->clip));
+
+  data->clip_added_cb_called = TRUE;
+
+  g_signal_connect (clip, "child-added", G_CALLBACK (_child_added_cb), data);
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    g_signal_connect (tmp->data, "notify::track",
+        G_CALLBACK (_track_selected_cb), data);
+}
+
+static void
+_disconnect_cbs (GESLayer * layer, GESClip * clip, SplitOrderData * data)
+{
+  GList *tmp;
+  g_signal_handlers_disconnect_by_func (clip, _child_added_cb, data);
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    g_signal_handlers_disconnect_by_func (tmp->data, _track_selected_cb, data);
+}
+
+static void
+_duration_cb (GObject * object, GParamSpec * pspec, SplitOrderData * data)
+{
+  /* only called once */
+  fail_if (data->duration_cb_called, "notify::duration of neighbour %"
+      GES_FORMAT " already emitted ", GES_ARGS (object));
+  fail_if (data->clip_added_cb_called, "clip-added already emitted");
+  fail_if (data->track_selected_cb_called, "track selection already "
+      "occurred");
+
+  data->duration_cb_called = TRUE;
+}
+
+GST_START_TEST (test_split_ordering)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip, *splitclip;
+  SplitOrderData data;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_duration (clip, 10);
+
+  /* test order when adding clip to a layer */
+  /* don't care about duration yet */
+  data.duration_cb_called = TRUE;
+  data.clip_added_cb_called = FALSE;
+  data.track_selected_cb_called = FALSE;
+  data.clip = NULL;
+
+  g_signal_connect (layer, "clip-added", G_CALLBACK (_clip_added_cb), &data);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  fail_unless (data.duration_cb_called);
+  fail_unless (data.clip_added_cb_called);
+  fail_unless (data.track_selected_cb_called);
+  fail_unless (data.clip == clip);
+
+  /* now check for the same ordering when splitting, which the original
+   * clip shrinking before the new one is added to the layer */
+  data.duration_cb_called = FALSE;
+  data.clip_added_cb_called = FALSE;
+  data.track_selected_cb_called = FALSE;
+  data.clip = NULL;
+
+  g_signal_connect (clip, "notify::duration", G_CALLBACK (_duration_cb), &data);
+
+  splitclip = ges_clip_split (clip, 5);
+
+  fail_unless (splitclip);
+  fail_unless (data.duration_cb_called);
+  fail_unless (data.clip_added_cb_called);
+  fail_unless (data.track_selected_cb_called);
+  fail_unless (data.clip == splitclip);
+
+  /* disconnect since track of children will change when timeline is
+   * freed */
+  _disconnect_cbs (layer, clip, &data);
+  _disconnect_cbs (layer, splitclip, &data);
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _assert_higher_priority(el, higher) \
+{ \
+  if (higher) { \
+    guint32 el_prio = GES_TIMELINE_ELEMENT_PRIORITY (el); \
+    guint32 higher_prio = GES_TIMELINE_ELEMENT_PRIORITY (higher); \
+    fail_unless (el_prio > higher_prio, "%s does not have a higher " \
+        "priority than %s (%u vs %u)", GES_TIMELINE_ELEMENT_NAME (el), \
+        GES_TIMELINE_ELEMENT_NAME (higher), el_prio, higher_prio); \
+  } \
+}
+
+#define _assert_regroup_fails(clip_list) \
+{ \
+  GESContainer *regrouped = ges_container_group (clip_list); \
+  fail_unless (GES_IS_GROUP (regrouped)); \
+  assert_equals_int (g_list_length (regrouped->children), \
+      g_list_length (clip_list)); \
+  g_list_free_full (ges_container_ungroup (regrouped, FALSE), \
+      gst_object_unref); \
+}
+
 GST_START_TEST (test_clip_group_ungroup)
 {
   GESAsset *asset;
   GESTimeline *timeline;
-  GESClip *clip, *clip2;
+  GESClip *clip, *video_clip, *audio_clip;
+  GESTrackElement *el;
   GList *containers, *tmp;
   GESLayer *layer;
   GESContainer *regrouped_clip;
   GESTrack *audio_track, *video_track;
+  guint selection_called = 0;
+  struct
+  {
+    GESTrackElement *element;
+    GESTrackElement *higher_priority;
+  } audio_els[2];
+  struct
+  {
+    GESTrackElement *element;
+    GESTrackElement *higher_priority;
+  } video_els[3];
+  guint i, j;
+  const gchar *name;
+  GESTrackType type;
 
   ges_init ();
 
@@ -420,101 +865,385 @@ GST_START_TEST (test_clip_group_ungroup)
   assert_is_type (asset, GES_TYPE_ASSET);
 
   clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
-  ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_elements", 2);
-  assert_equals_uint64 (_START (clip), 0);
-  assert_equals_uint64 (_INPOINT (clip), 0);
-  assert_equals_uint64 (_DURATION (clip), 10);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 2);
+  ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_els", 2);
+  assert_num_children (clip, 2);
+  CHECK_OBJECT_PROPS (clip, 0, 0, 10);
+
+  el = GES_TRACK_ELEMENT (ges_effect_new ("audioecho"));
+  ges_track_element_set_track_type (el, GES_TRACK_TYPE_AUDIO);
+  _assert_add (clip, el);
+
+  el = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO);
+  _assert_add (clip, el);
+
+  el = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
+  ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO);
+  _assert_add (clip, el);
+
+  assert_num_children (clip, 5);
+  CHECK_OBJECT_PROPS (clip, 0, 0, 10);
+
+  i = j = 0;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    el = tmp->data;
+    type = ges_track_element_get_track_type (el);
+    if (type == GES_TRACK_TYPE_AUDIO) {
+      fail_unless (i < G_N_ELEMENTS (audio_els));
+      audio_els[i].element = el;
+      fail_unless (ges_track_element_get_track (el) == audio_track,
+          "%s not in audio track", GES_TIMELINE_ELEMENT_NAME (el));
+      if (i == 0)
+        audio_els[i].higher_priority = NULL;
+      else
+        audio_els[i].higher_priority = audio_els[i - 1].element;
+      _assert_higher_priority (el, audio_els[i].higher_priority);
+      i++;
+    }
+    if (type == GES_TRACK_TYPE_VIDEO) {
+      fail_unless (j < G_N_ELEMENTS (video_els));
+      video_els[j].element = el;
+      fail_unless (ges_track_element_get_track (el) == video_track,
+          "%s not in video track", GES_TIMELINE_ELEMENT_NAME (el));
+      if (j == 0)
+        video_els[j].higher_priority = NULL;
+      else
+        video_els[j].higher_priority = video_els[j - 1].element;
+      _assert_higher_priority (el, video_els[j].higher_priority);
+      j++;
+    }
+  }
+  fail_unless (i == G_N_ELEMENTS (audio_els));
+  fail_unless (j == G_N_ELEMENTS (video_els));
+  assert_num_in_track (audio_track, 2);
+  assert_num_in_track (video_track, 3);
+
+  /* group and ungroup should avoid track selection */
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
 
   containers = ges_container_ungroup (GES_CONTAINER (clip), FALSE);
+
+  fail_if (selection_called);
+
+  video_clip = NULL;
+  audio_clip = NULL;
+
   assert_equals_int (g_list_length (containers), 2);
-  fail_unless (clip == containers->data);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
-  assert_equals_uint64 (_START (clip), 0);
-  assert_equals_uint64 (_INPOINT (clip), 0);
-  assert_equals_uint64 (_DURATION (clip), 10);
-  ASSERT_OBJECT_REFCOUNT (clip, "1 for the layer + 1 for the timeline + "
+
+  type = ges_clip_get_supported_formats (containers->data);
+  if (type == GES_TRACK_TYPE_VIDEO)
+    video_clip = containers->data;
+  if (type == GES_TRACK_TYPE_AUDIO)
+    audio_clip = containers->data;
+
+  type = ges_clip_get_supported_formats (containers->next->data);
+  if (type == GES_TRACK_TYPE_VIDEO)
+    video_clip = containers->next->data;
+  if (type == GES_TRACK_TYPE_AUDIO)
+    audio_clip = containers->next->data;
+
+  fail_unless (video_clip);
+  fail_unless (audio_clip);
+  fail_unless (video_clip == clip || audio_clip == clip);
+
+  assert_layer (video_clip, layer);
+  assert_num_children (video_clip, 3);
+  fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (video_clip) == timeline);
+  CHECK_OBJECT_PROPS (video_clip, 0, 0, 10);
+  ASSERT_OBJECT_REFCOUNT (video_clip, "1 for the layer + 1 for the timeline + "
       "1 in containers list", 3);
 
-  clip2 = containers->next->data;
-  fail_if (clip2 == clip);
-  fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip2) != NULL);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1);
-  assert_equals_uint64 (_START (clip2), 0);
-  assert_equals_uint64 (_INPOINT (clip2), 0);
-  assert_equals_uint64 (_DURATION (clip2), 10);
-  ASSERT_OBJECT_REFCOUNT (clip2, "1 for the layer + 1 for the timeline +"
-      " 1 in containers list", 3);
-
-  tmp = ges_track_get_elements (audio_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container "
-      "+ 1 for the timeline + 1 in tmp list", 4);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_AUDIO);
-  assert_equals_int (ges_clip_get_supported_formats (GES_CLIP
-          (ges_timeline_element_get_parent (tmp->data))), GES_TRACK_TYPE_AUDIO);
-  g_list_free_full (tmp, gst_object_unref);
-  tmp = ges_track_get_elements (video_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container "
-      "+ 1 for the timeline + 1 in tmp list", 4);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_VIDEO);
-  assert_equals_int (ges_clip_get_supported_formats (GES_CLIP
-          (ges_timeline_element_get_parent (tmp->data))), GES_TRACK_TYPE_VIDEO);
-  g_list_free_full (tmp, gst_object_unref);
-
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 10);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
-  assert_equals_uint64 (_START (clip), 10);
-  assert_equals_uint64 (_INPOINT (clip), 0);
-  assert_equals_uint64 (_DURATION (clip), 10);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1);
-  assert_equals_uint64 (_START (clip2), 0);
-  assert_equals_uint64 (_INPOINT (clip2), 0);
-  assert_equals_uint64 (_DURATION (clip2), 10);
+  assert_layer (audio_clip, layer);
+  assert_num_children (audio_clip, 2);
+  fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (audio_clip) == timeline);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+  ASSERT_OBJECT_REFCOUNT (audio_clip, "1 for the layer + 1 for the timeline + "
+      "1 in containers list", 3);
 
-  regrouped_clip = ges_container_group (containers);
-  fail_unless (GES_IS_GROUP (regrouped_clip));
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)),
-      2);
-  tmp = ges_container_ungroup (regrouped_clip, FALSE);
-  g_list_free_full (tmp, gst_object_unref);
+  for (i = 0; i < G_N_ELEMENTS (audio_els); i++) {
+    el = audio_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == audio_track,
+        "%s not in audio track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (audio_clip), "%s not in the audio clip", name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, audio_els[i].higher_priority);
+  }
+  for (i = 0; i < G_N_ELEMENTS (video_els); i++) {
+    el = video_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == video_track,
+        "%s not in video track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (video_clip), "%s not in the video clip", name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, video_els[i].higher_priority);
+  }
+  assert_num_in_track (audio_track, 2);
+  assert_num_in_track (video_track, 3);
+
+  assert_set_start (video_clip, 10);
+  CHECK_OBJECT_PROPS (video_clip, 10, 0, 10);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+
+  _assert_regroup_fails (containers);
+
+  assert_set_start (video_clip, 0);
+  assert_set_inpoint (video_clip, 10);
+  CHECK_OBJECT_PROPS (video_clip, 0, 10, 10);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+
+  _assert_regroup_fails (containers);
+
+  assert_set_inpoint (video_clip, 0);
+  assert_set_duration (video_clip, 15);
+  CHECK_OBJECT_PROPS (video_clip, 0, 0, 15);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+
+  _assert_regroup_fails (containers);
+
+  assert_set_duration (video_clip, 10);
+  CHECK_OBJECT_PROPS (video_clip, 0, 0, 10);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
 
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 0);
   regrouped_clip = ges_container_group (containers);
+
+  fail_if (selection_called);
+
   assert_is_type (regrouped_clip, GES_TYPE_CLIP);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)),
-      2);
+  assert_num_children (regrouped_clip, 5);
   assert_equals_int (ges_clip_get_supported_formats (GES_CLIP (regrouped_clip)),
       GES_TRACK_TYPE_VIDEO | GES_TRACK_TYPE_AUDIO);
   g_list_free_full (containers, gst_object_unref);
 
-  GST_DEBUG ("Check clips in the layer");
-  tmp = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (tmp), 1);
-  g_list_free_full (tmp, gst_object_unref);
+  assert_layer (regrouped_clip, layer);
+
+  for (i = 0; i < G_N_ELEMENTS (audio_els); i++) {
+    el = audio_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == audio_track,
+        "%s not in audio track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip",
+        name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, audio_els[i].higher_priority);
+  }
+  for (i = 0; i < G_N_ELEMENTS (video_els); i++) {
+    el = video_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == video_track,
+        "%s not in video track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip",
+        name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, video_els[i].higher_priority);
+  }
+  assert_num_in_track (audio_track, 2);
+  assert_num_in_track (video_track, 3);
 
-  GST_DEBUG ("Check TrackElement in audio track");
-  tmp = ges_track_get_elements (audio_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_AUDIO);
-  fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) ==
-      regrouped_clip);
-  g_list_free_full (tmp, gst_object_unref);
-
-  GST_DEBUG ("Check TrackElement in video track");
-  tmp = ges_track_get_elements (video_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container "
-      "+ 1 for the timeline + 1 in tmp list", 4);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_VIDEO);
-  fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) ==
-      regrouped_clip);
-  g_list_free_full (tmp, gst_object_unref);
+  gst_object_unref (timeline);
+  gst_object_unref (asset);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_clip_can_group)
+{
+  GESTimeline *timeline;
+  GESLayer *layer1, *layer2;
+  GESTrack *track1, *track2, *track3, *select_track;
+  GESAsset *asset1, *asset2, *asset3;
+  GESContainer *container;
+  GESClip *clip1, *clip2, *clip3, *grouped;
+  GList *clips = NULL;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+
+  track1 = GES_TRACK (ges_audio_track_new ());
+  track2 = GES_TRACK (ges_video_track_new ());
+  track3 = GES_TRACK (ges_video_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track1));
+  fail_unless (ges_timeline_add_track (timeline, track2));
+
+  layer1 = ges_timeline_append_layer (timeline);
+  layer2 = ges_timeline_append_layer (timeline);
+
+  asset1 = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
+  asset2 = ges_asset_request (GES_TYPE_TEST_CLIP, "width=700", NULL);
+  asset3 =
+      ges_asset_request (GES_TYPE_EFFECT_CLIP, "audioecho || agingtv", NULL);
+
+  /* fail if different layer */
+  clip1 = ges_layer_add_asset (layer1, asset1, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip1);
+  assert_num_children (clip1, 1);
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 1);
+
+  clip2 = ges_layer_add_asset (layer2, asset1, 0, 0, 10, GES_TRACK_TYPE_AUDIO);
+  fail_unless (clip2);
+  assert_num_children (clip2, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 1);
+
+  clips = g_list_append (clips, clip1);
+  clips = g_list_append (clips, clip2);
+
+  _assert_regroup_fails (clips);
+
+  g_list_free (clips);
+  clips = NULL;
+
+  gst_object_ref (clip1);
+  gst_object_ref (clip2);
+  fail_unless (ges_layer_remove_clip (layer1, clip1));
+  fail_unless (ges_layer_remove_clip (layer2, clip2));
+  assert_num_children (clip1, 1);
+  assert_num_children (clip2, 1);
+  gst_object_unref (clip1);
+  gst_object_unref (clip2);
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 0);
+
+  /* fail if different asset */
+  clip1 = ges_layer_add_asset (layer1, asset1, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip1);
+  assert_num_children (clip1, 1);
+
+  clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO);
+  fail_unless (clip2);
+  assert_num_children (clip2, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 1);
+
+  clips = g_list_append (clips, clip1);
+  clips = g_list_append (clips, clip2);
+
+  _assert_regroup_fails (clips);
+
+  g_list_free (clips);
+  clips = NULL;
+
+  fail_unless (ges_layer_remove_clip (layer1, clip1));
+  fail_unless (ges_layer_remove_clip (layer1, clip2));
+
+  /* fail if sharing track */
+  clip1 = ges_layer_add_asset (layer1, asset3, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip1);
+  assert_num_children (clip1, 1);
+
+  clip2 = ges_layer_add_asset (layer1, asset3, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip2);
+  assert_num_children (clip2, 1);
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 2);
+
+  clips = g_list_append (clips, clip1);
+  clips = g_list_append (clips, clip2);
+
+  _assert_regroup_fails (clips);
+
+  g_list_free (clips);
+  clips = NULL;
+
+  fail_unless (ges_layer_remove_clip (layer1, clip1));
+  fail_unless (ges_layer_remove_clip (layer1, clip2));
+
+  clip1 = ges_layer_add_asset (layer1, asset1, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip1);
+  assert_num_children (clip1, 1);
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 1);
+
+  clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO);
+  fail_unless (clip2);
+  assert_num_children (clip2, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 1);
+
+  clips = g_list_append (clips, clip1);
+  clips = g_list_append (clips, clip2);
+
+  _assert_regroup_fails (clips);
+
+  g_list_free (clips);
+  clips = NULL;
+
+  fail_unless (ges_layer_remove_clip (layer1, clip1));
+  fail_unless (ges_layer_remove_clip (layer1, clip2));
+
+  /* can group if same asset but different tracks */
+  clip1 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip1);
+  _assert_add (clip1, ges_effect_new ("agingtv"));
+  assert_num_children (clip1, 2);
+
+  clip2 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_AUDIO);
+  fail_unless (clip2);
+  assert_num_children (clip2, 1);
+
+  fail_unless (ges_timeline_add_track (timeline, track3));
+  assert_num_children (clip1, 2);
+  assert_num_children (clip2, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 2);
+  assert_num_in_track (track3, 0);
+
+  select_track = track3;
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_track), &select_track);
+
+  clip3 = ges_layer_add_asset (layer1, asset2, 0, 0, 10, GES_TRACK_TYPE_VIDEO);
+  fail_unless (select_track == NULL);
+  assert_num_children (clip1, 2);
+  assert_num_children (clip2, 1);
+  assert_num_children (clip3, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 2);
+  assert_num_in_track (track3, 1);
+
+  clips = g_list_append (clips, clip1);
+  clips = g_list_append (clips, clip2);
+  clips = g_list_append (clips, clip3);
+
+  container = ges_container_group (clips);
+
+  fail_unless (GES_IS_CLIP (container));
+  grouped = GES_CLIP (container);
+  assert_num_children (grouped, 4);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 2);
+  assert_num_in_track (track3, 1);
+
+  fail_unless (ges_clip_get_supported_formats (grouped),
+      GES_TRACK_TYPE_VIDEO | GES_TRACK_TYPE_AUDIO);
+  fail_unless (ges_extractable_get_asset (GES_EXTRACTABLE (grouped))
+      == asset2);
+  CHECK_OBJECT_PROPS (grouped, 0, 0, 10);
+
+  g_list_free (clips);
+
+  clips = ges_layer_get_clips (layer1);
+  fail_unless (g_list_length (clips), 1);
+  fail_unless (GES_CLIP (clips->data) == grouped);
+  g_list_free_full (clips, gst_object_unref);
+
+  gst_object_unref (asset1);
+  gst_object_unref (asset2);
+  gst_object_unref (asset3);
 
   gst_object_unref (timeline);
 
@@ -523,12 +1252,332 @@ GST_START_TEST (test_clip_group_ungroup)
 
 GST_END_TEST;
 
+GST_START_TEST (test_adding_children_to_track)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTrack *track1, *track2;
+  GESClip *clip, *clip2;
+  GESAsset *asset;
+  GESTrackElement *source, *effect, *effect2, *added, *added2, *added3;
+  GstControlSource *ctrl_source;
+  guint selection_called = 0;
+  GError *error = NULL;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+  ges_timeline_set_auto_transition (timeline, TRUE);
+  track1 = GES_TRACK (ges_video_track_new ());
+  track2 = GES_TRACK (ges_video_track_new ());
+
+  /* only add two for now */
+  fail_unless (ges_timeline_add_track (timeline, track1));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
+
+  clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
+  fail_unless (clip);
+  assert_num_children (clip, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 0);
+  source = GES_CONTAINER_CHILDREN (clip)->data;
+  fail_unless (ges_track_element_get_track (source) == track1);
+
+  effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  _assert_add (clip, effect);
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
+  _assert_add (clip, effect2);
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  fail_unless (ges_track_element_get_track (effect) == track1);
+  fail_unless (ges_track_element_get_track (effect2) == track1);
+
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip),
+      "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1,
+      "freq", 449.0, "scratch-lines", 2, NULL);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_CUBIC, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 20.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 45.0));
+  fail_unless (ges_track_element_set_control_source (source, ctrl_source,
+          "posx", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_LINEAR, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 2, 0.1));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 0.7));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 0.3));
+  fail_unless (ges_track_element_set_control_source (source, ctrl_source,
+          "alpha", "direct"));
+  gst_object_unref (ctrl_source);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_NONE, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0));
+  fail_unless (ges_track_element_set_control_source (effect, ctrl_source,
+          "scratch-lines", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  /* can't add to a track that does not belong to the timeline */
+  fail_if (ges_clip_add_child_to_track (clip, source, track2, &error));
+  assert_num_children (clip, 3);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  /* programming/usage error gives no error code/message */
+  fail_if (error);
+
+  /* can't add the clip to a track that already contains our source */
+  fail_if (ges_clip_add_child_to_track (clip, source, track1, &error));
+  assert_num_children (clip, 3);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  fail_if (error);
+
+  /* can't remove a core element from its track whilst a non-core sits
+   * above it */
+  fail_if (ges_track_remove_element (track1, source));
+  assert_num_children (clip, 3);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can not add to the same track as it is currently in */
+  fail_if (ges_clip_add_child_to_track (clip, effect, track1, &error));
+  fail_unless (ges_track_element_get_track (effect) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  fail_if (error);
+
+  /* adding another video track, select-tracks-for-object will do nothing
+   * since no each track element is already part of a track */
+  fail_unless (ges_timeline_add_track (timeline, track2));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can not add effect to a track that does not contain a core child */
+  fail_if (ges_clip_add_child_to_track (clip, effect, track2, &error));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  fail_if (error);
+
+  /* can add core */
+
+  added = ges_clip_add_child_to_track (clip, source, track2, &error);
+  fail_unless (added);
+  assert_num_children (clip, 4);
+  fail_unless (added != source);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  fail_unless (ges_track_element_get_track (added) == track2);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 1);
+  fail_if (error);
+
+  assert_equal_children_properties (added, source);
+  assert_equal_bindings (added, source);
+
+  /* can now add non-core */
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+
+  added2 = ges_clip_add_child_to_track (clip, effect, track2, &error);
+  fail_unless (added2);
+  fail_if (error);
+  assert_num_children (clip, 5);
+  fail_unless (added2 != effect);
+  fail_unless (ges_track_element_get_track (effect) == track1);
+  fail_unless (ges_track_element_get_track (added2) == track2);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 2);
+
+  assert_equal_children_properties (added2, effect);
+  assert_equal_bindings (added2, effect);
+
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+
+  added3 = ges_clip_add_child_to_track (clip, effect2, track2, &error);
+  fail_unless (added3);
+  fail_if (error);
+  assert_num_children (clip, 6);
+  fail_unless (added3 != effect2);
+  fail_unless (ges_track_element_get_track (effect2) == track1);
+  fail_unless (ges_track_element_get_track (added3) == track2);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 3);
+
+  assert_equal_children_properties (added3, effect2);
+  assert_equal_bindings (added3, effect2);
+
+  /* priorities within new track match that in previous track! */
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+  assert_equals_int (3,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added3)));
+
+  /* removing core from the container, empties the non-core from their
+   * tracks */
+  gst_object_ref (added);
+  _assert_remove (clip, added);
+  assert_num_children (clip, 5);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  fail_if (ges_track_element_get_track (added));
+  fail_if (ges_track_element_get_track (added2));
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (added) == NULL);
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (added2) ==
+      GES_TIMELINE_ELEMENT (clip));
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  gst_object_unref (added);
+
+  _assert_remove (clip, added2);
+  _assert_remove (clip, added3);
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* remove from layer empties all children from the tracks */
+  gst_object_ref (clip);
+
+  fail_unless (ges_layer_remove_clip (layer, clip));
+  assert_num_children (clip, 3);
+  fail_if (ges_track_element_get_track (source));
+  fail_if (ges_track_element_get_track (effect));
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 0);
+
+  /* add different sources to the layer */
+  fail_unless (ges_layer_add_asset (layer, asset, 0, 0, 10,
+          GES_TRACK_TYPE_UNKNOWN));
+  fail_unless (ges_layer_add_asset (layer, asset, 20, 0, 10,
+          GES_TRACK_TYPE_UNKNOWN));
+  fail_unless (clip2 = ges_layer_add_asset (layer, asset, 25, 0, 10,
+          GES_TRACK_TYPE_UNKNOWN));
+  assert_num_children (clip2, 2);
+  /* 3 sources + 1 transition */
+  assert_num_in_track (track1, 4);
+  assert_num_in_track (track2, 4);
+
+  /* removing the track from the timeline empties it of track elements */
+  gst_object_ref (track2);
+  fail_unless (ges_timeline_remove_track (timeline, track2));
+  /* but children remain in the clips */
+  assert_num_children (clip2, 2);
+  assert_num_in_track (track1, 4);
+  assert_num_in_track (track2, 0);
+  gst_object_unref (track2);
+
+  /* add clip back in, but don't select any tracks */
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
+
+  /* can add the clip to the layer, despite a source existing between
+   * 0 and 10 because the clip will not fill any track */
+  /* NOTE: normally this would be useless because it would not trigger
+   * the creation of any core children. But clip currently still has
+   * its core children */
+  fail_unless (ges_layer_add_clip (layer, clip));
+  gst_object_unref (clip);
+
+  /* one call for each child */
+  assert_equals_int (selection_called, 3);
+
+  fail_if (ges_track_element_get_track (source));
+  fail_if (ges_track_element_get_track (effect));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 4);
+
+  /* can not add the source to the track because it would overlap another
+   * source */
+  fail_if (ges_clip_add_child_to_track (clip, source, track1, &error));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 4);
+  assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* can not add source at time 23 because it would result in three
+   * overlapping sources in the track */
+  assert_set_start (clip, 23);
+  fail_if (ges_clip_add_child_to_track (clip, source, track1, &error));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 4);
+  assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* can add at 5, with overlap */
+  assert_set_start (clip, 5);
+  added = ges_clip_add_child_to_track (clip, source, track1, &error);
+  /* added is the source since it was not already in a track */
+  fail_unless (added == source);
+  fail_if (error);
+  assert_num_children (clip, 3);
+  /* 4 sources + 2 transitions */
+  assert_num_in_track (track1, 6);
+
+  /* also add effect */
+  added = ges_clip_add_child_to_track (clip, effect, track1, &error);
+  /* added is the source since it was not already in a track */
+  fail_unless (added == effect);
+  fail_if (error);
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 7);
+
+  added = ges_clip_add_child_to_track (clip, effect2, track1, &error);
+  /* added is the source since it was not already in a track */
+  fail_unless (added == effect2);
+  fail_if (error);
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 8);
+
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+
+  gst_object_unref (timeline);
+  gst_object_unref (asset);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
 
 static void
 child_removed_cb (GESClip * clip, GESTimelineElement * effect,
     gboolean * called)
 {
-  ASSERT_OBJECT_REFCOUNT (effect, "Keeping alive ref + emission ref", 2);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 test ref + 1 keeping alive ref + "
+      "emission ref", 3);
   *called = TRUE;
 }
 
@@ -537,30 +1586,50 @@ GST_START_TEST (test_clip_refcount_remove_child)
   GESClip *clip;
   GESTrack *track;
   gboolean called;
-  GESTrackElement *effect;
+  GESTrackElement *effect, *source;
+  GESTimeline *timeline;
+  GESLayer *layer;
 
   ges_init ();
 
-  clip = GES_CLIP (ges_test_clip_new ());
+  timeline = ges_timeline_new ();
   track = GES_TRACK (ges_audio_track_new ());
-  effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_CLIP (ges_test_clip_new ());
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  assert_num_children (clip, 1);
+  assert_num_in_track (track, 1);
 
+  source = GES_CONTAINER_CHILDREN (clip)->data;
+  ASSERT_OBJECT_REFCOUNT (source, "1 for the container + 1 for the track"
+      " + 1 timeline", 3);
+
+  effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track, effect));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
-  ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track", 2);
+  assert_num_in_track (track, 2);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 for the track + 1 timeline", 2);
+
+  _assert_add (clip, effect);
+  assert_num_children (clip, 2);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track"
+      " + 1 timeline", 3);
 
   fail_unless (ges_track_remove_element (track, effect));
-  ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track", 1);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 for the container", 1);
 
   g_signal_connect (clip, "child-removed", G_CALLBACK (child_removed_cb),
       &called);
-  fail_unless (ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  gst_object_ref (effect);
+  _assert_remove (clip, effect);
   fail_unless (called == TRUE);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 test ref", 1);
+  gst_object_unref (effect);
 
-  check_destroyed (G_OBJECT (track), NULL, NULL);
-  check_destroyed (G_OBJECT (clip), NULL, NULL);
+  check_destroyed (G_OBJECT (timeline), G_OBJECT (track),
+      G_OBJECT (layer), G_OBJECT (clip), G_OBJECT (source), NULL);
 
   ges_deinit ();
 }
@@ -572,13 +1641,14 @@ GST_START_TEST (test_clip_find_track_element)
   GESClip *clip;
   GList *foundelements;
   GESTimeline *timeline;
+  GESLayer *layer;
   GESTrack *track, *track1, *track2;
+  guint selection_called = 0;
 
-  GESTrackElement *effect, *effect1, *effect2, *foundelem;
+  GESTrackElement *effect, *effect1, *effect2, *foundelem, *video_source;
 
   ges_init ();
 
-  clip = GES_CLIP (ges_test_clip_new ());
   track = GES_TRACK (ges_audio_track_new ());
   track1 = GES_TRACK (ges_audio_track_new ());
   track2 = GES_TRACK (ges_video_track_new ());
@@ -588,52 +1658,144 @@ GST_START_TEST (test_clip_find_track_element)
   fail_unless (ges_timeline_add_track (timeline, track1));
   fail_unless (ges_timeline_add_track (timeline, track2));
 
-  effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_CLIP (ges_test_clip_new ());
+
+  /* should have a source in every track */
+  fail_unless (ges_layer_add_clip (layer, clip));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 1);
+
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
+
+  effect = GES_TRACK_ELEMENT (ges_effect_new ("audio identity"));
   fail_unless (ges_track_add_element (track, effect));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_add (clip, effect);
 
-  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("audio identity"));
   fail_unless (ges_track_add_element (track1, effect1));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect1)));
+  _assert_add (clip, effect1);
 
   effect2 = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track2, effect2));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect2)));
+  _assert_add (clip, effect2);
+
+  fail_if (selection_called);
+  assert_num_children (clip, 6);
+  assert_num_in_track (track, 2);
+  assert_num_in_track (track1, 2);
+  assert_num_in_track (track2, 2);
 
-  foundelem = ges_clip_find_track_element (clip, track, G_TYPE_NONE);
+  foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_EFFECT);
   fail_unless (foundelem == effect);
   gst_object_unref (foundelem);
 
-  foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_SOURCE);
+  foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_EFFECT);
+  fail_unless (foundelem == effect1);
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_EFFECT);
+  fail_unless (foundelem == effect2);
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_TRANSITION);
+  fail_unless (foundelem == NULL);
+
+  foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_TRANSITION);
+  fail_unless (foundelem == NULL);
+
+  foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_TRANSITION);
+  fail_unless (foundelem == NULL);
+
+  foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_TRANSITION);
   fail_unless (foundelem == NULL);
 
+  foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
+  fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem));
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE);
+  fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem));
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE);
+  fail_unless (GES_IS_VIDEO_TEST_SOURCE (foundelem));
+  gst_object_unref (foundelem);
+
+  video_source = ges_clip_find_track_element (clip, NULL,
+      GES_TYPE_VIDEO_TEST_SOURCE);
+  fail_unless (foundelem == video_source);
+  gst_object_unref (video_source);
+
+
   foundelements = ges_clip_find_track_elements (clip, NULL,
       GES_TRACK_TYPE_AUDIO, G_TYPE_NONE);
-  fail_unless_equals_int (g_list_length (foundelements), 2);
+  fail_unless_equals_int (g_list_length (foundelements), 4);
   g_list_free_full (foundelements, gst_object_unref);
 
   foundelements = ges_clip_find_track_elements (clip, NULL,
       GES_TRACK_TYPE_VIDEO, G_TYPE_NONE);
-  fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless_equals_int (g_list_length (foundelements), 2);
   g_list_free_full (foundelements, gst_object_unref);
 
-  foundelements = ges_clip_find_track_elements (clip, track,
-      GES_TRACK_TYPE_VIDEO, G_TYPE_NONE);
-  fail_unless_equals_int (g_list_length (foundelements), 2);
-  fail_unless (g_list_find (foundelements, effect2) != NULL,
-      "In the video track");
-  fail_unless (g_list_find (foundelements, effect2) != NULL, "In 'track'");
+  foundelements = ges_clip_find_track_elements (clip, NULL,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE);
+  fail_unless_equals_int (g_list_length (foundelements), 3);
+  fail_unless (g_list_find (foundelements, video_source));
   g_list_free_full (foundelements, gst_object_unref);
 
-  gst_object_unref (timeline);
+  foundelements = ges_clip_find_track_elements (clip, NULL,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT);
+  fail_unless_equals_int (g_list_length (foundelements), 3);
+  fail_unless (g_list_find (foundelements, effect));
+  fail_unless (g_list_find (foundelements, effect1));
+  fail_unless (g_list_find (foundelements, effect2));
+  g_list_free_full (foundelements, gst_object_unref);
 
-  ges_deinit ();
-}
+  foundelements = ges_clip_find_track_elements (clip, NULL,
+      GES_TRACK_TYPE_VIDEO, GES_TYPE_SOURCE);
+  fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless (foundelements->data == video_source);
+  g_list_free_full (foundelements, gst_object_unref);
 
-GST_END_TEST;
+  foundelements = ges_clip_find_track_elements (clip, track2,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE);
+  fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless (foundelements->data == video_source);
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, track2,
+      GES_TRACK_TYPE_UNKNOWN, G_TYPE_NONE);
+  fail_unless_equals_int (g_list_length (foundelements), 2);
+  fail_unless (g_list_find (foundelements, effect2));
+  fail_unless (g_list_find (foundelements, video_source));
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, track1,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT);
+  fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless (foundelements->data == effect1);
+  g_list_free_full (foundelements, gst_object_unref);
+
+  /* NOTE: search in *either* track or track type
+   * TODO 2.0: this should be an AND condition, rather than OR */
+  foundelements = ges_clip_find_track_elements (clip, track,
+      GES_TRACK_TYPE_VIDEO, G_TYPE_NONE);
+  fail_unless_equals_int (g_list_length (foundelements), 4);
+  fail_unless (g_list_find (foundelements, effect));
+  fail_unless (g_list_find (foundelements, effect2));
+  fail_unless (g_list_find (foundelements, video_source));
+  g_list_free_full (foundelements, gst_object_unref);
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
 
 GST_START_TEST (test_effects_priorities)
 {
@@ -660,16 +1822,13 @@ GST_START_TEST (test_effects_priorities)
   ges_layer_add_clip (layer, clip);
 
   effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect)));
+  _assert_add (clip, effect);
 
   effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect1)));
+  _assert_add (clip, effect1);
 
   effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
-  fail_unless (ges_container_add (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (effect2)));
+  _assert_add (clip, effect2);
 
   fail_unless_equals_int (MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0,
       _PRIORITY (effect));
@@ -729,6 +1888,3634 @@ GST_START_TEST (test_effects_priorities)
 
 GST_END_TEST;
 
+static void
+_count_cb (GObject * obj, GParamSpec * pspec, gint * count)
+{
+  *count = *count + 1;
+}
+
+#define _assert_children_time_setter(clip, child, prop, setter, val1, val2) \
+{ \
+  gint clip_count = 0; \
+  gint child_count = 0; \
+  gchar *notify_name = g_strconcat ("notify::", prop, NULL); \
+  gchar *clip_name = GES_TIMELINE_ELEMENT_NAME (clip); \
+  gchar *child_name = NULL; \
+  g_signal_connect (clip, notify_name, G_CALLBACK (_count_cb), \
+      &clip_count); \
+  if (child) { \
+    child_name = GES_TIMELINE_ELEMENT_NAME (child); \
+    g_signal_connect (child, notify_name, G_CALLBACK (_count_cb), \
+        &child_count); \
+  } \
+  \
+  fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val1), \
+      "Failed to set the %s property for clip %s", prop, clip_name); \
+  assert_clip_children_time_val (clip, prop, val1); \
+  \
+  fail_unless (clip_count == 1, "The callback for the %s property was " \
+      "called %i times for clip %s, rather than once", \
+      prop, clip_count, clip_name); \
+  if (child) { \
+    fail_unless (child_count == 1, "The callback for the %s property " \
+        "was called %i times for the child %s of clip %s, rather than " \
+        "once", prop, child_count, child_name, clip_name); \
+  } \
+  \
+  clip_count = 0; \
+  if (child) { \
+    child_count = 0; \
+    fail_unless (setter (GES_TIMELINE_ELEMENT (child), val2), \
+        "Failed to set the %s property for the child %s of clip %s", \
+        prop, child_name, clip_name); \
+    fail_unless (child_count == 1, "The callback for the %s property " \
+        "was called %i more times for the child %s of clip %s, rather " \
+        "than once more", prop, child_count, child_name, clip_name); \
+  } else { \
+    fail_unless (setter (GES_TIMELINE_ELEMENT (clip), val2), \
+      "Failed to set the %s property for clip %s", prop, clip_name); \
+  } \
+  assert_clip_children_time_val (clip, prop, val2); \
+  \
+  fail_unless (clip_count == 1, "The callback for the %s property " \
+      "was called %i more times for clip %s, rather than once more", \
+      prop, clip_count, clip_name); \
+  assert_equals_int (g_signal_handlers_disconnect_by_func (clip, \
+          G_CALLBACK (_count_cb), &clip_count), 1); \
+  if (child) { \
+    assert_equals_int (g_signal_handlers_disconnect_by_func (child, \
+            G_CALLBACK (_count_cb), &child_count), 1); \
+  } \
+  \
+  g_free (notify_name); \
+}
+
+static void
+_test_children_time_setting_on_clip (GESClip * clip, GESTrackElement * child)
+{
+  _assert_children_time_setter (clip, child, "in-point",
+      ges_timeline_element_set_inpoint, 11, 101);
+  _assert_children_time_setter (clip, child, "in-point",
+      ges_timeline_element_set_inpoint, 51, 1);
+  _assert_children_time_setter (clip, child, "start",
+      ges_timeline_element_set_start, 12, 102);
+  _assert_children_time_setter (clip, child, "start",
+      ges_timeline_element_set_start, 52, 2);
+  _assert_children_time_setter (clip, child, "duration",
+      ges_timeline_element_set_duration, 13, 103);
+  _assert_children_time_setter (clip, child, "duration",
+      ges_timeline_element_set_duration, 53, 3);
+}
+
+GST_START_TEST (test_children_time_setters)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clips[] = { NULL, NULL };
+  gint i;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  fail_unless (timeline);
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clips[0] =
+      GES_CLIP (ges_transition_clip_new
+      (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE));
+  clips[1] = GES_CLIP (ges_test_clip_new ());
+
+  for (i = 0; i < G_N_ELEMENTS (clips); i++) {
+    GESClip *clip = clips[i];
+    GESContainer *group = GES_CONTAINER (ges_group_new ());
+    GList *children;
+    GESTrackElement *child;
+    /* no children */
+    _test_children_time_setting_on_clip (clip, NULL);
+    /* child in timeline */
+    fail_unless (ges_layer_add_clip (layer, clip));
+    children = GES_CONTAINER_CHILDREN (clip);
+    fail_unless (children);
+    child = GES_TRACK_ELEMENT (children->data);
+    /* make sure the child can have its in-point set */
+    ges_track_element_set_has_internal_source (child, TRUE);
+    _test_children_time_setting_on_clip (clip, child);
+    /* clip in a group */
+    _assert_add (group, clip);
+    _test_children_time_setting_on_clip (clip, child);
+    /* group is removed from the timeline and destroyed when empty */
+    _assert_remove (group, clip);
+    /* child not in timeline */
+    gst_object_ref (clip);
+    fail_unless (ges_layer_remove_clip (layer, clip));
+    children = GES_CONTAINER_CHILDREN (clip);
+    fail_unless (children);
+    child = GES_TRACK_ELEMENT (children->data);
+    ges_track_element_set_has_internal_source (child, TRUE);
+    _test_children_time_setting_on_clip (clip, child);
+    gst_object_unref (clip);
+  }
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_not_enough_internal_content_for_core)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESAsset *asset;
+  GError *error = NULL;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_timeline_append_layer (timeline);
+
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, "max-duration=30", NULL);
+  fail_unless (asset);
+
+  fail_if (ges_layer_add_asset_full (layer, asset, 0, 31, 10,
+          GES_TRACK_TYPE_UNKNOWN, &error));
+  assert_GESError (error, GES_ERROR_NOT_ENOUGH_INTERNAL_CONTENT);
+
+  gst_object_unref (timeline);
+  gst_object_unref (asset);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_can_add_effect)
+{
+  struct CanAddEffectData
+  {
+    GESClip *clip;
+    gboolean can_add_effect;
+  } clips[6];
+  guint i;
+  gchar *uri;
+
+  ges_init ();
+
+  uri = ges_test_get_audio_video_uri ();
+
+  clips[0] = (struct CanAddEffectData) {
+  GES_CLIP (ges_test_clip_new ()), TRUE};
+  clips[1] = (struct CanAddEffectData) {
+  GES_CLIP (ges_uri_clip_new (uri)), TRUE};
+  clips[2] = (struct CanAddEffectData) {
+  GES_CLIP (ges_title_clip_new ()), TRUE};
+  clips[3] = (struct CanAddEffectData) {
+  GES_CLIP (ges_effect_clip_new ("agingtv", "audioecho")), TRUE};
+  clips[4] = (struct CanAddEffectData) {
+  GES_CLIP (ges_transition_clip_new
+        (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)), FALSE};
+  clips[5] = (struct CanAddEffectData) {
+  GES_CLIP (ges_text_overlay_clip_new ()), FALSE};
+
+  g_free (uri);
+
+  for (i = 0; i < G_N_ELEMENTS (clips); i++) {
+    GESClip *clip = clips[i].clip;
+    GESTimelineElement *effect =
+        GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"));
+    gst_object_ref_sink (effect);
+    gst_object_ref_sink (clip);
+    fail_unless (clip);
+    if (clips[i].can_add_effect)
+      fail_unless (ges_container_add (GES_CONTAINER (clip), effect),
+          "Could not add an effect to clip %s",
+          GES_TIMELINE_ELEMENT_NAME (clip));
+    else
+      fail_if (ges_container_add (GES_CONTAINER (clip), effect),
+          "Could add an effect to clip %s, but we expect this to fail",
+          GES_TIMELINE_ELEMENT_NAME (clip));
+    gst_object_unref (effect);
+    gst_object_unref (clip);
+  }
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _assert_active(el, active) \
+  fail_unless (ges_track_element_is_active (el) == active)
+
+#define _assert_set_active(el, active) \
+  fail_unless (ges_track_element_set_active (el, active))
+
+GST_START_TEST (test_children_active)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrack *track0, *track1, *select_track;
+  GESTrackElement *effect0, *effect1, *effect2, *effect3;
+  GESTrackElement *source0, *source1;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+
+  track0 = GES_TRACK (ges_video_track_new ());
+  track1 = GES_TRACK (ges_video_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track0));
+  fail_unless (ges_timeline_add_track (timeline, track1));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_CLIP (ges_test_clip_new ());
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  assert_num_children (clip, 2);
+
+  source0 =
+      ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE);
+  source1 =
+      ges_clip_find_track_element (clip, track1, GES_TYPE_VIDEO_TEST_SOURCE);
+
+  fail_unless (source0);
+  fail_unless (source1);
+
+  gst_object_unref (source0);
+  gst_object_unref (source1);
+
+  _assert_active (source0, TRUE);
+  _assert_active (source1, TRUE);
+
+  _assert_set_active (source0, FALSE);
+
+  _assert_active (source0, FALSE);
+  _assert_active (source1, TRUE);
+
+  select_track = track0;
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_track), &select_track);
+
+  /* add an active effect should become inactive to match the core */
+  effect0 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
+  _assert_active (effect0, TRUE);
+
+  _assert_add (clip, effect0);
+  fail_if (select_track);
+
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (source1, TRUE);
+
+  /* adding inactive to track with inactive core does nothing */
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
+  _assert_active (effect1, TRUE);
+  _assert_set_active (effect1, FALSE);
+  _assert_active (effect1, FALSE);
+
+  select_track = track0;
+  _assert_add (clip, effect1);
+  fail_if (select_track);
+
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+
+  /* adding active to track with active core does nothing */
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  _assert_active (effect2, TRUE);
+
+  select_track = track1;
+  _assert_add (clip, effect2);
+  fail_if (select_track);
+
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, TRUE);
+
+  /* adding inactive to track with active core does nothing */
+  effect3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha"));
+  _assert_active (effect3, TRUE);
+  _assert_set_active (effect3, FALSE);
+  _assert_active (effect3, FALSE);
+
+  select_track = track1;
+  _assert_add (clip, effect3);
+  fail_if (select_track);
+
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, TRUE);
+  _assert_active (effect3, FALSE);
+
+  /* activate a core does not change non-core */
+  _assert_set_active (source0, TRUE);
+
+  _assert_active (source0, TRUE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, TRUE);
+  _assert_active (effect3, FALSE);
+
+  /* but de-activating a core will de-activate the non-core */
+  _assert_set_active (source1, FALSE);
+
+  _assert_active (source0, TRUE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, FALSE);
+  _assert_active (effect2, FALSE);
+  _assert_active (effect3, FALSE);
+
+  /* activate a non-core will activate the core */
+  _assert_set_active (effect3, TRUE);
+
+  _assert_active (source0, TRUE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, FALSE);
+  _assert_active (effect3, TRUE);
+
+  /* if core is already active, nothing else happens */
+  _assert_set_active (effect0, TRUE);
+
+  _assert_active (source0, TRUE);
+  _assert_active (effect0, TRUE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, FALSE);
+  _assert_active (effect3, TRUE);
+
+  _assert_set_active (effect1, TRUE);
+
+  _assert_active (source0, TRUE);
+  _assert_active (effect0, TRUE);
+  _assert_active (effect1, TRUE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, FALSE);
+  _assert_active (effect3, TRUE);
+
+  _assert_set_active (effect2, TRUE);
+
+  _assert_active (source0, TRUE);
+  _assert_active (effect0, TRUE);
+  _assert_active (effect1, TRUE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, TRUE);
+  _assert_active (effect3, TRUE);
+
+  /* de-activate a core will de-active all the non-core */
+  _assert_set_active (source0, FALSE);
+
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, TRUE);
+  _assert_active (effect3, TRUE);
+
+  /* de-activate a non-core does nothing else */
+  _assert_set_active (effect3, FALSE);
+
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (source1, TRUE);
+  _assert_active (effect2, TRUE);
+  _assert_active (effect3, FALSE);
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_children_inpoint)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTimelineElement *clip, *child0, *child1, *effect;
+  GList *children;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  fail_unless (timeline);
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
+
+  assert_set_start (clip, 5);
+  assert_set_duration (clip, 20);
+  assert_set_inpoint (clip, 30);
+
+  CHECK_OBJECT_PROPS (clip, 5, 30, 20);
+
+  fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
+
+  /* clip now has children */
+  children = GES_CONTAINER_CHILDREN (clip);
+  fail_unless (children);
+  child0 = children->data;
+  fail_unless (children->next);
+  child1 = children->next->data;
+  fail_unless (children->next->next == NULL);
+
+  fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT
+          (child0)));
+  fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT
+          (child1)));
+
+  CHECK_OBJECT_PROPS (clip, 5, 30, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 30, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 30, 20);
+
+  /* add a non-core element */
+  effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"));
+  fail_if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT (effect)));
+  /* allow us to choose our own in-point */
+  ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (effect), TRUE);
+  assert_set_start (effect, 104);
+  assert_set_duration (effect, 53);
+  assert_set_inpoint (effect, 67);
+
+  /* adding the effect will change its start and duration, but not its
+   * in-point */
+  _assert_add (clip, effect);
+
+  CHECK_OBJECT_PROPS (clip, 5, 30, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 30, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 30, 20);
+  CHECK_OBJECT_PROPS (effect, 5, 67, 20);
+
+  /* register child0 as having no internal source, which means its
+   * in-point will be set to 0 */
+  ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0), FALSE);
+
+  CHECK_OBJECT_PROPS (clip, 5, 30, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 0, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 30, 20);
+  CHECK_OBJECT_PROPS (effect, 5, 67, 20);
+
+  /* should not be able to set the in-point to non-zero */
+  assert_fail_set_inpoint (child0, 40);
+
+  CHECK_OBJECT_PROPS (clip, 5, 30, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 0, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 30, 20);
+  CHECK_OBJECT_PROPS (effect, 5, 67, 20);
+
+  /* when we set the in-point on a core-child with an internal source we
+   * also set the clip and siblings with the same features */
+  assert_set_inpoint (child1, 50);
+
+  CHECK_OBJECT_PROPS (clip, 5, 50, 20);
+  /* child with no internal source not changed */
+  CHECK_OBJECT_PROPS (child0, 5, 0, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 50, 20);
+  /* non-core no changed */
+  CHECK_OBJECT_PROPS (effect, 5, 67, 20);
+
+  /* setting back to having internal source will put in sync with the
+   * in-point of the clip */
+  ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0), TRUE);
+
+  CHECK_OBJECT_PROPS (clip, 5, 50, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 50, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 50, 20);
+  CHECK_OBJECT_PROPS (effect, 5, 67, 20);
+
+  assert_set_inpoint (child0, 40);
+
+  CHECK_OBJECT_PROPS (clip, 5, 40, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 40, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 40, 20);
+  CHECK_OBJECT_PROPS (effect, 5, 67, 20);
+
+  /* setting in-point on effect shouldn't change any other siblings */
+  assert_set_inpoint (effect, 77);
+
+  CHECK_OBJECT_PROPS (clip, 5, 40, 20);
+  CHECK_OBJECT_PROPS (child0, 5, 40, 20);
+  CHECK_OBJECT_PROPS (child1, 5, 40, 20);
+  CHECK_OBJECT_PROPS (effect, 5, 77, 20);
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_children_max_duration)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  gchar *uri;
+  GESTimelineElement *child0, *child1, *effect;
+  guint i;
+  GstClockTime max_duration, new_max;
+  GList *children;
+  struct
+  {
+    GESTimelineElement *clip;
+    GstClockTime max_duration;
+  } clips[] = {
+    {
+    NULL, GST_SECOND}, {
+    NULL, GST_CLOCK_TIME_NONE}
+  };
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  fail_unless (timeline);
+
+  layer = ges_timeline_append_layer (timeline);
+
+  uri = ges_test_get_audio_video_uri ();
+  clips[0].clip = GES_TIMELINE_ELEMENT (ges_uri_clip_new (uri));
+  fail_unless (clips[0].clip);
+  g_free (uri);
+
+  clips[1].clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
+
+  for (i = 0; i < G_N_ELEMENTS (clips); i++) {
+    GESTimelineElement *clip = clips[i].clip;
+
+    max_duration = clips[i].max_duration;
+    fail_unless_equals_uint64 (_MAX_DURATION (clip), max_duration);
+    assert_set_start (clip, 5);
+    assert_set_duration (clip, 20);
+    assert_set_inpoint (clip, 30);
+
+    /* can set the max duration the clip to anything whilst it has
+     * no core child */
+    assert_set_max_duration (clip, 150);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 150);
+
+    /* add a non-core element */
+    effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"));
+    fail_if (ges_track_element_has_internal_source (GES_TRACK_ELEMENT
+            (effect)));
+    /* allow us to choose our own max-duration */
+    ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (effect),
+        TRUE);
+    assert_set_start (effect, 104);
+    assert_set_duration (effect, 53);
+    assert_set_max_duration (effect, 400);
+
+    /* adding the effect will change its start and duration, but not its
+     * max-duration (or in-point) */
+    _assert_add (clip, effect);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 150);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* only non-core, so can still set the max-duration */
+    assert_set_max_duration (clip, 200);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* removing should not change the max-duration we set on the clip */
+    gst_object_ref (effect);
+    _assert_remove (clip, effect);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    _assert_add (clip, effect);
+    gst_object_unref (effect);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 200);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* now add to a layer to create the core children */
+    fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
+
+    children = GES_CONTAINER_CHILDREN (clip);
+    fail_unless (children);
+    fail_unless (GES_TIMELINE_ELEMENT (children->data) == effect);
+    fail_unless (children->next);
+    child0 = children->next->data;
+    fail_unless (children->next->next);
+    child1 = children->next->next->data;
+    fail_unless (children->next->next->next == NULL);
+
+    fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT
+            (child0)));
+    fail_unless (ges_track_element_has_internal_source (GES_TRACK_ELEMENT
+            (child1)));
+
+    if (GES_IS_URI_CLIP (clip))
+      new_max = max_duration;
+    else
+      /* need a valid clock time that is not too large */
+      new_max = 500;
+
+    /* added children do not change the clip's max-duration, but will
+     * instead set it to the minimum value of its children */
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, max_duration);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, max_duration);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, max_duration);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* when setting max_duration of core children, clip will take the
+     * minimum value */
+    assert_set_max_duration (child0, new_max - 1);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 1);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max - 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, max_duration);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_set_max_duration (child1, new_max - 2);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max - 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_set_max_duration (child0, new_max + 1);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* can not set in-point above max_duration, nor max_duration below
+     * in-point */
+
+    assert_fail_set_max_duration (child0, 29);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_fail_set_max_duration (child1, 29);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_fail_set_max_duration (clip, 29);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* can't set the inpoint to (new_max), even though it is lower than
+     * our own max-duration (new_max + 1) because it is too high for our
+     * sibling child1 */
+    assert_fail_set_inpoint (child0, new_max);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_fail_set_inpoint (child1, new_max);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_fail_set_inpoint (clip, new_max);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* setting below new_max is ok */
+    assert_set_inpoint (child0, 15);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 15, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 15, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 15, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_set_inpoint (child1, 25);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 25, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 25, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 25, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_set_inpoint (clip, 30);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* non-core has no effect */
+    assert_set_max_duration (effect, new_max + 500);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, new_max + 500);
+
+    /* can set the in-point of non-core to be higher than the max_duration
+     * of the clip */
+    assert_set_inpoint (effect, new_max + 2);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500);
+
+    /* but not higher than our own */
+    assert_fail_set_inpoint (effect, new_max + 501);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500);
+
+    assert_fail_set_max_duration (effect, new_max + 1);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, new_max + 2, 20, new_max + 500);
+
+    assert_set_inpoint (effect, 0);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, new_max + 500);
+
+    assert_set_max_duration (effect, 400);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, new_max + 1);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, new_max - 2);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* setting on the clip will set all the core children to the same
+     * value */
+    assert_set_max_duration (clip, 180);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* register child0 as having no internal source, which means its
+     * in-point will be set to 0 and max-duration set to
+     * GST_CLOCK_TIME_NONE */
+    ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0),
+        FALSE);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* should not be able to set the max-duration to a valid time */
+    assert_fail_set_max_duration (child0, 40);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 180);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* same with child1 */
+    /* clock time of the clip should now be GST_CLOCK_TIME_NONE */
+    ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child1),
+        FALSE);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* should not be able to set the max of the clip to anything else
+     * when it has no core children with an internal source */
+    assert_fail_set_max_duration (clip, 150);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* setting back to having an internal source will not immediately
+     * change the max-duration (unlike in-point) */
+    ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child0),
+        TRUE);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* can now set the max-duration, which will effect the clip */
+    assert_set_max_duration (child0, 140);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 0, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (child1),
+        TRUE);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    assert_set_max_duration (child1, 130);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 130);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* removing a child may change the max_duration of the clip */
+    gst_object_ref (child0);
+    gst_object_ref (child1);
+    gst_object_ref (effect);
+
+    /* removing non-core does nothing */
+    _assert_remove (clip, effect);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 130);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* new minimum max-duration for the clip when we remove child1 */
+    _assert_remove (clip, child1);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    /* with no core-children, the max-duration of the clip is set to
+     * GST_CLOCK_TIME_NONE */
+    _assert_remove (clip, child0);
+
+    CHECK_OBJECT_PROPS_MAX (clip, 5, 30, 20, GST_CLOCK_TIME_NONE);
+    CHECK_OBJECT_PROPS_MAX (child0, 5, 30, 20, 140);
+    CHECK_OBJECT_PROPS_MAX (child1, 5, 30, 20, 130);
+    CHECK_OBJECT_PROPS_MAX (effect, 5, 0, 20, 400);
+
+    fail_unless (ges_layer_remove_clip (layer, GES_CLIP (clip)));
+
+    gst_object_unref (child0);
+    gst_object_unref (child1);
+    gst_object_unref (effect);
+  }
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _assert_duration_limit(clip, expect) \
+  assert_equals_uint64 (ges_clip_get_duration_limit (GES_CLIP (clip)), expect)
+
+GST_START_TEST (test_duration_limit)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *video_source, *audio_source;
+  GESTrackElement *effect1, *effect2, *effect3;
+  GESTrack *track1, *track2;
+  gint limit_notify_count = 0;
+  gint duration_notify_count = 0;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+  track1 = GES_TRACK (ges_video_track_new ());
+  track2 = GES_TRACK (ges_audio_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track1));
+  fail_unless (ges_timeline_add_track (timeline, track2));
+  /* add track3 later */
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_CLIP (ges_test_clip_new ());
+  g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb),
+      &limit_notify_count);
+  g_signal_connect (clip, "notify::duration", G_CALLBACK (_count_cb),
+      &duration_notify_count);
+
+  /* no limit to begin with */
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+
+  /* add effects */
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (effect1, TRUE);
+
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  ges_track_element_set_has_internal_source (effect2, TRUE);
+
+  effect3 = GES_TRACK_ELEMENT (ges_effect_new ("audioecho"));
+  ges_track_element_set_has_internal_source (effect3, TRUE);
+
+  _assert_add (clip, effect1);
+  _assert_add (clip, effect2);
+  _assert_add (clip, effect3);
+  assert_num_children (clip, 3);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 0);
+  assert_equals_int (duration_notify_count, 0);
+
+  /* no change in duration limit whilst children are not in any track */
+  assert_set_max_duration (effect1, 20);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 0);
+  assert_equals_int (duration_notify_count, 0);
+
+  assert_set_inpoint (effect1, 5);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 0);
+  assert_equals_int (duration_notify_count, 0);
+
+  /* set a duration that will be above the duration-limit */
+  assert_set_duration (clip, 20);
+  assert_equals_int (duration_notify_count, 1);
+
+  /* add to layer to create sources */
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  /* duration-limit changes once because of effect1 */
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 1);
+  assert_equals_int (duration_notify_count, 2);
+  /* duration has automatically been set to the duration-limit */
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  assert_num_children (clip, 5);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 2);
+
+  video_source = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE);
+  fail_unless (video_source);
+  gst_object_unref (video_source);
+
+  audio_source = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE);
+  fail_unless (audio_source);
+  gst_object_unref (audio_source);
+
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (video_source) == track1);
+  fail_unless (ges_track_element_get_track_type (video_source) ==
+      GES_TRACK_TYPE_VIDEO);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (audio_source) == track2);
+  fail_unless (ges_track_element_get_track_type (audio_source) ==
+      GES_TRACK_TYPE_AUDIO);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  fail_unless (ges_track_element_get_track (effect1) == track1);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (effect2) == track1);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  fail_unless (ges_track_element_get_track (effect3) == track2);
+
+  /* Make effect1 inactive, which will remove the duration-limit */
+  fail_unless (ges_track_element_set_active (effect1, FALSE));
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 2);
+  /* duration is unchanged */
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* changing the in-point does not change the duration limit whilst
+   * there is no max-duration */
+  assert_set_inpoint (clip, 10);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  assert_equals_int (limit_notify_count, 2);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  assert_set_max_duration (video_source, 40);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 3);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 10, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 10, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 10, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* set in-point using child */
+  assert_set_inpoint (audio_source, 15);
+  _assert_duration_limit (clip, 25);
+  assert_equals_int (limit_notify_count, 4);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 40);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* set max-duration of core children */
+  assert_set_max_duration (clip, 60);
+  _assert_duration_limit (clip, 45);
+  assert_equals_int (limit_notify_count, 5);
+  assert_equals_int (duration_notify_count, 2);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 15, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, GST_CLOCK_TIME_NONE);
+
+  /* can set duration up to the limit */
+  assert_set_duration (clip, 45);
+  _assert_duration_limit (clip, 45);
+  assert_equals_int (limit_notify_count, 5);
+  assert_equals_int (duration_notify_count, 3);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 45, 60);
+  /* effect1 has a duration that exceeds max-duration - in-point
+   * ok since it is currently inactive */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 45, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 45, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 45, GST_CLOCK_TIME_NONE);
+
+  /* limit the effects */
+  assert_set_max_duration (effect2, 70);
+  _assert_duration_limit (clip, 45);
+  assert_equals_int (limit_notify_count, 5);
+  assert_equals_int (duration_notify_count, 3);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 45, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 45, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 0, 45, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 45, GST_CLOCK_TIME_NONE);
+
+  assert_set_inpoint (effect2, 40);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 6);
+  assert_equals_int (duration_notify_count, 4);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 30, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 30, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 30, GST_CLOCK_TIME_NONE);
+
+  /* no change */
+  assert_set_max_duration (effect3, 35);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 6);
+  assert_equals_int (duration_notify_count, 4);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 30, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 30, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 30, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 30, 35);
+
+  /* make effect1 active again */
+  fail_unless (ges_track_element_set_active (effect1, TRUE));
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 7);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+
+  /* removing effect2 from track does not change limit */
+  fail_unless (ges_track_remove_element (track1, effect2));
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 7);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  /* removing effect1 does */
+  fail_unless (ges_track_remove_element (track1, effect1));
+  _assert_duration_limit (clip, 35);
+  assert_equals_int (limit_notify_count, 8);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  /* add back */
+  fail_unless (ges_track_add_element (track1, effect2));
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 9);
+  assert_equals_int (duration_notify_count, 5);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+
+  assert_set_duration (clip, 20);
+  _assert_duration_limit (clip, 30);
+  assert_equals_int (limit_notify_count, 9);
+  assert_equals_int (duration_notify_count, 6);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 20, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 20, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 20, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 20, 60);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 20, 70);
+  /* no track */
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 20, 20);
+
+  fail_unless (ges_track_add_element (track1, effect1));
+  _assert_duration_limit (clip, 15);
+  assert_equals_int (limit_notify_count, 10);
+  assert_equals_int (duration_notify_count, 7);
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  gst_object_ref (clip);
+  fail_unless (ges_layer_remove_clip (layer, clip));
+
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 0);
+  assert_num_children (clip, 5);
+
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  /* may have several changes in duration limit as the children are
+   * emptied from their tracks */
+  fail_unless (limit_notify_count > 10);
+  assert_equals_int (duration_notify_count, 7);
+  /* none in any track */
+  CHECK_OBJECT_PROPS_MAX (clip, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (audio_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect3, 0, 0, 15, 35);
+  CHECK_OBJECT_PROPS_MAX (video_source, 0, 15, 15, 60);
+  CHECK_OBJECT_PROPS_MAX (effect1, 0, 5, 15, 20);
+  CHECK_OBJECT_PROPS_MAX (effect2, 0, 40, 15, 70);
+
+  gst_object_unref (timeline);
+  gst_object_unref (clip);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_can_set_duration_limit)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *source0, *source1;
+  GESTrackElement *effect0, *effect1, *effect2;
+  GESTrack *track0, *track1;
+  gint limit_notify_count = 0;
+  GError *error = NULL;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+  track0 = GES_TRACK (ges_video_track_new ());
+  track1 = GES_TRACK (ges_audio_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track0));
+  fail_unless (ges_timeline_add_track (timeline, track1));
+  /* add track3 later */
+
+  layer = ges_timeline_append_layer (timeline);
+
+  /* place a dummy clip at the start of the layer */
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_start (clip, 0);
+  assert_set_duration (clip, 20);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  /* the clip we will be editing overlaps the first clip at its start */
+  clip = GES_CLIP (ges_test_clip_new ());
+
+  g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb),
+      &limit_notify_count);
+
+  assert_set_start (clip, 10);
+  assert_set_duration (clip, 20);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  source0 =
+      ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE);
+  source1 =
+      ges_clip_find_track_element (clip, track1, GES_TYPE_AUDIO_TEST_SOURCE);
+
+  fail_unless (source0);
+  fail_unless (source1);
+
+  gst_object_unref (source0);
+  gst_object_unref (source1);
+
+  assert_equals_int (limit_notify_count, 0);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+
+  assert_set_inpoint (clip, 16);
+
+  assert_equals_int (limit_notify_count, 0);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, GST_CLOCK_TIME_NONE);
+
+  assert_set_max_duration (clip, 36);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+
+  /* add effects */
+  effect0 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("alpha"));
+
+  ges_track_element_set_has_internal_source (effect0, TRUE);
+  fail_unless (ges_track_element_has_internal_source (effect1) == FALSE);
+  ges_track_element_set_has_internal_source (effect2, TRUE);
+
+  assert_set_max_duration (effect0, 10);
+  /* already set the track */
+  fail_unless (ges_track_add_element (track0, effect0));
+  /* cannot add because it would cause the duration-limit to go to 10,
+   * causing a full overlap with the clip at the beginning of the layer */
+
+  gst_object_ref (effect0);
+  fail_if (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (effect0)));
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+
+  /* removing from the track and adding will work, but track selection
+   * will fail */
+  fail_unless (ges_track_remove_element (track0, effect0));
+
+  _assert_add (clip, effect0);
+  fail_if (ges_track_element_get_track (effect0));
+  gst_object_unref (effect0);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 10);
+
+  fail_if (ges_clip_add_child_to_track (clip, effect0, track0, &error));
+  assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* set max-duration to 11 and we are fine to select a track */
+  assert_set_max_duration (effect0, 11);
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 20);
+
+  fail_unless (ges_clip_add_child_to_track (clip, effect0, track0,
+          &error) == effect0);
+  fail_if (error);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 11);
+
+  /* cannot set duration above the limit */
+  assert_fail_set_duration (clip, 12);
+  assert_fail_set_duration (source0, 12);
+  assert_fail_set_duration (effect0, 12);
+
+  /* allow the max_duration to increase again */
+  assert_set_max_duration (effect0, 25);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+
+  assert_set_duration (clip, 20);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+
+  /* add another effect */
+  _assert_add (clip, effect1);
+  fail_unless (ges_track_element_get_track (effect1) == track0);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+
+  /* make source0 inactive and reduce its max-duration
+   * note that this causes effect0 and effect1 to also become in-active */
+  _assert_set_active (source0, FALSE);
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+
+  /* can set a low max duration whilst the source is inactive */
+  assert_set_max_duration (source0, 26);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+
+  /* add the last effect */
+  assert_set_inpoint (effect2, 7);
+  assert_set_max_duration (effect2, 17);
+  _assert_active (effect2, TRUE);
+
+  /* safe to add because the source is inactive */
+  assert_equals_int (limit_notify_count, 3);
+  _assert_add (clip, effect2);
+  assert_equals_int (limit_notify_count, 3);
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (effect2, FALSE);
+
+  fail_unless (ges_track_element_get_track (effect2) == track0);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 16, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 16, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 16, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 17);
+
+  /* want to make the source and its effects active again */
+  assert_set_inpoint (source0, 6);
+  assert_set_max_duration (effect2, 33);
+
+  assert_equals_int (limit_notify_count, 4);
+  _assert_duration_limit (clip, 30);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  _assert_set_active (source0, TRUE);
+  _assert_set_active (effect0, TRUE);
+  _assert_set_active (effect1, TRUE);
+  _assert_set_active (effect2, TRUE);
+
+  assert_equals_int (limit_notify_count, 5);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* cannot set in-point of clip to 16, nor of either source */
+  assert_fail_set_inpoint (clip, 16);
+  assert_fail_set_inpoint (source0, 16);
+  assert_fail_set_inpoint (source1, 16);
+
+  assert_equals_int (limit_notify_count, 5);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* can set just below */
+  assert_set_inpoint (source1, 15);
+
+  assert_equals_int (limit_notify_count, 6);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 15, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 15, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 15, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 33);
+
+  assert_set_inpoint (clip, 6);
+  assert_set_duration (clip, 20);
+
+  assert_equals_int (limit_notify_count, 7);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* cannot set in-point of non-core in a way that would cause limit to
+   * drop */
+  assert_fail_set_inpoint (effect2, 23);
+  assert_fail_set_inpoint (effect0, 15);
+
+  assert_equals_int (limit_notify_count, 7);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* can set just below */
+  assert_set_inpoint (effect2, 22);
+
+  assert_equals_int (limit_notify_count, 8);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 22, 11, 33);
+
+  assert_set_inpoint (effect2, 7);
+  assert_set_duration (clip, 20);
+
+  assert_equals_int (limit_notify_count, 9);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* same but with max-duration */
+
+  /* core */
+  assert_fail_set_max_duration (clip, 16);
+  assert_fail_set_max_duration (source0, 16);
+  assert_fail_set_max_duration (source1, 16);
+
+  assert_equals_int (limit_notify_count, 9);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 36);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  assert_set_max_duration (source1, 17);
+
+  assert_equals_int (limit_notify_count, 10);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 17);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 17);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 33);
+
+  assert_set_max_duration (source1, 30);
+  assert_set_duration (clip, 20);
+
+  assert_equals_int (limit_notify_count, 11);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 30);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  assert_set_max_duration (clip, 17);
+
+  assert_equals_int (limit_notify_count, 12);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 17);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 17);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 17);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 33);
+
+  assert_set_max_duration (clip, 26);
+  assert_set_duration (clip, 20);
+
+  assert_equals_int (limit_notify_count, 13);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* non-core */
+  assert_fail_set_max_duration (effect0, 10);
+  assert_fail_set_max_duration (effect2, 17);
+
+  assert_equals_int (limit_notify_count, 13);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  assert_set_max_duration (effect2, 18);
+
+  assert_equals_int (limit_notify_count, 14);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 11, 18);
+
+  assert_set_max_duration (effect2, 33);
+  assert_set_duration (clip, 20);
+
+  assert_equals_int (limit_notify_count, 15);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 33);
+
+  /* test setting active */
+  _assert_active (effect2, TRUE);
+  _assert_set_active (effect2, FALSE);
+  assert_set_max_duration (effect2, 17);
+
+  assert_equals_int (limit_notify_count, 15);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 17);
+
+  fail_if (ges_track_element_set_active (effect2, TRUE));
+
+  assert_equals_int (limit_notify_count, 15);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 7, 20, 17);
+
+  assert_set_inpoint (effect2, 6);
+  _assert_set_active (effect2, TRUE);
+
+  assert_equals_int (limit_notify_count, 16);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 11, 17);
+
+  /* make source0 in-active */
+  _assert_active (source0, TRUE);
+  _assert_set_active (source0, FALSE);
+  _assert_active (source0, FALSE);
+  _assert_active (effect0, FALSE);
+  _assert_active (effect1, FALSE);
+  _assert_active (effect2, FALSE);
+
+  assert_set_duration (source0, 20);
+
+  assert_equals_int (limit_notify_count, 17);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17);
+
+  /* lower duration-limit for source for when it becomes active */
+  assert_set_max_duration (source0, 16);
+
+  assert_equals_int (limit_notify_count, 17);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17);
+
+  /* cannot make the source active */
+  fail_if (ges_track_element_set_active (source0, TRUE));
+
+  assert_equals_int (limit_notify_count, 17);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17);
+
+  /* nor can we make the effects active because this would activate the
+   * source */
+  fail_if (ges_track_element_set_active (effect0, TRUE));
+  fail_if (ges_track_element_set_active (effect1, TRUE));
+  fail_if (ges_track_element_set_active (effect2, TRUE));
+
+  assert_equals_int (limit_notify_count, 17);
+  _assert_duration_limit (clip, 20);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 6, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 6, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 6, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17);
+
+  /* allow it to just succeed */
+  assert_set_inpoint (source0, 5);
+
+  assert_equals_int (limit_notify_count, 18);
+  _assert_duration_limit (clip, 21);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 5, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 5, 20, 16);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 5, 20, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 20, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 20, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 20, 17);
+
+  _assert_set_active (effect1, TRUE);
+
+  assert_equals_int (limit_notify_count, 19);
+  _assert_duration_limit (clip, 11);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 5, 11, 16);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 5, 11, 16);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 5, 11, 26);
+  CHECK_OBJECT_PROPS_MAX (effect0, 10, 0, 11, 25);
+  CHECK_OBJECT_PROPS_MAX (effect1, 10, 0, 11, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (effect2, 10, 6, 11, 17);
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _assert_set_rate(element, prop_name, rate, val) \
+{ \
+  GError *error = NULL; \
+  if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \
+    g_value_set_double (&val, rate); \
+  else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \
+    g_value_set_float (&val, rate); \
+  \
+  fail_unless (ges_timeline_element_set_child_property_full ( \
+        GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \
+  fail_if (error); \
+  g_value_reset (&val); \
+}
+
+#define _assert_fail_set_rate(element, prop_name, rate, val, code) \
+{ \
+  GError * error = NULL; \
+  if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \
+    g_value_set_double (&val, rate); \
+  else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \
+    g_value_set_float (&val, rate); \
+  \
+  fail_if (ges_timeline_element_set_child_property_full ( \
+        GES_TIMELINE_ELEMENT (element), prop_name, &val, &error)); \
+  assert_GESError (error, code); \
+  g_value_reset (&val); \
+}
+
+#define _assert_rate_equal(element, prop_name, rate, val) \
+{ \
+  gdouble found = -1.0; \
+  fail_unless (ges_timeline_element_get_child_property ( \
+        GES_TIMELINE_ELEMENT (element), prop_name, &val)); \
+  \
+  if (G_VALUE_TYPE (&val) == G_TYPE_DOUBLE) \
+    found = g_value_get_double (&val); \
+  else if (G_VALUE_TYPE (&val) == G_TYPE_FLOAT) \
+    found = g_value_get_float (&val); \
+  \
+  fail_unless (found == rate, "found %s: %g != expected: %g", found, \
+      prop_name, rate); \
+  g_value_reset (&val); \
+}
+
+GST_START_TEST (test_rate_effects_duration_limit)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *source0, *source1;
+  GESTrackElement *overlay0, *overlay1, *videorate, *pitch;
+  GESTrack *track0, *track1;
+  gint limit_notify_count = 0;
+  GValue fval = G_VALUE_INIT;
+  GValue dval = G_VALUE_INIT;
+
+  ges_init ();
+
+  g_value_init (&fval, G_TYPE_FLOAT);
+  g_value_init (&dval, G_TYPE_DOUBLE);
+
+  timeline = ges_timeline_new ();
+  track0 = GES_TRACK (ges_video_track_new ());
+  track1 = GES_TRACK (ges_audio_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track0));
+  fail_unless (ges_timeline_add_track (timeline, track1));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  /* place a dummy clip at the start of the layer */
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_start (clip, 0);
+  assert_set_duration (clip, 26);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  /* the clip we will be editing overlaps first clip by 16 at its start */
+  clip = GES_CLIP (ges_test_clip_new ());
+
+  g_signal_connect (clip, "notify::duration-limit", G_CALLBACK (_count_cb),
+      &limit_notify_count);
+
+  assert_set_start (clip, 10);
+  assert_set_duration (clip, 64);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  source0 =
+      ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE);
+  source1 =
+      ges_clip_find_track_element (clip, track1, GES_TYPE_AUDIO_TEST_SOURCE);
+
+  fail_unless (source0);
+  fail_unless (source1);
+
+  gst_object_unref (source0);
+  gst_object_unref (source1);
+
+  assert_equals_int (limit_notify_count, 0);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+
+  assert_set_inpoint (clip, 13);
+
+  assert_equals_int (limit_notify_count, 0);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE);
+
+  assert_set_max_duration (clip, 77);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+
+  /* add effects */
+  overlay0 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay0, TRUE);
+
+  videorate = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+  fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (videorate)));
+
+  overlay1 = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay1, TRUE);
+
+  pitch = GES_TRACK_ELEMENT (ges_effect_new ("pitch"));
+  fail_unless (ges_base_effect_is_time_effect (GES_BASE_EFFECT (pitch)));
+
+  /* add overlay1 at highest priority */
+  _assert_add (clip, overlay1);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+
+  _assert_set_rate (videorate, "rate", 4.0, dval);
+  _assert_rate_equal (videorate, "rate", 4.0, dval);
+  fail_unless (ges_track_add_element (track0, videorate));
+
+  /* cannot add videorate as it would cause the duration-limit to drop
+   * to 16, causing a full overlap */
+  /* track keeps alive */
+  fail_if (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (videorate)));
+
+  /* setting to 1.0 makes it work again */
+  _assert_set_rate (videorate, "rate", 1.0, dval);
+  _assert_add (clip, videorate);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+
+  /* add second overlay at lower priority */
+  _assert_add (clip, overlay0);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+
+  /* also add a pitch element in another track */
+  _assert_add (clip, pitch);
+  _assert_set_rate (pitch, "rate", 1.0, fval);
+  _assert_set_rate (pitch, "tempo", 1.0, fval);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  fail_unless (ges_track_element_get_track (overlay0) == track0);
+  fail_unless (ges_track_element_get_track (videorate) == track0);
+  fail_unless (ges_track_element_get_track (overlay1) == track0);
+  fail_unless (ges_track_element_get_track (pitch) == track1);
+
+  /* flow in track0:
+   * source0 -> overlay0 -> videorate -> overlay1 -> timeline output
+   *
+   * flow in track1:
+   * source1 -> pitch -> timeline output
+   */
+
+  /* cannot set the rates to 4.0 since this would cause a full overlap */
+  _assert_fail_set_rate (videorate, "rate", 4.0, dval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _assert_fail_set_rate (pitch, "rate", 4.0, fval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _assert_fail_set_rate (pitch, "tempo", 4.0, fval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* limit overlay0 */
+  assert_set_max_duration (overlay0, 91);
+
+  assert_equals_int (limit_notify_count, 1);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 0, 64, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  assert_set_inpoint (overlay0, 59);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 1.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* can set pitch rate to 2.0, but not videorate rate because videorate
+   * shares a track with overlay0 */
+
+  _assert_set_rate (pitch, "rate", 2.0, fval);
+  assert_equals_int (limit_notify_count, 2);
+  _assert_fail_set_rate (videorate, "rate", 2.0, dval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  assert_equals_int (limit_notify_count, 2);
+  /* can't set tempo to 2.0 since overall effect would bring duration
+   * limit too low */
+  _assert_fail_set_rate (pitch, "tempo", 2.0, fval,
+      GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 2.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* cannot set in-point of clip because pitch would cause limit to go
+   * to 16 */
+  assert_fail_set_inpoint (clip, 45);
+  /* same for max-duration of source1 */
+  assert_fail_set_max_duration (source1, 45);
+
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 1.0, dval);
+  _assert_rate_equal (pitch, "rate", 2.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* can set rate to 0.5 */
+  _assert_set_rate (videorate, "rate", 0.5, dval);
+
+  /* no change yet, since pitch rate is still 2.0 */
+  assert_equals_int (limit_notify_count, 2);
+  _assert_duration_limit (clip, 32);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 2.0, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  _assert_set_rate (pitch, "rate", 0.5, fval);
+
+  assert_equals_int (limit_notify_count, 3);
+  /* duration-limit is 64 because overlay0 only has 32 nanoseconds of
+   * content, stretched to 64 by videorate */
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* setting the max-duration of the sources does not change the limit
+   * since the limit on overlay0 is fine.
+   * Note that pitch handles the unlimited duration (GST_CLOCK_TIME_NONE)
+   * without any problems */
+  assert_set_max_duration (clip, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  assert_set_max_duration (clip, 77);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* limit overlay1. It should not be changes by the videorate element
+   * since it acts at a lower priority
+   * first make it last longer, so no change in duration-limit */
+
+  assert_set_max_duration (overlay1, 81);
+
+  assert_equals_int (limit_notify_count, 3);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 32, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 32, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 0, 32, 81);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 32, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* now make it shorter */
+  assert_set_inpoint (overlay1, 51);
+
+  assert_equals_int (limit_notify_count, 4);
+  _assert_duration_limit (clip, 30);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, 91);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* remove the overlay0 limit */
+  assert_set_max_duration (overlay0, GST_CLOCK_TIME_NONE);
+
+  /* no change because of overlay1 */
+  assert_equals_int (limit_notify_count, 4);
+  _assert_duration_limit (clip, 30);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 30, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 30, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 30, 81);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 30, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  assert_set_max_duration (overlay1, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 5);
+  _assert_duration_limit (clip, 128);
+  /* can set up to the limit */
+  assert_set_duration (clip, 128);
+
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 128, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 128, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 128, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 128, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 128, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 128, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 128, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* tempo contributes the same factor as rate */
+
+  _assert_set_rate (pitch, "tempo", 2.0, fval);
+
+  assert_equals_int (limit_notify_count, 6);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 2.0, fval);
+
+  _assert_set_rate (videorate, "rate", 0.1, dval);
+  assert_equals_int (limit_notify_count, 6);
+  _assert_set_rate (pitch, "tempo", 0.5, fval);
+
+  assert_equals_int (limit_notify_count, 7);
+  _assert_duration_limit (clip, 256);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.1, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 0.5, fval);
+
+  _assert_set_rate (pitch, "tempo", 1.0, fval);
+  assert_equals_int (limit_notify_count, 8);
+  _assert_set_rate (videorate, "rate", 0.5, dval);
+
+  assert_equals_int (limit_notify_count, 8);
+  _assert_duration_limit (clip, 128);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* make videorate in-active */
+  fail_unless (ges_track_element_set_active (videorate, FALSE));
+
+  assert_equals_int (limit_notify_count, 9);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  fail_unless (ges_track_element_set_active (videorate, TRUE));
+
+  assert_equals_int (limit_notify_count, 10);
+  _assert_duration_limit (clip, 128);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (pitch, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+  _assert_rate_equal (pitch, "rate", 0.5, fval);
+  _assert_rate_equal (pitch, "tempo", 1.0, fval);
+
+  /* removing pitch, same effect as making inactive */
+  _assert_remove (clip, pitch);
+
+  assert_equals_int (limit_notify_count, 11);
+  _assert_duration_limit (clip, 64);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+
+  /* no max-duration will give unlimited limit */
+  assert_set_max_duration (source1, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 12);
+  _assert_duration_limit (clip, 128);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, 77);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+
+  assert_set_max_duration (source0, GST_CLOCK_TIME_NONE);
+
+  assert_equals_int (limit_notify_count, 13);
+  _assert_duration_limit (clip, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (clip, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source0, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (source1, 10, 13, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay0, 10, 59, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (videorate, 10, 0, 64, GST_CLOCK_TIME_NONE);
+  CHECK_OBJECT_PROPS_MAX (overlay1, 10, 51, 64, GST_CLOCK_TIME_NONE);
+  _assert_rate_equal (videorate, "rate", 0.5, dval);
+
+  gst_object_unref (timeline);
+
+  g_value_unset (&fval);
+  g_value_unset (&dval);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_children_properties_contain)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip;
+  GList *tmp;
+  GParamSpec **clips_child_props, **childrens_child_props = NULL;
+  guint num_clips_props, num_childrens_props = 0;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_duration (clip, 50);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  clips_child_props =
+      ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
+      (clip), &num_clips_props);
+  fail_unless (clips_child_props);
+  fail_unless (num_clips_props);
+
+  fail_unless (GES_CONTAINER_CHILDREN (clip));
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    childrens_child_props =
+        append_children_properties (childrens_child_props, tmp->data,
+        &num_childrens_props);
+
+  assert_property_list_match (clips_child_props, num_clips_props,
+      childrens_child_props, num_childrens_props);
+
+  free_children_properties (clips_child_props, num_clips_props);
+  free_children_properties (childrens_child_props, num_childrens_props);
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+static gboolean
+_has_child_property (GESTimelineElement * element, GParamSpec * property)
+{
+  gboolean has_prop = FALSE;
+  guint num_props, i;
+  GParamSpec **props =
+      ges_timeline_element_list_children_properties (element, &num_props);
+  for (i = 0; i < num_props; i++) {
+    if (props[i] == property)
+      has_prop = TRUE;
+    g_param_spec_unref (props[i]);
+  }
+  g_free (props);
+  return has_prop;
+}
+
+typedef struct
+{
+  GstElement *child;
+  GParamSpec *property;
+  guint num_calls;
+} PropChangedData;
+
+#define _INIT_PROP_CHANGED_DATA(data) \
+  data.child = NULL; \
+  data.property = NULL; \
+  data.num_calls = 0;
+
+static void
+_prop_changed_cb (GESTimelineElement * element, GstElement * child,
+    GParamSpec * property, PropChangedData * data)
+{
+  data->num_calls++;
+  data->property = property;
+  data->child = child;
+}
+
+#define _assert_prop_changed_data(element, data, num_cmp, chld_cmp, prop_cmp) \
+  fail_unless (num_cmp == data.num_calls, \
+      "%s: num calls to callback (%u) not the expected %u", element->name, \
+      data.num_calls, num_cmp); \
+  fail_unless (prop_cmp == data.property, \
+      "%s: property %s is not the expected property %s", element->name, \
+      data.property->name, prop_cmp ? ((GParamSpec *)prop_cmp)->name : NULL); \
+  fail_unless (chld_cmp == data.child, \
+      "%s: child %s is not the expected child %s", element->name, \
+      GST_ELEMENT_NAME (data.child), \
+      chld_cmp ? GST_ELEMENT_NAME (chld_cmp) : NULL);
+
+#define _assert_int_val_child_prop(element, val, int_cmp, prop, prop_name) \
+  g_value_init (&val, G_TYPE_INT); \
+  ges_timeline_element_get_child_property_by_pspec (element, prop, &val); \
+  assert_equals_int (g_value_get_int (&val), int_cmp); \
+  g_value_unset (&val); \
+  g_value_init (&val, G_TYPE_INT); \
+  fail_unless (ges_timeline_element_get_child_property ( \
+        element, prop_name, &val)); \
+  assert_equals_int (g_value_get_int (&val), int_cmp); \
+  g_value_unset (&val); \
+
+GST_START_TEST (test_children_properties_change)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTimelineElement *clip, *child;
+  PropChangedData clip_add_data, clip_remove_data, clip_notify_data,
+      child_add_data, child_remove_data, child_notify_data;
+  GstElement *sub_child;
+  GParamSpec *prop1, *prop2, *prop3;
+  GValue val = G_VALUE_INIT;
+  gint num_buffs;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
+  assert_set_duration (clip, 50);
+
+  fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
+  fail_unless (GES_CONTAINER_CHILDREN (clip));
+  child = GES_CONTAINER_CHILDREN (clip)->data;
+
+  /* fake sub-child */
+  sub_child = gst_element_factory_make ("fakesink", "sub-child");
+  fail_unless (sub_child);
+  gst_object_ref_sink (sub_child);
+  prop1 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child),
+      "num-buffers");
+  fail_unless (prop1);
+  prop2 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child), "dump");
+  fail_unless (prop2);
+  prop3 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child),
+      "silent");
+  fail_unless (prop2);
+
+  _INIT_PROP_CHANGED_DATA (clip_add_data);
+  _INIT_PROP_CHANGED_DATA (clip_remove_data);
+  _INIT_PROP_CHANGED_DATA (clip_notify_data);
+  _INIT_PROP_CHANGED_DATA (child_add_data);
+  _INIT_PROP_CHANGED_DATA (child_remove_data);
+  _INIT_PROP_CHANGED_DATA (child_notify_data);
+  g_signal_connect (clip, "child-property-added",
+      G_CALLBACK (_prop_changed_cb), &clip_add_data);
+  g_signal_connect (clip, "child-property-removed",
+      G_CALLBACK (_prop_changed_cb), &clip_remove_data);
+  g_signal_connect (clip, "deep-notify",
+      G_CALLBACK (_prop_changed_cb), &clip_notify_data);
+  g_signal_connect (child, "child-property-added",
+      G_CALLBACK (_prop_changed_cb), &child_add_data);
+  g_signal_connect (child, "child-property-removed",
+      G_CALLBACK (_prop_changed_cb), &child_remove_data);
+  g_signal_connect (child, "deep-notify",
+      G_CALLBACK (_prop_changed_cb), &child_notify_data);
+
+  /* adding to child should also add it to the parent clip */
+  fail_unless (ges_timeline_element_add_child_property (child, prop1,
+          G_OBJECT (sub_child)));
+
+  fail_unless (_has_child_property (child, prop1));
+  fail_unless (_has_child_property (clip, prop1));
+
+  _assert_prop_changed_data (clip, clip_add_data, 1, sub_child, prop1);
+  _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_add_data, 1, sub_child, prop1);
+  _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
+
+  fail_unless (ges_timeline_element_add_child_property (child, prop2,
+          G_OBJECT (sub_child)));
+
+  fail_unless (_has_child_property (child, prop2));
+  fail_unless (_has_child_property (clip, prop2));
+
+  _assert_prop_changed_data (clip, clip_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
+
+  /* adding to parent does not add to the child */
+
+  fail_unless (ges_timeline_element_add_child_property (clip, prop3,
+          G_OBJECT (sub_child)));
+
+  fail_if (_has_child_property (child, prop3));
+  fail_unless (_has_child_property (clip, prop3));
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
+
+  /* both should be notified of a change in the value */
+
+  g_object_set (G_OBJECT (sub_child), "num-buffers", 100, NULL);
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (clip, clip_notify_data, 1, sub_child, prop1);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_notify_data, 1, sub_child, prop1);
+
+  _assert_int_val_child_prop (clip, val, 100, prop1,
+      "GstFakeSink::num-buffers");
+  _assert_int_val_child_prop (child, val, 100, prop1,
+      "GstFakeSink::num-buffers");
+
+  g_value_init (&val, G_TYPE_INT);
+  g_value_set_int (&val, 79);
+  ges_timeline_element_set_child_property_by_pspec (clip, prop1, &val);
+  g_value_unset (&val);
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (clip, clip_notify_data, 2, sub_child, prop1);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_notify_data, 2, sub_child, prop1);
+
+  _assert_int_val_child_prop (clip, val, 79, prop1, "GstFakeSink::num-buffers");
+  _assert_int_val_child_prop (child, val, 79, prop1,
+      "GstFakeSink::num-buffers");
+  g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL);
+  assert_equals_int (num_buffs, 79);
+
+  g_value_init (&val, G_TYPE_INT);
+  g_value_set_int (&val, 97);
+  fail_unless (ges_timeline_element_set_child_property (child,
+          "GstFakeSink::num-buffers", &val));
+  g_value_unset (&val);
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
+  _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
+
+  _assert_int_val_child_prop (clip, val, 97, prop1, "GstFakeSink::num-buffers");
+  _assert_int_val_child_prop (child, val, 97, prop1,
+      "GstFakeSink::num-buffers");
+  g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL);
+  assert_equals_int (num_buffs, 97);
+
+  /* remove a property from the child, removes from the parent */
+
+  fail_unless (ges_timeline_element_remove_child_property (child, prop2));
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 1, sub_child, prop2);
+  _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2);
+  _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
+
+  fail_if (_has_child_property (child, prop2));
+  fail_if (_has_child_property (clip, prop2));
+
+  /* removing from parent doesn't remove from child */
+
+  fail_unless (ges_timeline_element_remove_child_property (clip, prop1));
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1);
+  _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2);
+  _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
+
+  fail_unless (_has_child_property (child, prop1));
+  fail_if (_has_child_property (clip, prop1));
+
+  /* but still safe to remove it from the child later */
+
+  fail_unless (ges_timeline_element_remove_child_property (child, prop1));
+
+  _assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
+  _assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1);
+  _assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
+  _assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
+  _assert_prop_changed_data (child, child_remove_data, 2, sub_child, prop1);
+  _assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
+
+  fail_if (_has_child_property (child, prop1));
+  fail_if (_has_child_property (clip, prop1));
+
+  gst_object_unref (sub_child);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+static GESTimelineElement *
+_el_with_child_prop (GESTimelineElement * clip, GObject * prop_child,
+    GParamSpec * prop)
+{
+  GList *tmp;
+  GESTimelineElement *child = NULL;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GObject *found_child;
+    GParamSpec *found_prop;
+    if (ges_timeline_element_lookup_child (tmp->data, prop->name,
+            &found_child, &found_prop)) {
+      if (found_child == prop_child && found_prop == prop) {
+        g_param_spec_unref (found_prop);
+        g_object_unref (found_child);
+        child = tmp->data;
+        break;
+      }
+      g_param_spec_unref (found_prop);
+      g_object_unref (found_child);
+    }
+  }
+  return child;
+}
+
+static GstTimedValue *
+_new_timed_value (GstClockTime time, gdouble val)
+{
+  GstTimedValue *tmval = g_new0 (GstTimedValue, 1);
+  tmval->value = val;
+  tmval->timestamp = time;
+  return tmval;
+}
+
+#define _assert_binding(element, prop_name, child, timed_vals, mode) \
+{ \
+  GstInterpolationMode found_mode; \
+  GSList *tmp1; \
+  GList *tmp2; \
+  guint i; \
+  GList *found_timed_vals; \
+  GObject *found_object = NULL; \
+  GstControlSource *source = NULL; \
+  GstControlBinding *binding = ges_track_element_get_control_binding ( \
+      GES_TRACK_ELEMENT (element), prop_name); \
+  fail_unless (binding, "No control binding found for %s on %s", \
+      prop_name, GES_TIMELINE_ELEMENT_NAME (element)); \
+  g_object_get (G_OBJECT (binding), "control-source", &source, \
+      "object", &found_object, NULL); \
+  \
+  if (child) \
+    fail_unless (found_object == child); \
+  g_object_unref (found_object); \
+  \
+  fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)); \
+  found_timed_vals = gst_timed_value_control_source_get_all ( \
+      GST_TIMED_VALUE_CONTROL_SOURCE (source)); \
+  \
+  for (i = 0, tmp1 = timed_vals, tmp2 = found_timed_vals; tmp1 && tmp2; \
+      tmp1 = tmp1->next, tmp2 = tmp2->next, i++) { \
+    GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \
+    gdouble diff = (val1->value > val2->value) ? \
+        val1->value - val2->value : val2->value - val1->value; \
+    fail_unless (val1->timestamp == val2->timestamp && diff < 0.0001, \
+        "The %ith timed value (%lu: %g) does not match the found timed " \
+        "value (%lu: %g)", i, val1->timestamp, val1->value, \
+        val2->timestamp, val2->value); \
+  } \
+  fail_unless (tmp1 == NULL, "Found too few timed values"); \
+  fail_unless (tmp2 == NULL, "Found too many timed values"); \
+  \
+  g_list_free (found_timed_vals); \
+  g_object_get (G_OBJECT (source), "mode", &found_mode, NULL); \
+  fail_unless (found_mode == mode); \
+  g_object_unref (source); \
+}
+
+GST_START_TEST (test_copy_paste_children_properties)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTimelineElement *clip, *copy, *pasted, *track_el, *pasted_el;
+  GObject *sub_child, *pasted_sub_child;
+  GParamSpec **orig_props;
+  guint num_orig_props;
+  GParamSpec *prop, *found_prop;
+  GValue val = G_VALUE_INIT;
+  GstControlSource *source;
+  GSList *timed_vals;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_TIMELINE_ELEMENT (ges_source_clip_new_time_overlay ());
+  assert_set_duration (clip, 50);
+
+  fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
+
+  /* get children properties */
+  orig_props =
+      ges_timeline_element_list_children_properties (clip, &num_orig_props);
+  fail_unless (num_orig_props);
+
+  /* font-desc is originally "", but on setting switches to Normal, so we
+   * set it explicitly */
+  ges_timeline_element_set_child_properties (clip, "font-desc", "Normal",
+      "posx", 30, "posy", 50, "alpha", 0.1, "freq", 449.0, NULL);
+
+  /* focus on one property */
+  fail_unless (ges_timeline_element_lookup_child (clip, "posx",
+          &sub_child, &prop));
+  _assert_int_val_child_prop (clip, val, 30, prop, "posx");
+
+  /* find the track element where the child property comes from */
+  fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop));
+  _assert_int_val_child_prop (track_el, val, 30, prop, "posx");
+  ges_track_element_set_auto_clamp_control_sources (GES_TRACK_ELEMENT
+      (track_el), FALSE);
+
+  /* set a control binding */
+  timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5));
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (40, 50));
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (20, 10));
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (0, 20));
+
+  source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (source), "mode", GST_INTERPOLATION_MODE_CUBIC, NULL);
+  fail_unless (gst_timed_value_control_source_set_from_list
+      (GST_TIMED_VALUE_CONTROL_SOURCE (source), timed_vals));
+
+  fail_unless (ges_track_element_set_control_source (GES_TRACK_ELEMENT
+          (track_el), source, "posx", "direct-absolute"));
+
+  g_object_unref (source);
+
+  /* check the control binding */
+  _assert_binding (track_el, "posx", sub_child, timed_vals,
+      GST_INTERPOLATION_MODE_CUBIC);
+
+  /* copy and paste */
+  fail_unless (copy = ges_timeline_element_copy (clip, TRUE));
+  fail_unless (pasted = ges_timeline_element_paste (copy, 30));
+
+  gst_object_unref (copy);
+  gst_object_unref (pasted);
+
+  /* test that the new clip has the same child properties */
+  assert_equal_children_properties (clip, pasted);
+
+  /* get the details for the copied 'prop' property */
+  fail_unless (ges_timeline_element_lookup_child (pasted,
+          "posx", &pasted_sub_child, &found_prop));
+  fail_unless (found_prop == prop);
+  g_param_spec_unref (found_prop);
+  fail_unless (G_OBJECT_TYPE (pasted_sub_child) == G_OBJECT_TYPE (sub_child));
+
+  _assert_int_val_child_prop (pasted, val, 30, prop, "posx");
+
+  /* get the associated child */
+  fail_unless (pasted_el =
+      _el_with_child_prop (pasted, pasted_sub_child, prop));
+  _assert_int_val_child_prop (pasted_el, val, 30, prop, "posx");
+
+  assert_equal_children_properties (track_el, pasted_el);
+
+  /* check the control binding on the pasted element */
+  _assert_binding (pasted_el, "posx", pasted_sub_child, timed_vals,
+      GST_INTERPOLATION_MODE_CUBIC);
+
+  assert_equal_bindings (pasted_el, track_el);
+
+  /* free */
+  g_slist_free_full (timed_vals, g_free);
+
+  free_children_properties (orig_props, num_orig_props);
+
+  g_param_spec_unref (prop);
+  g_object_unref (pasted_sub_child);
+  g_object_unref (sub_child);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _THREE_TIMED_VALS(timed_vals, tm1, val1, tm2, val2, tm3, val3) \
+  if (timed_vals) \
+    g_slist_free_full (timed_vals, g_free); \
+  timed_vals = g_slist_prepend (NULL, _new_timed_value (tm3, val3)); \
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm2, val2)); \
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1));
+
+#define _TWO_TIMED_VALS(timed_vals, tm1, val1, tm2, val2) \
+  if (timed_vals) \
+    g_slist_free_full (timed_vals, g_free); \
+  timed_vals = g_slist_prepend (NULL, _new_timed_value (tm2, val2)); \
+  timed_vals = g_slist_prepend (timed_vals, _new_timed_value (tm1, val1));
+
+#define _assert_control_source(obj, prop, vals) \
+  _assert_binding (obj, prop, NULL, vals, GST_INTERPOLATION_MODE_LINEAR);
+
+GST_START_TEST (test_children_property_bindings_with_rate_effects)
+{
+  GESTimeline *timeline;
+  GESTrack *track;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *video_source, *rate0, *rate1, *overlay;
+  GstControlSource *ctrl_source;
+  GSList *video_source_vals = NULL, *overlay_vals = NULL;
+  GValue value = G_VALUE_INIT;
+  GstControlBinding *binding;
+
+  ges_init ();
+
+  g_value_init (&value, G_TYPE_DOUBLE);
+
+  timeline = ges_timeline_new ();
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = GES_CLIP (ges_test_clip_new ());
+  assert_set_duration (clip, 4);
+  assert_set_start (clip, 20);
+  assert_set_inpoint (clip, 3);
+
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  video_source = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
+  fail_unless (video_source);
+  gst_object_unref (video_source);
+
+  rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=0.5"));
+  rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate rate=4.0"));
+  overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay, TRUE);
+  assert_set_inpoint (overlay, 9);
+
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), -1,
+          NULL));
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (overlay), 0,
+          NULL));
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate1), 0,
+          NULL));
+
+  fail_unless (ges_track_element_get_auto_clamp_control_sources (video_source));
+  fail_unless (ges_track_element_get_auto_clamp_control_sources (overlay));
+
+  /* source's alpha property */
+  _THREE_TIMED_VALS (video_source_vals, 1, 0.7, 7, 1.0, 15, 0.2);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_LINEAR, NULL);
+  fail_unless (gst_timed_value_control_source_set_from_list
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), video_source_vals));
+
+  fail_unless (ges_track_element_set_control_source (video_source, ctrl_source,
+          "alpha", "direct"));
+  gst_object_unref (ctrl_source);
+
+  /* values have been clamped between its in-point:3 and its
+   * out-point:11 (4ns in timeline is 8ns in source) */
+  _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* overlay's xpos property */
+  _THREE_TIMED_VALS (overlay_vals, 9, 12, 17, 16, 25, 8);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_LINEAR, NULL);
+  fail_unless (gst_timed_value_control_source_set_from_list
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), overlay_vals));
+
+  fail_unless (ges_track_element_set_control_source (overlay, ctrl_source,
+          "xpos", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  /* unchanged since values are at the edges already
+   * in-point:9 out-point:25 (4ns in timeline is 16ns in source) */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* setting the in-point changes the in-point and out-point */
+  /* increase in-point */
+  assert_set_inpoint (video_source, 5);
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* decrease in-point */
+  assert_set_inpoint (overlay, 7);
+
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* when trimming start, out-point should stay the same */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 19, NULL));
+
+  /* in-point of video_source now 3 */
+  _THREE_TIMED_VALS (video_source_vals, 3, 0.8, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* in-point of video_source now 3 */
+  _THREE_TIMED_VALS (overlay_vals, 3, 9, 17, 16, 23, 10);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* trim forwards */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 20, NULL));
+
+  /* in-point of video_source now 5 again */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* in-point of overlay now 7 again */
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 23, 10);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* trim end */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 25, NULL));
+
+  /* out-point of video_source now 15 */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 15, 0.2);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* out-point of overlay now 27 */
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 27, 6);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* trim backwards */
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 23, NULL));
+
+  /* out-point of video_source now 11 */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* in-point of overlay now 19 */
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 17, 16, 19, 14);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* changing the rate changes the out-point */
+  _assert_set_rate (rate0, "rate", 1.0, value);
+
+  /* out-point of video_source now 17 */
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* change back */
+  _assert_set_rate (rate0, "rate", 0.5, value);
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* make inactive */
+  fail_unless (ges_track_element_set_active (rate0, FALSE));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* make active again */
+  fail_unless (ges_track_element_set_active (rate0, TRUE));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay, which is after rate0 */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* change order */
+  fail_unless (ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (overlay),
+          2));
+
+  /* video source unchanged */
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* new out-point is 13
+   * new value is interpolated between the previous value
+   * (at time 7, value 11) and the *final* value (at time 19, value 14)
+   * Not the middle value at time 17, value 16! */
+  _TWO_TIMED_VALS (overlay_vals, 7, 11, 13, 12.5);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* removing time effect changes out-point */
+  gst_object_ref (rate0);
+  fail_unless (ges_clip_remove_top_effect (clip, GES_BASE_EFFECT (rate0),
+          NULL));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 17, 0.0);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  _TWO_TIMED_VALS (overlay_vals, 7, 11, 19, 14);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* adding also changes it */
+  fail_unless (ges_clip_add_top_effect (clip, GES_BASE_EFFECT (rate0), 2,
+          NULL));
+
+  _THREE_TIMED_VALS (video_source_vals, 5, 0.9, 7, 1.0, 11, 0.6);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* unchanged for overlay */
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  /* new value will use the value already set at in-point if possible */
+
+  assert_set_inpoint (video_source, 7);
+
+  _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 13, 0.4);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* same with out-point for overlay */
+  binding = ges_track_element_get_control_binding (overlay, "xpos");
+  fail_unless (binding);
+  g_object_get (binding, "control-source", &ctrl_source, NULL);
+
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 11, 5));
+  gst_object_unref (ctrl_source);
+  _THREE_TIMED_VALS (overlay_vals, 7, 11, 11, 5, 19, 14);
+
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  fail_unless (ges_timeline_element_edit_full (GES_TIMELINE_ELEMENT (clip),
+          -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 21, NULL));
+
+  _TWO_TIMED_VALS (video_source_vals, 7, 1.0, 9, 0.8);
+  _assert_control_source (video_source, "alpha", video_source_vals);
+
+  /* overlay uses existing value, rather than an interpolation */
+  _TWO_TIMED_VALS (overlay_vals, 7, 11, 11, 5);
+  _assert_control_source (overlay, "xpos", overlay_vals);
+
+  g_slist_free_full (video_source_vals, g_free);
+  g_slist_free_full (overlay_vals, g_free);
+
+  gst_object_unref (timeline);
+  g_value_unset (&value);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_unchanged_after_layer_add_failure)
+{
+  GList *found;
+  GESTrack *track;
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESClip *clip0, *clip1;
+  GESTimelineElement *effect, *source;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+  layer = ges_timeline_append_layer (timeline);
+
+  /* two video tracks */
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  clip0 = GES_CLIP (ges_test_clip_new ());
+  clip1 = GES_CLIP (ges_test_clip_new ());
+
+  gst_object_ref (clip0);
+  gst_object_ref (clip1);
+
+  assert_set_start (clip0, 0);
+  assert_set_duration (clip0, 10);
+  assert_set_start (clip1, 0);
+  assert_set_duration (clip1, 10);
+
+  effect = GES_TIMELINE_ELEMENT (ges_effect_new ("agingtv"));
+  _assert_add (clip1, effect);
+
+  assert_num_children (clip0, 0);
+  assert_num_children (clip1, 1);
+
+  fail_unless (ges_layer_add_clip (layer, clip0));
+
+  assert_num_children (clip0, 2);
+  assert_num_children (clip1, 1);
+
+  fail_unless (GES_CONTAINER_CHILDREN (clip1)->data == effect);
+
+  /* addition should fail since sources would fully overlap */
+  fail_if (ges_layer_add_clip (layer, clip1));
+
+  /* children should be the same */
+  assert_num_children (clip0, 2);
+  assert_num_children (clip1, 1);
+
+  fail_unless (GES_CONTAINER_CHILDREN (clip1)->data == effect);
+
+  /* should be able to add again once we have fixed the problem */
+  fail_unless (ges_layer_remove_clip (layer, clip0));
+
+  assert_num_children (clip0, 2);
+  assert_num_children (clip1, 1);
+
+  fail_unless (ges_layer_add_clip (layer, clip1));
+
+  assert_num_children (clip0, 2);
+  /* now has two sources and two effects */
+  assert_num_children (clip1, 4);
+
+  found = ges_clip_find_track_elements (clip1, NULL, GES_TRACK_TYPE_VIDEO,
+      GES_TYPE_VIDEO_SOURCE);
+  fail_unless_equals_int (g_list_length (found), 2);
+  g_list_free_full (found, gst_object_unref);
+
+  found = ges_clip_find_track_elements (clip1, NULL, GES_TRACK_TYPE_VIDEO,
+      GES_TYPE_EFFECT);
+  fail_unless_equals_int (g_list_length (found), 2);
+  g_list_free_full (found, gst_object_unref);
+
+  /* similarly cannot add clip0 back, and children should not change */
+  /* remove the extra source */
+  _assert_remove (clip0, GES_CONTAINER_CHILDREN (clip0)->data);
+  assert_num_children (clip0, 1);
+  source = GES_CONTAINER_CHILDREN (clip0)->data;
+
+  fail_if (ges_layer_add_clip (layer, clip0));
+
+  /* children should be the same */
+  assert_num_children (clip0, 1);
+  assert_num_children (clip1, 4);
+
+  fail_unless (GES_CONTAINER_CHILDREN (clip0)->data == source);
+
+  gst_object_unref (clip0);
+  gst_object_unref (clip1);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+#define _assert_timeline_to_internal(clip, child, in, expect_out) \
+{\
+  GError *error = NULL; \
+  GstClockTime found = ges_clip_get_internal_time_from_timeline_time ( \
+        clip, child, (in) * GST_SECOND, &error); \
+  GstClockTime expect =  expect_out * GST_SECOND; \
+  fail_unless (found == expect, "Conversion from timeline time %" \
+      GST_TIME_FORMAT " to the internal time of %" GES_FORMAT " gave %" \
+      GST_TIME_FORMAT " rather than the expected %" GST_TIME_FORMAT \
+      " (error: %s)", GST_TIME_ARGS ((in) * GST_SECOND), \
+      GES_ARGS (child), GST_TIME_ARGS (found), GST_TIME_ARGS (expect), \
+      error ? error->message : "None"); \
+  fail_if (error); \
+}
+
+#define _assert_timeline_to_internal_fails(clip, child, in, error_code) \
+{ \
+  GError *error = NULL; \
+  GstClockTime found = ges_clip_get_internal_time_from_timeline_time ( \
+        clip, child, (in) * GST_SECOND, &error); \
+  fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from timeline " \
+      "time %" GST_TIME_FORMAT " to the internal time of %" GES_FORMAT \
+      " successfully converted to %" GST_TIME_FORMAT " rather than " \
+      "GST_CLOCK_TIME_NONE", GST_TIME_ARGS ((in) * GST_SECOND), \
+      GES_ARGS (child), GST_TIME_ARGS (found)); \
+  assert_GESError (error, error_code); \
+}
+
+#define _assert_internal_to_timeline(clip, child, in, expect_out) \
+{\
+  GError *error = NULL; \
+  GstClockTime found = ges_clip_get_timeline_time_from_internal_time ( \
+        clip, child, (in) * GST_SECOND, &error); \
+  GstClockTime expect = expect_out * GST_SECOND; \
+  fail_unless (found == expect, "Conversion from the internal time %" \
+      GST_TIME_FORMAT " of %" GES_FORMAT " to the timeline time gave %" \
+      GST_TIME_FORMAT " rather than the expected %" GST_TIME_FORMAT, \
+      GST_TIME_ARGS ((in) * GST_SECOND), GES_ARGS (child), \
+      GST_TIME_ARGS (found), GST_TIME_ARGS (expect)); \
+  fail_if (error); \
+}
+
+#define _assert_internal_to_timeline_fails(clip, child, in, error_code) \
+{\
+  GError *error = NULL; \
+  GstClockTime found = ges_clip_get_timeline_time_from_internal_time ( \
+        clip, child, (in) * GST_SECOND, &error); \
+  fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from the " \
+      "internal time %" GST_TIME_FORMAT " of %" GES_FORMAT " to the " \
+      "timeline time gave %" GST_TIME_FORMAT " rather than " \
+      "GST_CLOCK_TIME_NONE", GST_TIME_ARGS ((in) * GST_SECOND), \
+      GES_ARGS (child), GST_TIME_ARGS (found)); \
+  assert_GESError (error, error_code); \
+}
+
+#define _assert_frame_to_timeline(clip, frame, expect_out) \
+{\
+  GError *error = NULL; \
+  GstClockTime found = ges_clip_get_timeline_time_from_source_frame ( \
+        clip, frame, &error); \
+  GstClockTime expect = expect_out * GST_SECOND; \
+  fail_unless (found == expect, "Conversion from the source frame %" \
+      G_GINT64_FORMAT " to the timeline time gave %" GST_TIME_FORMAT \
+      " rather than the expected %" GST_TIME_FORMAT, frame, \
+      GST_TIME_ARGS (found), GST_TIME_ARGS (expect)); \
+  fail_if (error); \
+}
+
+#define _assert_frame_to_timeline_fails(clip, frame, error_code) \
+{\
+  GError *error = NULL; \
+  GstClockTime found = ges_clip_get_timeline_time_from_source_frame ( \
+        clip, frame, &error); \
+  fail_if (GST_CLOCK_TIME_IS_VALID (found), "Conversion from the " \
+      "source frame %" G_GINT64_FORMAT " to the timeline time gave %" \
+      GST_TIME_FORMAT " rather than the expected GST_CLOCK_TIME_NONE", \
+      frame, GST_TIME_ARGS (found)); \
+  assert_GESError (error, error_code); \
+}
+
+GST_START_TEST (test_convert_time)
+{
+  GESTimeline *timeline;
+  GESTrack *track0, *track1;
+  GESAsset *asset;
+  GESLayer *layer;
+  GESClip *clip;
+  GESTrackElement *source0, *source1, *rate0, *rate1, *rate2, *overlay;
+  GValue val = G_VALUE_INIT;
+
+  ges_init ();
+
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP,
+      "framerate=30/1, max-duration=93.0", NULL);
+  fail_unless (asset);
+
+  timeline = ges_timeline_new ();
+
+  track0 = GES_TRACK (ges_video_track_new ());
+  track1 = GES_TRACK (ges_video_track_new ());
+
+  fail_unless (ges_timeline_add_track (timeline, track0));
+  fail_unless (ges_timeline_add_track (timeline, track1));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  clip = ges_layer_add_asset (layer, asset, 20 * GST_SECOND,
+      13 * GST_SECOND, 10 * GST_SECOND, GES_TRACK_TYPE_VIDEO);
+  fail_unless (clip);
+  CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND,
+      10 * GST_SECOND, 93 * GST_SECOND);
+
+  source0 =
+      ges_clip_find_track_element (clip, track0, GES_TYPE_VIDEO_TEST_SOURCE);
+  source1 =
+      ges_clip_find_track_element (clip, track1, GES_TYPE_VIDEO_TEST_SOURCE);
+
+  rate0 = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+  rate1 = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+  rate2 = GES_TRACK_ELEMENT (ges_effect_new ("videorate"));
+  overlay = GES_TRACK_ELEMENT (ges_effect_new ("textoverlay"));
+  ges_track_element_set_has_internal_source (overlay, TRUE);
+  /* enough internal content to last 10 seconds at a rate of 4.0 */
+  assert_set_inpoint (overlay, 7 * GST_SECOND);
+  assert_set_max_duration (overlay, 50 * GST_SECOND);
+
+  fail_unless (ges_track_add_element (track0, rate0));
+  fail_unless (ges_track_add_element (track1, rate1));
+  fail_unless (ges_track_add_element (track1, rate2));
+  fail_unless (ges_track_add_element (track1, overlay));
+
+  _assert_add (clip, rate0);
+  _assert_add (clip, rate2);
+  _assert_add (clip, overlay);
+  _assert_add (clip, rate1);
+
+  /* in track0:
+   *
+   * source0 -> rate0 -> out
+   *
+   * in track1:
+   *
+   * source1 -> rate1 -> overlay -> rate2 -> out
+   */
+
+  g_value_init (&val, G_TYPE_DOUBLE);
+
+  _assert_rate_equal (rate0, "rate", 1.0, val);
+  _assert_rate_equal (rate1, "rate", 1.0, val);
+  _assert_rate_equal (rate2, "rate", 1.0, val);
+
+  /* without rates */
+
+  /* start of the clip */
+  _assert_internal_to_timeline (clip, source0, 13, 20);
+  _assert_internal_to_timeline (clip, source1, 13, 20);
+  _assert_internal_to_timeline (clip, overlay, 7, 20);
+  _assert_frame_to_timeline (clip, 390, 20);
+  _assert_timeline_to_internal (clip, source0, 20, 13);
+  _assert_timeline_to_internal (clip, source1, 20, 13);
+  _assert_timeline_to_internal (clip, overlay, 20, 7);
+
+  /* middle of the clip */
+  _assert_internal_to_timeline (clip, source0, 18, 25);
+  _assert_internal_to_timeline (clip, source1, 18, 25);
+  _assert_internal_to_timeline (clip, overlay, 12, 25);
+  _assert_frame_to_timeline (clip, 540, 25);
+  _assert_timeline_to_internal (clip, source0, 25, 18);
+  _assert_timeline_to_internal (clip, source1, 25, 18);
+  _assert_timeline_to_internal (clip, overlay, 25, 12);
+
+  /* end of the clip */
+  _assert_internal_to_timeline (clip, source0, 23, 30);
+  _assert_internal_to_timeline (clip, source1, 23, 30);
+  _assert_internal_to_timeline (clip, overlay, 17, 30);
+  _assert_frame_to_timeline (clip, 690, 30);
+  _assert_timeline_to_internal (clip, source0, 30, 23);
+  _assert_timeline_to_internal (clip, source1, 30, 23);
+  _assert_timeline_to_internal (clip, overlay, 30, 17);
+
+  /* beyond the end of the clip */
+  /* exceeds the max-duration of the elements, but that is ok */
+  _assert_internal_to_timeline (clip, source0, 123, 130);
+  _assert_internal_to_timeline (clip, source1, 123, 130);
+  _assert_internal_to_timeline (clip, overlay, 117, 130);
+  _assert_frame_to_timeline (clip, 3690, 130);
+  _assert_timeline_to_internal (clip, source0, 130, 123);
+  _assert_timeline_to_internal (clip, source1, 130, 123);
+  _assert_timeline_to_internal (clip, overlay, 130, 117);
+
+  /* before the start of the clip */
+  _assert_internal_to_timeline (clip, source0, 8, 15);
+  _assert_internal_to_timeline (clip, source1, 8, 15);
+  _assert_internal_to_timeline (clip, overlay, 2, 15);
+  _assert_frame_to_timeline (clip, 240, 15);
+  _assert_timeline_to_internal (clip, source0, 15, 8);
+  _assert_timeline_to_internal (clip, source1, 15, 8);
+  _assert_timeline_to_internal (clip, overlay, 15, 2);
+
+  /* too early for overlay */
+  _assert_timeline_to_internal (clip, source0, 10, 3);
+  _assert_timeline_to_internal (clip, source1, 10, 3);
+  _assert_timeline_to_internal_fails (clip, overlay, 10,
+      GES_ERROR_NEGATIVE_TIME);
+
+  /* too early for sources */
+  _assert_timeline_to_internal_fails (clip, source0, 5,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, source1, 5,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, overlay, 5,
+      GES_ERROR_NEGATIVE_TIME);
+
+  assert_set_start (clip, 10 * GST_SECOND);
+
+  /* too early in the timeline */
+  _assert_internal_to_timeline_fails (clip, source0, 2,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_internal_to_timeline_fails (clip, source1, 2,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_internal_to_timeline (clip, overlay, 2, 5);
+  _assert_frame_to_timeline_fails (clip, 60, GES_ERROR_INVALID_FRAME_NUMBER);
+
+  assert_set_start (clip, 6 * GST_SECOND);
+  _assert_internal_to_timeline_fails (clip, source0, 6,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_internal_to_timeline_fails (clip, source1, 6,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_internal_to_timeline_fails (clip, overlay, 0,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_frame_to_timeline_fails (clip, 180, GES_ERROR_INVALID_FRAME_NUMBER);
+
+  assert_set_start (clip, 20 * GST_SECOND);
+
+  /* now with rate effects
+   * Note, they are currently out of sync */
+  _assert_set_rate (rate0, "rate", 0.5, val);
+  _assert_set_rate (rate1, "rate", 2.0, val);
+  _assert_set_rate (rate2, "rate", 4.0, val);
+
+  CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND,
+      10 * GST_SECOND, 93 * GST_SECOND);
+
+  /* start of the clip is the same */
+  _assert_internal_to_timeline (clip, source0, 13, 20);
+  _assert_internal_to_timeline (clip, source1, 13, 20);
+  _assert_internal_to_timeline (clip, overlay, 7, 20);
+  _assert_timeline_to_internal (clip, source0, 20, 13);
+  _assert_timeline_to_internal (clip, source1, 20, 13);
+  _assert_timeline_to_internal (clip, overlay, 20, 7);
+
+  /* middle is different */
+  /* 5 seconds in the timeline is 2.5 seconds into the source */
+  _assert_internal_to_timeline (clip, source0, 15.5, 25);
+  /* 5 seconds in the timeline is 40 seconds into the source */
+  _assert_internal_to_timeline (clip, source1, 53, 25);
+  /* 5 seconds in the timeline is 20 seconds into the source */
+  _assert_internal_to_timeline (clip, overlay, 27, 25);
+  /* reverse */
+  _assert_timeline_to_internal (clip, source0, 25, 15.5);
+  _assert_timeline_to_internal (clip, source1, 25, 53);
+  _assert_timeline_to_internal (clip, overlay, 25, 27);
+
+  /* end is different */
+  _assert_internal_to_timeline (clip, source0, 18, 30);
+  _assert_internal_to_timeline (clip, source1, 93, 30);
+  _assert_internal_to_timeline (clip, overlay, 47, 30);
+  _assert_timeline_to_internal (clip, source0, 30, 18);
+  _assert_timeline_to_internal (clip, source1, 30, 93);
+  _assert_timeline_to_internal (clip, overlay, 30, 47);
+
+  /* beyond end is different */
+  _assert_internal_to_timeline (clip, source0, 68, 130);
+  _assert_internal_to_timeline (clip, source1, 893, 130);
+  _assert_internal_to_timeline (clip, overlay, 447, 130);
+  _assert_timeline_to_internal (clip, source0, 130, 68);
+  _assert_timeline_to_internal (clip, source1, 130, 893);
+  _assert_timeline_to_internal (clip, overlay, 130, 447);
+
+  /* before the start */
+  _assert_internal_to_timeline (clip, source0, 12.5, 19);
+  _assert_internal_to_timeline (clip, source1, 5, 19);
+  _assert_internal_to_timeline (clip, overlay, 3, 19);
+  _assert_timeline_to_internal (clip, source0, 19, 12.5);
+  _assert_timeline_to_internal (clip, source1, 19, 5);
+  _assert_timeline_to_internal (clip, overlay, 19, 3);
+
+  /* too early for source1 and overlay */
+  _assert_internal_to_timeline (clip, source0, 12, 18);
+  _assert_timeline_to_internal (clip, source0, 18, 12);
+  _assert_timeline_to_internal_fails (clip, source1, 18,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, overlay, 18,
+      GES_ERROR_NEGATIVE_TIME);
+
+  assert_set_inpoint (overlay, 8 * GST_SECOND);
+  /* now fine */
+  _assert_internal_to_timeline (clip, overlay, 0, 18);
+  _assert_timeline_to_internal (clip, overlay, 18, 0);
+
+  assert_set_inpoint (overlay, 7 * GST_SECOND);
+
+  /* still not too early for source0 */
+  _assert_internal_to_timeline (clip, source0, 5.5, 5);
+  _assert_timeline_to_internal (clip, source0, 5, 5.5);
+  _assert_timeline_to_internal_fails (clip, source1, 5,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, overlay, 5,
+      GES_ERROR_NEGATIVE_TIME);
+
+  _assert_internal_to_timeline (clip, source0, 3, 0);
+  _assert_timeline_to_internal (clip, source0, 0, 3);
+  _assert_timeline_to_internal_fails (clip, source1, 5,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, overlay, 5,
+      GES_ERROR_NEGATIVE_TIME);
+
+  /* too early for the timeline */
+  _assert_internal_to_timeline_fails (clip, source0, 2,
+      GES_ERROR_NEGATIVE_TIME);
+
+  /* re-sync rates between tracks */
+  _assert_set_rate (rate2, "rate", 0.25, val);
+
+  CHECK_OBJECT_PROPS_MAX (clip, 20 * GST_SECOND, 13 * GST_SECOND,
+      10 * GST_SECOND, 93 * GST_SECOND);
+
+  /* start of the clip */
+  _assert_internal_to_timeline (clip, source0, 13, 20);
+  _assert_internal_to_timeline (clip, source1, 13, 20);
+  _assert_internal_to_timeline (clip, overlay, 7, 20);
+  _assert_frame_to_timeline (clip, 390, 20);
+  _assert_timeline_to_internal (clip, source0, 20, 13);
+  _assert_timeline_to_internal (clip, source1, 20, 13);
+  _assert_timeline_to_internal (clip, overlay, 20, 7);
+
+  /* middle of the clip */
+  _assert_internal_to_timeline (clip, source0, 15.5, 25);
+  _assert_internal_to_timeline (clip, source1, 15.5, 25);
+  _assert_internal_to_timeline (clip, overlay, 8.25, 25);
+  _assert_frame_to_timeline (clip, 465, 25);
+  _assert_timeline_to_internal (clip, source0, 25, 15.5);
+  _assert_timeline_to_internal (clip, source1, 25, 15.5);
+  _assert_timeline_to_internal (clip, overlay, 25, 8.25);
+
+  /* end of the clip */
+  _assert_internal_to_timeline (clip, source0, 18, 30);
+  _assert_internal_to_timeline (clip, source1, 18, 30);
+  _assert_internal_to_timeline (clip, overlay, 9.5, 30);
+  _assert_frame_to_timeline (clip, 540, 30);
+  _assert_timeline_to_internal (clip, source0, 30, 18);
+  _assert_timeline_to_internal (clip, source1, 30, 18);
+  _assert_timeline_to_internal (clip, overlay, 30, 9.5);
+
+  /* beyond the end of the clip */
+  /* exceeds the max-duration of the elements, but that is ok */
+  _assert_internal_to_timeline (clip, source0, 68, 130);
+  _assert_internal_to_timeline (clip, source1, 68, 130);
+  _assert_internal_to_timeline (clip, overlay, 34.5, 130);
+  _assert_frame_to_timeline (clip, 2040, 130);
+  _assert_timeline_to_internal (clip, source0, 130, 68);
+  _assert_timeline_to_internal (clip, source1, 130, 68);
+  _assert_timeline_to_internal (clip, overlay, 130, 34.5);
+
+  /* before the start of the clip */
+  _assert_internal_to_timeline (clip, source0, 10.5, 15);
+  _assert_internal_to_timeline (clip, source1, 10.5, 15);
+  _assert_internal_to_timeline (clip, overlay, 5.75, 15);
+  _assert_frame_to_timeline (clip, 315, 15);
+  _assert_timeline_to_internal (clip, source0, 15, 10.5);
+  _assert_timeline_to_internal (clip, source1, 15, 10.5);
+  _assert_timeline_to_internal (clip, overlay, 15, 5.75);
+
+  /* not too early */
+  _assert_internal_to_timeline (clip, source0, 3, 0);
+  _assert_internal_to_timeline (clip, source1, 3, 0);
+  _assert_internal_to_timeline (clip, overlay, 2, 0);
+  _assert_frame_to_timeline (clip, 90, 0);
+  _assert_timeline_to_internal (clip, source0, 0, 3);
+  _assert_timeline_to_internal (clip, source1, 0, 3);
+  _assert_timeline_to_internal (clip, overlay, 0, 2);
+
+  /* too early for timeline */
+  _assert_internal_to_timeline_fails (clip, source0, 2,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_internal_to_timeline_fails (clip, source1, 2,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_internal_to_timeline_fails (clip, overlay, 1,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_frame_to_timeline_fails (clip, 89, GES_ERROR_INVALID_FRAME_NUMBER);
+
+  assert_set_start (clip, 30 * GST_SECOND);
+  /* timeline times have shifted by 10 */
+  _assert_timeline_to_internal (clip, source0, 10, 3);
+  _assert_timeline_to_internal (clip, source1, 10, 3);
+  _assert_timeline_to_internal (clip, overlay, 10, 2);
+
+  _assert_timeline_to_internal (clip, source0, 4, 0);
+  _assert_timeline_to_internal (clip, source1, 4, 0);
+  _assert_timeline_to_internal (clip, overlay, 2, 0);
+  /* too early for internal */
+  _assert_timeline_to_internal_fails (clip, source0, 3,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, source1, 3,
+      GES_ERROR_NEGATIVE_TIME);
+  _assert_timeline_to_internal_fails (clip, overlay, 1,
+      GES_ERROR_NEGATIVE_TIME);
+
+  g_value_unset (&val);
+  gst_object_unref (asset);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 static Suite *
 ges_suite (void)
 {
@@ -739,12 +5526,31 @@ ges_suite (void)
 
   tcase_add_test (tc_chain, test_object_properties);
   tcase_add_test (tc_chain, test_split_object);
+  tcase_add_test (tc_chain, test_split_ordering);
   tcase_add_test (tc_chain, test_split_direct_bindings);
   tcase_add_test (tc_chain, test_split_direct_absolute_bindings);
+  tcase_add_test (tc_chain, test_split_with_auto_transitions);
   tcase_add_test (tc_chain, test_clip_group_ungroup);
+  tcase_add_test (tc_chain, test_clip_can_group);
+  tcase_add_test (tc_chain, test_adding_children_to_track);
   tcase_add_test (tc_chain, test_clip_refcount_remove_child);
   tcase_add_test (tc_chain, test_clip_find_track_element);
   tcase_add_test (tc_chain, test_effects_priorities);
+  tcase_add_test (tc_chain, test_children_time_setters);
+  tcase_add_test (tc_chain, test_not_enough_internal_content_for_core);
+  tcase_add_test (tc_chain, test_can_add_effect);
+  tcase_add_test (tc_chain, test_children_active);
+  tcase_add_test (tc_chain, test_children_inpoint);
+  tcase_add_test (tc_chain, test_children_max_duration);
+  tcase_add_test (tc_chain, test_duration_limit);
+  tcase_add_test (tc_chain, test_can_set_duration_limit);
+  tcase_add_test (tc_chain, test_rate_effects_duration_limit);
+  tcase_add_test (tc_chain, test_children_properties_contain);
+  tcase_add_test (tc_chain, test_children_properties_change);
+  tcase_add_test (tc_chain, test_copy_paste_children_properties);
+  tcase_add_test (tc_chain, test_children_property_bindings_with_rate_effects);
+  tcase_add_test (tc_chain, test_unchanged_after_layer_add_failure);
+  tcase_add_test (tc_chain, test_convert_time);
 
   return s;
 }
index d7cb857..b89c078 100644 (file)
@@ -182,15 +182,11 @@ GST_START_TEST (test_effect_clip)
   GESLayer *layer;
   GESTrack *track_audio, *track_video;
   GESEffectClip *effect_clip;
-  GESEffect *effect, *effect1;
-  GList *effects, *tmp;
-  gint i, clip_height;
-  gint effect_prio = -1;
-  /* FIXME the order of track type is not well defined */
-  guint track_type[4] = { GES_TRACK_TYPE_AUDIO,
-    GES_TRACK_TYPE_VIDEO, GES_TRACK_TYPE_VIDEO,
-    GES_TRACK_TYPE_AUDIO
-  };
+  GESEffect *effect, *effect1, *core_effect, *core_effect1;
+  GList *children, *top_effects, *tmp;
+  gint clip_height;
+  gint core_effect_prio;
+  gint effect_index, effect1_index;
 
   ges_init ();
 
@@ -204,45 +200,91 @@ GST_START_TEST (test_effect_clip)
   ges_timeline_add_layer (timeline, layer);
 
   GST_DEBUG ("Create effect");
-  effect_clip = ges_effect_clip_new ("agingtv", "audiopanorama");
+  /* these are the core video and audio effects for the clip */
+  effect_clip = ges_effect_clip_new ("videobalance", "audioecho");
 
   g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL);
 
   ges_layer_add_clip (layer, (GESClip *) effect_clip);
 
+  /* core elements should now be created */
+  fail_unless (children = GES_CONTAINER_CHILDREN (effect_clip));
+  core_effect = GES_EFFECT (children->data);
+  fail_unless (children = children->next);
+  core_effect1 = GES_EFFECT (children->data);
+  fail_unless (children->next == NULL);
+
+  /* both effects are placed at the same priority since they are core
+   * children of the clip, destined for different tracks */
+  core_effect_prio = _PRIORITY (core_effect);
+  assert_equals_int (core_effect_prio, _PRIORITY (core_effect1));
+  g_object_get (effect_clip, "height", &clip_height, NULL);
+  assert_equals_int (clip_height, 1);
+
+  /* add additional non-core effects */
   effect = ges_effect_new ("agingtv");
   fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
           GES_TIMELINE_ELEMENT (effect)));
   fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) ==
       track_video);
 
+  /* placed at a higher priority than the effects */
+  core_effect_prio = _PRIORITY (core_effect);
+  assert_equals_int (core_effect_prio, _PRIORITY (core_effect1));
+  fail_unless (_PRIORITY (effect) < core_effect_prio);
   g_object_get (effect_clip, "height", &clip_height, NULL);
-  assert_equals_int (clip_height, 3);
+  assert_equals_int (clip_height, 2);
+
+  effect_index =
+      ges_clip_get_top_effect_index (GES_CLIP (effect_clip),
+      GES_BASE_EFFECT (effect));
+  assert_equals_int (effect_index, 0);
 
+  /* 'effect1' is placed in between the core children and 'effect' */
   effect1 = ges_effect_new ("audiopanorama");
   fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
           GES_TIMELINE_ELEMENT (effect1)));
   fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect1)) ==
       track_audio);
 
+  /* 'effect' is still the highest priority effect, and the core
+   * elements are at the lowest priority */
+  core_effect_prio = _PRIORITY (core_effect);
+  assert_equals_int (core_effect_prio, _PRIORITY (core_effect1));
+  fail_unless (_PRIORITY (effect1) < core_effect_prio);
+  fail_unless (_PRIORITY (effect1) > _PRIORITY (effect));
   g_object_get (effect_clip, "height", &clip_height, NULL);
-  assert_equals_int (clip_height, 4);
-
-  effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
-  for (tmp = effects, i = 0; tmp; tmp = tmp->next, i++) {
-    gint priority = ges_clip_get_top_effect_position (GES_CLIP (effect_clip),
-        GES_BASE_EFFECT (tmp->data));
-    fail_unless (priority > effect_prio);
-    fail_unless (GES_IS_EFFECT (tmp->data));
-    fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (tmp->data))->
-        type == track_type[i]);
-    effect_prio = priority;
-
-    gst_object_unref (tmp->data);
-  }
-  g_list_free (effects);
+  assert_equals_int (clip_height, 3);
 
-  ges_layer_remove_clip (layer, (GESClip *) effect_clip);
+  effect_index =
+      ges_clip_get_top_effect_index (GES_CLIP (effect_clip),
+      GES_BASE_EFFECT (effect));
+  effect1_index =
+      ges_clip_get_top_effect_index (GES_CLIP (effect_clip),
+      GES_BASE_EFFECT (effect1));
+  assert_equals_int (effect_index, 0);
+  assert_equals_int (effect1_index, 1);
+
+  /* all effects are children of the effect_clip, ordered by priority */
+  fail_unless (children = GES_CONTAINER_CHILDREN (effect_clip));
+  fail_unless (children->data == effect);
+  fail_unless (children = children->next);
+  fail_unless (children->data == effect1);
+  fail_unless (children = children->next);
+  fail_unless (children->data == core_effect);
+  fail_unless (children = children->next);
+  fail_unless (children->data == core_effect1);
+  fail_unless (children->next == NULL);
+
+  /* but only the additional effects are part of the top effects */
+  top_effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
+  fail_unless (tmp = top_effects);
+  fail_unless (tmp->data == effect);
+  fail_unless (tmp = tmp->next);
+  fail_unless (tmp->data == effect1);
+  fail_unless (tmp->next == NULL);
+
+  g_list_free_full (top_effects, gst_object_unref);
 
   gst_object_unref (timeline);
 
@@ -253,14 +295,14 @@ GST_END_TEST;
 
 GST_START_TEST (test_priorities_clip)
 {
-  GList *effects, *tmp;
+  GList *top_effects, *tmp;
   GESTimeline *timeline;
   GESLayer *layer;
-  GESEffectClip *effect_clip;
-  GESTrack *track_audio, *track_video;
-  GESEffect *effect, *effect1, *audio_effect = NULL, *video_effect = NULL;
-
-  gint effect_prio = -1;
+  GESClip *effect_clip;
+  GESTrack *track_audio, *track_video, *track;
+  GESBaseEffect *effects[6], *audio_effect = NULL, *video_effect = NULL;
+  gint prev_index, i, num_effects = G_N_ELEMENTS (effects);
+  guint32 base_prio = MIN_NLE_PRIO + TRANSITIONS_HEIGHT;
 
   ges_init ();
 
@@ -274,7 +316,7 @@ GST_START_TEST (test_priorities_clip)
   ges_timeline_add_layer (timeline, layer);
 
   GST_DEBUG ("Create effect");
-  effect_clip = ges_effect_clip_new ("agingtv", "audiopanorama");
+  effect_clip = GES_CLIP (ges_effect_clip_new ("videobalance", "audioecho"));
 
   g_object_set (effect_clip, "duration", 25 * GST_SECOND, NULL);
 
@@ -291,73 +333,134 @@ GST_START_TEST (test_priorities_clip)
   }
   fail_unless (GES_IS_EFFECT (audio_effect));
   fail_unless (GES_IS_EFFECT (video_effect));
+  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (audio_effect)) ==
+      track_audio);
+  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (video_effect)) ==
+      track_video);
 
-  /* FIXME This is ridiculus, both effects should have the same priority */
-  assert_equals_int (_PRIORITY (audio_effect),
-      MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (_PRIORITY (video_effect),
-      MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 1);
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 2);
+  /* both the core effects have the same priority */
+  assert_equals_int (_PRIORITY (audio_effect), base_prio);
+  assert_equals_int (_PRIORITY (video_effect), base_prio);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 1);
+
+  /* can not change their priority using the top effect methods since
+   * they are not top effects */
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, audio_effect, 1)
+      == FALSE);
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, video_effect, 0)
+      == FALSE);
+
+  /* adding non-core effects */
+  GST_DEBUG ("Adding effects to the effect clip ");
+  for (i = 0; i < num_effects; i++) {
+    if (i % 2)
+      effects[i] = GES_BASE_EFFECT (ges_effect_new ("agingtv"));
+    else
+      effects[i] = GES_BASE_EFFECT (ges_effect_new ("audiopanorama"));
+    fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
+            GES_TIMELINE_ELEMENT (effects[i])));
+    assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 2 + i);
+    track = ges_track_element_get_track (GES_TRACK_ELEMENT (effects[i]));
+    if (i % 2)
+      fail_unless (track == track_video);
+    else
+      fail_unless (track == track_audio);
+  }
 
-  effect = ges_effect_new ("agingtv");
-  GST_DEBUG ("Adding effect to the effect clip %" GST_PTR_FORMAT, effect);
-  fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
-          GES_TIMELINE_ELEMENT (effect)));
-  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect)) ==
-      track_video);
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 3);
+  /* change top effect index */
+  for (i = 0; i < num_effects; i++) {
+    assert_equals_int (ges_clip_get_top_effect_index (effect_clip, effects[i]),
+        i);
+    assert_equals_int (_PRIORITY (effects[i]), i + base_prio);
+  }
 
-  effect1 = ges_effect_new ("audiopanorama");
-  GST_DEBUG ("Adding effect1 to the effect clip %" GST_PTR_FORMAT, effect1);
-  fail_unless (ges_container_add (GES_CONTAINER (effect_clip),
-          GES_TIMELINE_ELEMENT (effect1)));
-  fail_unless (ges_track_element_get_track (GES_TRACK_ELEMENT (effect1)) ==
-      track_audio);
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
+
+  /* moving 4th effect to index 1 should only change the priority of
+   * effects 1, 2, 3, and 4 because these lie between the new index (1)
+   * and the old index (4). */
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 1));
+
+  assert_equals_int (_PRIORITY (effects[0]), 0 + base_prio);
+  assert_equals_int (_PRIORITY (effects[1]), 2 + base_prio);
+  assert_equals_int (_PRIORITY (effects[2]), 3 + base_prio);
+  assert_equals_int (_PRIORITY (effects[3]), 4 + base_prio);
+  assert_equals_int (_PRIORITY (effects[4]), 1 + base_prio);
+  assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio);
+
+  /* everything else stays the same */
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
 
-  fail_unless (ges_clip_set_top_effect_priority (GES_CLIP (effect_clip),
-          GES_BASE_EFFECT (effect1), 0));
+  /* move back */
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 4));
+
+  for (i = 0; i < num_effects; i++) {
+    assert_equals_int (ges_clip_get_top_effect_index (effect_clip, effects[i]),
+        i);
+    assert_equals_int (_PRIORITY (effects[i]), i + base_prio);
+  }
+
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
   assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
 
-  assert_equals_int (_PRIORITY (effect), 3 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (_PRIORITY (effect1),
-      0 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
+  /* moving 2nd effect to index 4 should only change the priority of
+   * effects 2, 3 and 4 because these lie between the new index (4) and
+   * the old index (2). */
 
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 4);
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[2], 4));
 
-  fail_unless (ges_clip_set_top_effect_priority (GES_CLIP (effect_clip),
-          GES_BASE_EFFECT (effect1), 3));
-  assert_equals_int (_PRIORITY (effect), 2 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (_PRIORITY (effect1),
-      3 + MIN_NLE_PRIO + TRANSITIONS_HEIGHT);
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 4);
+  assert_equals_int (_PRIORITY (effects[0]), 0 + base_prio);
+  assert_equals_int (_PRIORITY (effects[1]), 1 + base_prio);
+  assert_equals_int (_PRIORITY (effects[2]), 4 + base_prio);
+  assert_equals_int (_PRIORITY (effects[3]), 2 + base_prio);
+  assert_equals_int (_PRIORITY (effects[4]), 3 + base_prio);
+  assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio);
 
-  effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
-  for (tmp = effects; tmp; tmp = tmp->next) {
-    gint priority = ges_clip_get_top_effect_position (GES_CLIP (effect_clip),
-        GES_BASE_EFFECT (tmp->data));
-    fail_unless (priority > effect_prio);
-    fail_unless (GES_IS_EFFECT (tmp->data));
-    effect_prio = priority;
+  /* everything else stays the same */
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
 
-    gst_object_unref (tmp->data);
-  }
-  g_list_free (effects);
+  /* move 4th effect to index 0 should only change the priority of
+   * effects 0, 1, 3 and 4 because these lie between the new index (0) and
+   * the old index (3) */
 
-  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), 4);
-  effects = ges_clip_get_top_effects (GES_CLIP (effect_clip));
-  effect_prio = 0;
-  for (tmp = effects; tmp; tmp = tmp->next) {
-    gint priority = ges_clip_get_top_effect_position (GES_CLIP (effect_clip),
+  fail_unless (ges_clip_set_top_effect_index (effect_clip, effects[4], 0));
+
+  assert_equals_int (_PRIORITY (effects[0]), 1 + base_prio);
+  assert_equals_int (_PRIORITY (effects[1]), 2 + base_prio);
+  assert_equals_int (_PRIORITY (effects[2]), 4 + base_prio);
+  assert_equals_int (_PRIORITY (effects[3]), 3 + base_prio);
+  assert_equals_int (_PRIORITY (effects[4]), 0 + base_prio);
+  assert_equals_int (_PRIORITY (effects[5]), 5 + base_prio);
+
+  /* everything else stays the same */
+  assert_equals_int (_PRIORITY (video_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (audio_effect), num_effects + base_prio);
+  assert_equals_int (_PRIORITY (effect_clip), 1);
+  assert_equals_int (GES_CONTAINER_HEIGHT (effect_clip), num_effects + 1);
+
+  /* make sure top effects are ordered by index */
+  top_effects = ges_clip_get_top_effects (effect_clip);
+  prev_index = -1;
+  for (tmp = top_effects; tmp; tmp = tmp->next) {
+    gint index = ges_clip_get_top_effect_index (effect_clip,
         GES_BASE_EFFECT (tmp->data));
-    fail_unless (priority >= effect_prio, "%d >= %d", priority, effect_prio);
+    fail_unless (index >= 0);
+    fail_unless (index > prev_index);
     fail_unless (GES_IS_EFFECT (tmp->data));
-    effect_prio = priority;
-
-    gst_object_unref (tmp->data);
+    prev_index = index;
   }
-  g_list_free (effects);
-
-  ges_layer_remove_clip (layer, (GESClip *) effect_clip);
+  g_list_free_full (top_effects, gst_object_unref);
 
   gst_object_unref (timeline);
 
@@ -526,7 +629,7 @@ GST_START_TEST (test_split_clip_effect_priorities)
   ges_init ();
 
   timeline = ges_timeline_new ();
-  layer = ges_layer_new ();
+  layer = ges_timeline_append_layer (timeline);
   track_video = GES_TRACK (ges_video_track_new ());
 
   g_object_set (timeline, "auto-transition", TRUE, NULL);
@@ -547,6 +650,7 @@ GST_START_TEST (test_split_clip_effect_priorities)
   assert_equals_uint64 (GES_TIMELINE_ELEMENT_PRIORITY (source), 4);
 
   nclip = ges_clip_split (clip, GST_SECOND);
+  fail_unless (nclip);
   neffect = ges_clip_find_track_element (nclip, NULL, GES_TYPE_EFFECT);
   nsource = ges_clip_find_track_element (nclip, NULL, GES_TYPE_VIDEO_SOURCE);
 
@@ -570,6 +674,220 @@ GST_START_TEST (test_split_clip_effect_priorities)
 
 GST_END_TEST;
 
+#define _NO_ERROR -1
+#define _set_rate(videorate, rate, error_code) \
+{ \
+  g_value_set_double (&val, rate); \
+  if (error_code != _NO_ERROR) { \
+    fail_if (ges_timeline_element_set_child_property_full ( \
+          GES_TIMELINE_ELEMENT (videorate), "rate", &val, &error)); \
+    assert_GESError (error, error_code); \
+  } else { \
+    fail_unless (ges_timeline_element_set_child_property_full ( \
+          GES_TIMELINE_ELEMENT (videorate), "rate", &val, &error)); \
+    fail_if (error); \
+  } \
+}
+
+#define _add_effect(clip, effect, _index, error_code) \
+{ \
+  gint index = _index; \
+  if (error_code != _NO_ERROR) { \
+    fail_if (ges_clip_add_top_effect (clip, effect, index, &error)); \
+    assert_GESError (error, error_code); \
+  } else { \
+    GList *effects; \
+    gboolean res = ges_clip_add_top_effect (clip, effect, index, &error); \
+    fail_unless (res, "Adding effect " #effect " failed: %s", \
+        error ? error->message : "No error produced"); \
+    fail_if (error); \
+    effects = ges_clip_get_top_effects (clip); \
+    fail_unless (g_list_find (effects, effect)); \
+    if (index < 0 || index >= g_list_length (effects)) \
+      index = g_list_length (effects) - 1; \
+    assert_equals_int (ges_clip_get_top_effect_index (clip, effect), index); \
+    fail_unless (g_list_nth_data (effects, index) == effect); \
+    g_list_free_full (effects, gst_object_unref); \
+  } \
+}
+
+#define _remove_effect(clip, effect, error_code) \
+{ \
+  if (error_code != _NO_ERROR) { \
+    fail_if (ges_clip_remove_top_effect (clip, effect, &error)); \
+    assert_GESError (error, error_code); \
+  } else { \
+    GList *effects; \
+    gboolean res = ges_clip_remove_top_effect (clip, effect, &error); \
+    fail_unless (res, "Removing effect " #effect " failed: %s", \
+        error ? error->message : "No error produced"); \
+    fail_if (error); \
+    effects = ges_clip_get_top_effects (clip); \
+    fail_if (g_list_find (effects, effect)); \
+    g_list_free_full (effects, gst_object_unref); \
+  } \
+}
+
+#define _move_effect(clip, effect, index, error_code) \
+{ \
+  if (error_code != _NO_ERROR) { \
+    fail_if ( \
+        ges_clip_set_top_effect_index_full (clip, effect, index, &error)); \
+    assert_GESError (error, error_code); \
+  } else { \
+    GList *effects; \
+    gboolean res = \
+        ges_clip_set_top_effect_index_full (clip, effect, index, &error); \
+    fail_unless (res, "Moving effect " #effect " failed: %s", \
+        error ? error->message : "No error produced"); \
+    fail_if (error); \
+    effects = ges_clip_get_top_effects (clip); \
+    fail_unless (g_list_find (effects, effect)); \
+    assert_equals_int (ges_clip_get_top_effect_index (clip, effect), index); \
+    fail_unless (g_list_nth_data (effects, index) == effect); \
+    g_list_free_full (effects, gst_object_unref); \
+  } \
+}
+
+GST_START_TEST (test_move_time_effect)
+{
+  GESTimeline *timeline;
+  GESTrack *track;
+  GESLayer *layer;
+  GESAsset *asset;
+  GESClip *clip;
+  GESBaseEffect *rate0, *rate1, *overlay;
+  GError *error = NULL;
+  GValue val = G_VALUE_INIT;
+
+  ges_init ();
+
+  g_value_init (&val, G_TYPE_DOUBLE);
+
+
+  timeline = ges_timeline_new ();
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  /* add a dummy clip for overlap */
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, "max-duration=16", &error);
+  fail_unless (asset);
+  fail_if (error);
+
+  fail_unless (ges_layer_add_asset_full (layer, asset, 0, 0, 16,
+          GES_TRACK_TYPE_UNKNOWN, &error));
+  fail_if (error);
+
+  clip = GES_CLIP (ges_asset_extract (asset, &error));
+  fail_unless (clip);
+  fail_if (error);
+  assert_set_start (clip, 8);
+  assert_set_duration (clip, 16);
+
+  rate0 = GES_BASE_EFFECT (ges_effect_new ("videorate"));
+  rate1 = GES_BASE_EFFECT (ges_effect_new ("videorate"));
+  overlay = GES_BASE_EFFECT (ges_effect_new ("textoverlay"));
+
+  ges_track_element_set_has_internal_source (GES_TRACK_ELEMENT (overlay), TRUE);
+  /* only has 8ns of content */
+  assert_set_inpoint (overlay, 13);
+  assert_set_max_duration (overlay, 21);
+
+  _set_rate (rate0, 2.0, _NO_ERROR);
+  _set_rate (rate1, 0.5, _NO_ERROR);
+
+  /* keep alive */
+  gst_object_ref (clip);
+  gst_object_ref (rate0);
+  gst_object_ref (rate1);
+  gst_object_ref (overlay);
+
+  /* cannot add to layer with rate effect because it would cause a full
+   * overlap */
+  _add_effect (clip, rate0, 0, _NO_ERROR);
+  fail_if (ges_layer_add_clip_full (layer, clip, &error));
+  assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _remove_effect (clip, rate0, _NO_ERROR);
+
+  /* same with overlay */
+  _add_effect (clip, overlay, 0, _NO_ERROR);
+  fail_if (ges_layer_add_clip_full (layer, clip, &error));
+  assert_GESError (error, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _remove_effect (clip, overlay, _NO_ERROR);
+
+  CHECK_OBJECT_PROPS (clip, 8, 0, 16);
+
+  fail_unless (ges_layer_add_clip_full (layer, clip, &error));
+  fail_if (error);
+
+  CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 16);
+
+  /* can't add rate0 or overlay in the same way */
+  _add_effect (clip, rate0, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _add_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* rate1 extends the duration-limit instead */
+  _add_effect (clip, rate1, 0, _NO_ERROR);
+
+  /* can't add overlay next to the timeline */
+  _add_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  /* but next to source is ok */
+  _add_effect (clip, overlay, 1, _NO_ERROR);
+
+  /* can't add rate0 after overlay */
+  _add_effect (clip, rate0, 1, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  /* but before is ok */
+  _add_effect (clip, rate0, -1, _NO_ERROR);
+
+  /* can't move rate0 to end */
+  _move_effect (clip, rate0, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  /* can't move overlay to start or end */
+  _move_effect (clip, overlay, 0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _move_effect (clip, overlay, 2, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* can now move: swap places with rate1 */
+  _set_rate (rate0, 0.5, _NO_ERROR);
+  _move_effect (clip, rate0, 0, _NO_ERROR);
+  _move_effect (clip, rate1, 2, _NO_ERROR);
+  _set_rate (rate1, 2.0, _NO_ERROR);
+
+  /* cannot speed up either rate too much */
+  _set_rate (rate0, 1.0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+  _set_rate (rate1, 4.0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* cannot remove rate0 which is slowing down */
+  _remove_effect (clip, rate0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  /* removing the speed-up is fine */
+  _remove_effect (clip, rate1, _NO_ERROR);
+
+  /* removing the overlay is fine */
+  _remove_effect (clip, overlay, _NO_ERROR);
+
+  CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 16);
+  assert_set_max_duration (clip, 8);
+  CHECK_OBJECT_PROPS_MAX (clip, 8, 0, 16, 8);
+  /* still can't remove the slow down since it is the only thing stopping
+   * a full overlap */
+  _remove_effect (clip, rate0, GES_ERROR_INVALID_OVERLAP_IN_TRACK);
+
+  gst_object_unref (clip);
+  /* shouldn't have any problems when removing from the layer */
+  fail_unless (ges_layer_remove_clip (layer, clip));
+
+  g_value_reset (&val);
+  gst_object_unref (rate0);
+  gst_object_unref (rate1);
+  gst_object_unref (overlay);
+  gst_object_unref (asset);
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
 
 static Suite *
 ges_suite (void)
@@ -587,6 +905,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_effect_set_properties);
   tcase_add_test (tc_chain, test_clip_signals);
   tcase_add_test (tc_chain, test_split_clip_effect_priorities);
+  tcase_add_test (tc_chain, test_move_time_effect);
 
   return s;
 }
index edbd911..6c6002b 100644 (file)
@@ -195,40 +195,67 @@ GST_START_TEST (test_move_group)
    *            |----------------------------------|
    *            |        7---------     2----------|
    * layer1:    |        | clip1   |    |  clip2   |
-   *            |       22--------30   62----------|
+   *            |       20--------30   60----------|
    *            |----------------------------------|
    */
   ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 12);
   CHECK_OBJECT_PROPS (clip, 12, 2, 3);
-  CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
-  CHECK_OBJECT_PROPS (clip2, 62, 2, 48);
+  CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
   CHECK_OBJECT_PROPS (group, 12, 0, 98);
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
 
   /* Setting the duration would lead to overlaps */
-  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 10);
+  fail_if (ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group),
+          10));
   CHECK_OBJECT_PROPS (clip, 12, 2, 3);
-  CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
-  CHECK_OBJECT_PROPS (clip2, 62, 2, 48);
+  CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
   CHECK_OBJECT_PROPS (group, 12, 0, 98);
   ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 100);
   CHECK_OBJECT_PROPS (clip, 12, 2, 3);
-  CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
-  CHECK_OBJECT_PROPS (clip2, 62, 2, 50);
+  CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 60, 0, 52);
   CHECK_OBJECT_PROPS (group, 12, 0, 100);
 
   ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (group), 20);
   CHECK_OBJECT_PROPS (clip, 20, 2, 3);
-  CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
-  CHECK_OBJECT_PROPS (clip2, 70, 2, 50);
+  CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
   CHECK_OBJECT_PROPS (group, 20, 0, 100);
 
+  /* Trim fails because clip inpoint would become negative */
   fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10));
   CHECK_OBJECT_PROPS (clip, 20, 2, 3);
-  CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
-  CHECK_OBJECT_PROPS (clip2, 70, 2, 50);
+  CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
   CHECK_OBJECT_PROPS (group, 20, 0, 100);
 
+  fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 18));
+  CHECK_OBJECT_PROPS (clip, 18, 0, 5);
+  CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
+  CHECK_OBJECT_PROPS (group, 18, 0, 102);
+
+  fail_unless (ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip),
+          17));
+  CHECK_OBJECT_PROPS (clip, 18, 0, 17);
+  CHECK_OBJECT_PROPS (clip1, 28, 5, 10);
+  CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
+  CHECK_OBJECT_PROPS (group, 18, 0, 102);
+
+  fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 30));
+  CHECK_OBJECT_PROPS (clip, 30, 12, 5);
+  CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
+  CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
+  CHECK_OBJECT_PROPS (group, 30, 0, 90);
+
+  fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25));
+  CHECK_OBJECT_PROPS (clip, 25, 7, 10);
+  CHECK_OBJECT_PROPS (clip1, 25, 2, 13);
+  CHECK_OBJECT_PROPS (clip2, 68, 0, 52);
+  CHECK_OBJECT_PROPS (group, 25, 0, 95);
+
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
   check_destroyed (G_OBJECT (timeline), G_OBJECT (group), NULL);
   gst_object_unref (asset);
@@ -489,13 +516,25 @@ GST_START_TEST (test_group_in_group_layer_moving)
   CHECK_OBJECT_PROPS (group, 10, 0, 20);
   assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 0);
   assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 0);
 
   ges_layer_set_priority (layer2, 0);
+  /* no change since none of the clips are in layer2 */
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 0);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 0);
+
   ges_layer_set_priority (layer, 1);
-  ges_layer_set_priority (layer1, 2);
+  /* c's layer now has priority 1 (conflicts with layer1) */
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 1);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 1);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 1);
 
+  ges_layer_set_priority (layer1, 2);
+  /* conflicting layer priorities now resolved */
   assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c), 1);
   assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (c1), 2);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (group), 1);
 
   /* Our timeline
    *
@@ -677,12 +716,149 @@ GST_START_TEST (test_group_serialization)
 
   g_free (tmpuri);
   gst_object_unref (timeline);
+  gst_object_unref (asset);
 
   ges_deinit ();
 }
 
 GST_END_TEST;
 
+GST_START_TEST (test_children_properties_contain)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESAsset *asset;
+  GESTimelineElement *c1, *c2, *c3, *g1, *g2;
+  GParamSpec **child_props1, **child_props2;
+  guint num_props1, num_props2;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_timeline_append_layer (timeline);
+
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
+  /* choose one audio and one video to give them different properties */
+  c1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, 10,
+          GES_TRACK_TYPE_AUDIO));
+  c2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 20, 0, 10,
+          GES_TRACK_TYPE_VIDEO));
+  /* but c3 will have the same child properties as c1! */
+  c3 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 40, 0, 10,
+          GES_TRACK_TYPE_AUDIO));
+
+  fail_unless (c1);
+  fail_unless (c2);
+
+  g1 = GES_TIMELINE_ELEMENT (ges_group_new ());
+  g2 = GES_TIMELINE_ELEMENT (ges_group_new ());
+
+  /* group should have the same as its children */
+  fail_unless (ges_container_add (GES_CONTAINER (g1), c1));
+
+  num_props1 = 0;
+  child_props1 = append_children_properties (NULL, c1, &num_props1);
+  num_props2 = 0;
+  child_props2 = append_children_properties (NULL, g1, &num_props2);
+
+  assert_property_list_match (child_props1, num_props1,
+      child_props2, num_props2);
+
+  /* add next child and gain its children properties as well */
+  fail_unless (ges_container_add (GES_CONTAINER (g1), c2));
+
+  /* add the child properties of c2 to the existing list for c1 */
+  child_props1 = append_children_properties (child_props1, c2, &num_props1);
+
+  free_children_properties (child_props2, num_props2);
+  num_props2 = 0;
+  child_props2 = append_children_properties (NULL, g1, &num_props2);
+
+  assert_property_list_match (child_props1, num_props1,
+      child_props2, num_props2);
+
+  /* FIXME: if c1 and c3 have the same child properties (they use the
+   * same GParamSpec) then ges_timeline_element_add_child_property_full
+   * will fail, even though the corresponding GObject child is not the
+   * same instance */
+
+  fail_unless (ges_container_add (GES_CONTAINER (g1), c3));
+
+  /* FIXME: regarding the above comment, ideally we would append the
+   * children properties for c3 to child_props1, so that its children
+   * properties appear twice in the list:
+   * child_props1 =
+   * append_children_properties (child_props1, c3, &num_props1); */
+
+  free_children_properties (child_props2, num_props2);
+  num_props2 = 0;
+  child_props2 = append_children_properties (NULL, g1, &num_props2);
+
+  assert_property_list_match (child_props1, num_props1,
+      child_props2, num_props2);
+
+  /* remove c3 */
+  fail_unless (ges_container_remove (GES_CONTAINER (g1), c3));
+
+  /* FIXME: regarding the above comment, ideally we would reset
+   * child_props1 to only contain the child properties for c1 and c2
+   * Currently, we at least want to make sure that the child properties
+   * for c1 remain.
+   * Currently, if we removed c1 first, all its children properties would
+   * be removed from g1, and this would *not* automatically register the
+   * children properties for c3. */
+
+  free_children_properties (child_props2, num_props2);
+  num_props2 = 0;
+  child_props2 = append_children_properties (NULL, g1, &num_props2);
+
+  assert_property_list_match (child_props1, num_props1,
+      child_props2, num_props2);
+
+  /* remove c1 */
+  fail_unless (ges_container_remove (GES_CONTAINER (g1), c1));
+
+  free_children_properties (child_props1, num_props1);
+  num_props1 = 0;
+  child_props1 = append_children_properties (NULL, c2, &num_props1);
+
+  free_children_properties (child_props2, num_props2);
+  num_props2 = 0;
+  child_props2 = append_children_properties (NULL, g1, &num_props2);
+
+  assert_property_list_match (child_props1, num_props1,
+      child_props2, num_props2);
+
+  /* add g1 and c1 to g2 */
+  fail_unless (ges_container_add (GES_CONTAINER (g2), g1));
+  fail_unless (ges_container_add (GES_CONTAINER (g2), c1));
+
+  free_children_properties (child_props1, num_props1);
+  num_props1 = 0;
+  child_props1 = append_children_properties (NULL, g2, &num_props1);
+
+  free_children_properties (child_props2, num_props2);
+  num_props2 = 0;
+  child_props2 = append_children_properties (NULL, c1, &num_props2);
+  child_props2 = append_children_properties (child_props2, g1, &num_props2);
+
+  assert_property_list_match (child_props1, num_props1,
+      child_props2, num_props2);
+
+  free_children_properties (child_props1, num_props1);
+  free_children_properties (child_props2, num_props2);
+
+  gst_object_unref (timeline);
+  gst_object_unref (asset);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+
+
 static Suite *
 ges_suite (void)
 {
@@ -696,6 +872,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_group_in_self);
   tcase_add_test (tc_chain, test_group_serialization);
   tcase_add_test (tc_chain, test_group_in_group_layer_moving);
+  tcase_add_test (tc_chain, test_children_properties_contain);
 
   return s;
 }
index c2ab229..763e78a 100644 (file)
@@ -395,8 +395,7 @@ GST_START_TEST (test_single_layer_automatic_transition)
 
   transition = objects->next->next->data;
   assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_int (_START (transition), 500);
-  assert_equals_uint64 (_DURATION (transition), 750);
+  CHECK_OBJECT_PROPS (transition, 500, 0, 750);
   g_list_free_full (objects, gst_object_unref);
 
   fail_if (ges_timeline_element_set_start (src1, 250));
@@ -461,6 +460,7 @@ GST_START_TEST (test_single_layer_automatic_transition)
   g_list_free_full (objects, gst_object_unref);
 
   gst_object_unref (timeline);
+  gst_object_unref (asset);
 
   ges_deinit ();
 }
@@ -929,6 +929,7 @@ GST_START_TEST (test_multi_layer_automatic_transition)
   ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2);
 
   gst_object_unref (timeline);
+  gst_object_unref (asset);
 
   ges_deinit ();
 }
@@ -1163,6 +1164,7 @@ GST_START_TEST (test_layer_activate_automatic_transition)
 
 
   gst_object_unref (timeline);
+  gst_object_unref (asset);
 
   ges_deinit ();
 }
@@ -1462,6 +1464,70 @@ GST_START_TEST (test_layer_meta_value)
 
 GST_END_TEST;
 
+
+GST_START_TEST (test_layer_meta_marker_list)
+{
+  GESTimeline *timeline;
+  GESLayer *layer, *layer2;
+  GESMarkerList *mlist, *mlist2;
+  GESMarker *marker;
+  gchar *metas1, *metas2;
+
+  ges_init ();
+
+  timeline = ges_timeline_new_audio_video ();
+  layer = ges_layer_new ();
+  ges_timeline_add_layer (timeline, layer);
+  layer2 = ges_layer_new ();
+  ges_timeline_add_layer (timeline, layer2);
+
+  mlist = ges_marker_list_new ();
+  marker = ges_marker_list_add (mlist, 42);
+  ges_meta_container_set_string (GES_META_CONTAINER (marker), "bar", "baz");
+  marker = ges_marker_list_add (mlist, 84);
+  ges_meta_container_set_string (GES_META_CONTAINER (marker), "lorem",
+      "ip\tsu\"m;");
+
+  ASSERT_OBJECT_REFCOUNT (mlist, "local ref", 1);
+
+  fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER (layer),
+          "foo", mlist));
+
+  ASSERT_OBJECT_REFCOUNT (mlist, "GstStructure + local ref", 2);
+
+  mlist2 =
+      ges_meta_container_get_marker_list (GES_META_CONTAINER (layer), "foo");
+
+  fail_unless (mlist == mlist2);
+
+  ASSERT_OBJECT_REFCOUNT (mlist, "GstStructure + getter + local ref", 3);
+
+  g_object_unref (mlist2);
+
+  ASSERT_OBJECT_REFCOUNT (mlist, "GstStructure + local ref", 2);
+
+  metas1 = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer));
+  ges_meta_container_add_metas_from_string (GES_META_CONTAINER (layer2),
+      metas1);
+  metas2 = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer2));
+
+  fail_unless_equals_string (metas1, metas2);
+  g_free (metas1);
+  g_free (metas2);
+
+  fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER (layer),
+          "foo", NULL));
+
+  ASSERT_OBJECT_REFCOUNT (mlist, "local ref", 1);
+
+  g_object_unref (mlist);
+  g_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 GST_START_TEST (test_layer_meta_register)
 {
   GESTimeline *timeline;
@@ -1667,6 +1733,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_layer_meta_date);
   tcase_add_test (tc_chain, test_layer_meta_date_time);
   tcase_add_test (tc_chain, test_layer_meta_value);
+  tcase_add_test (tc_chain, test_layer_meta_marker_list);
   tcase_add_test (tc_chain, test_layer_meta_register);
   tcase_add_test (tc_chain, test_layer_meta_foreach);
   tcase_add_test (tc_chain, test_layer_get_clips_in_interval);
diff --git a/tests/check/ges/markerlist.c b/tests/check/ges/markerlist.c
new file mode 100644 (file)
index 0000000..e26dfbb
--- /dev/null
@@ -0,0 +1,483 @@
+/* GStreamer Editing Services
+ *
+ * Copyright (C) <2019> Mathieu Duponchelle <mathieu@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "test-utils.h"
+#include <ges/ges.h>
+#include <gst/check/gstcheck.h>
+
+GST_START_TEST (test_add)
+{
+  GESMarkerList *markerlist;
+  GESMarker *marker;
+  ges_init ();
+
+  markerlist = ges_marker_list_new ();
+  marker = ges_marker_list_add (markerlist, 42);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "marker list", 1);
+
+  g_object_ref (marker);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "marker list + local ref", 2);
+
+  g_object_unref (markerlist);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1);
+
+  g_object_unref (marker);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_remove)
+{
+  GESMarkerList *markerlist;
+  GESMarker *marker;
+  ges_init ();
+
+  markerlist = ges_marker_list_new ();
+  marker = ges_marker_list_add (markerlist, 42);
+
+  g_object_ref (marker);
+
+  fail_unless_equals_int (ges_marker_list_size (markerlist), 1);
+
+  fail_unless (ges_marker_list_remove (markerlist, marker));
+  fail_unless_equals_int (ges_marker_list_size (markerlist), 0);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1);
+
+  fail_if (ges_marker_list_remove (markerlist, marker));
+
+  g_object_unref (marker);
+
+  g_object_unref (markerlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+static void
+marker_added_cb (GESMarkerList * mlist, GstClockTime position,
+    GESMarker * marker, gboolean * called)
+{
+  fail_unless_equals_int (position, 42);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref + signal", 2);
+  *called = TRUE;
+}
+
+GST_START_TEST (test_signal_marker_added)
+{
+  GESMarkerList *mlist;
+  GESMarker *marker;
+  gboolean called = FALSE;
+
+  ges_init ();
+
+  mlist = ges_marker_list_new ();
+  g_signal_connect (mlist, "marker-added", G_CALLBACK (marker_added_cb),
+      &called);
+  marker = ges_marker_list_add (mlist, 42);
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1);
+  fail_unless (called == TRUE);
+  g_signal_handlers_disconnect_by_func (mlist, marker_added_cb, &called);
+
+  g_object_unref (mlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+static void
+marker_removed_cb (GESMarkerList * mlist, GESMarker * marker, gboolean * called)
+{
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref + signal", 2);
+  *called = TRUE;
+}
+
+GST_START_TEST (test_signal_marker_removed)
+{
+  GESMarkerList *mlist;
+  GESMarker *marker;
+  gboolean called = FALSE;
+
+  ges_init ();
+
+  mlist = ges_marker_list_new ();
+  marker = ges_marker_list_add (mlist, 42);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1);
+
+  g_signal_connect (mlist, "marker-removed", G_CALLBACK (marker_removed_cb),
+      &called);
+
+  fail_unless (ges_marker_list_remove (mlist, marker));
+
+  fail_unless (called == TRUE);
+
+  g_signal_handlers_disconnect_by_func (mlist, marker_removed_cb, &called);
+
+  g_object_unref (mlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+static void
+marker_moved_cb (GESMarkerList * mlist, GstClockTime prev_position,
+    GstClockTime position, GESMarker * marker, gboolean * called)
+{
+  fail_unless_equals_int (prev_position, 10);
+  fail_unless_equals_int (position, 42);
+
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref + signal", 2);
+  *called = TRUE;
+}
+
+GST_START_TEST (test_signal_marker_moved)
+{
+  GESMarkerList *mlist;
+  GESMarker *marker;
+  gboolean called = FALSE;
+
+  ges_init ();
+
+  mlist = ges_marker_list_new ();
+  g_signal_connect (mlist, "marker-moved", G_CALLBACK (marker_moved_cb),
+      &called);
+
+  marker = ges_marker_list_add (mlist, 10);
+  ASSERT_OBJECT_REFCOUNT (marker, "local ref", 1);
+
+  fail_unless (ges_marker_list_move (mlist, marker, 42));
+
+  fail_unless (called == TRUE);
+
+  g_signal_handlers_disconnect_by_func (mlist, marker_moved_cb, &called);
+
+  g_object_unref (mlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_get_markers)
+{
+  GESMarkerList *markerlist;
+  GESMarker *marker1, *marker2, *marker3, *marker4;
+  GList *markers;
+
+  ges_init ();
+
+  markerlist = ges_marker_list_new ();
+  marker1 = ges_marker_list_add (markerlist, 0);
+  marker2 = ges_marker_list_add (markerlist, 10);
+  marker3 = ges_marker_list_add (markerlist, 20);
+  marker4 = ges_marker_list_add (markerlist, 30);
+
+  markers = ges_marker_list_get_markers (markerlist);
+
+  ASSERT_OBJECT_REFCOUNT (marker1, "local ref + markers", 2);
+  ASSERT_OBJECT_REFCOUNT (marker2, "local ref + markers", 2);
+  ASSERT_OBJECT_REFCOUNT (marker3, "local ref + markers", 2);
+  ASSERT_OBJECT_REFCOUNT (marker4, "local ref + markers", 2);
+
+  fail_unless (g_list_index (markers, marker1) == 0);
+  fail_unless (g_list_index (markers, marker2) == 1);
+  fail_unless (g_list_index (markers, marker3) == 2);
+  fail_unless (g_list_index (markers, marker4) == 3);
+
+  g_list_foreach (markers, (GFunc) gst_object_unref, NULL);
+  g_list_free (markers);
+
+  g_object_unref (markerlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_move_marker)
+{
+  GESMarkerList *markerlist;
+  GESMarker *marker_a, *marker_b;
+  GstClockTime position;
+  GList *range;
+
+  ges_init ();
+
+  markerlist = ges_marker_list_new ();
+
+  marker_a = ges_marker_list_add (markerlist, 10);
+  marker_b = ges_marker_list_add (markerlist, 30);
+
+  fail_unless (ges_marker_list_move (markerlist, marker_a, 20));
+
+  g_object_get (marker_a, "position", &position, NULL);
+  fail_unless_equals_int (position, 20);
+
+  range = ges_marker_list_get_markers (markerlist);
+
+  fail_unless (g_list_index (range, marker_a) == 0);
+  fail_unless (g_list_index (range, marker_b) == 1);
+
+  g_list_foreach (range, (GFunc) gst_object_unref, NULL);
+  g_list_free (range);
+
+  fail_unless (ges_marker_list_move (markerlist, marker_a, 35));
+
+  range = ges_marker_list_get_markers (markerlist);
+
+  fail_unless (g_list_index (range, marker_b) == 0);
+  fail_unless (g_list_index (range, marker_a) == 1);
+
+  g_list_foreach (range, (GFunc) gst_object_unref, NULL);
+  g_list_free (range);
+
+  fail_unless (ges_marker_list_move (markerlist, marker_a, 30));
+
+  g_object_get (marker_a, "position", &position, NULL);
+  fail_unless_equals_int (position, 30);
+
+  g_object_get (marker_b, "position", &position, NULL);
+  fail_unless_equals_int (position, 30);
+
+  fail_unless_equals_int (ges_marker_list_size (markerlist), 2);
+
+  ges_marker_list_remove (markerlist, marker_a);
+
+  fail_unless (!ges_marker_list_move (markerlist, marker_a, 20));
+
+  g_object_unref (markerlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_serialize_deserialize_in_timeline)
+{
+  GESMarkerList *markerlist1, *markerlist2;
+  gchar *metas1, *metas2;
+  GESTimeline *timeline1, *timeline2;
+
+  ges_init ();
+
+  timeline1 = ges_timeline_new_audio_video ();
+
+  markerlist1 = ges_marker_list_new ();
+  ges_marker_list_add (markerlist1, 0);
+  ges_marker_list_add (markerlist1, 10);
+
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1);
+
+  fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER
+          (timeline1), "ges-test", markerlist1));
+
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "GstStructure + local ref", 2);
+
+  markerlist2 =
+      ges_meta_container_get_marker_list (GES_META_CONTAINER (timeline1),
+      "ges-test");
+
+  fail_unless (markerlist1 == markerlist2);
+
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "GstStructure + getter + local ref", 3);
+
+  g_object_unref (markerlist2);
+
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "GstStructure + local ref", 2);
+
+  timeline2 = ges_timeline_new_audio_video ();
+
+  metas1 = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline1));
+  ges_meta_container_add_metas_from_string (GES_META_CONTAINER (timeline2),
+      metas1);
+  metas2 = ges_meta_container_metas_to_string (GES_META_CONTAINER (timeline2));
+
+  fail_unless_equals_string (metas1, metas2);
+
+  g_free (metas1);
+  g_free (metas2);
+
+  fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER
+          (timeline1), "ges-test", NULL));
+
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1);
+
+  g_object_unref (markerlist1);
+  g_object_unref (timeline1);
+  g_object_unref (timeline2);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_serialize_deserialize_in_value)
+{
+  GESMarkerList *markerlist1, *markerlist2;
+  GESMarker *marker;
+  gchar *serialized, *cmp;
+  const gchar *str_val;
+  guint uint_val;
+  const gchar *test_string = "test \" string";
+  GList *markers;
+  guint64 position;
+  GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT;
+  GESMarkerFlags flags;
+
+  ges_init ();
+
+  g_value_init (&val1, GES_TYPE_MARKER_LIST);
+  g_value_init (&val2, GES_TYPE_MARKER_LIST);
+
+  markerlist1 = ges_marker_list_new ();
+  g_object_set (markerlist1, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL);
+  marker = ges_marker_list_add (markerlist1, 0);
+  fail_unless (ges_meta_container_set_string (GES_META_CONTAINER (marker),
+          "str-val", test_string));
+  marker = ges_marker_list_add (markerlist1, 10);
+  fail_unless (ges_meta_container_set_string (GES_META_CONTAINER (marker),
+          "first", test_string));
+  fail_unless (ges_meta_container_set_uint (GES_META_CONTAINER (marker),
+          "second", 43));
+
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1);
+
+  g_value_set_instance (&val1, markerlist1);
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "GValue + local ref", 2);
+
+  serialized = gst_value_serialize (&val1);
+  fail_unless (serialized != NULL);
+  GST_DEBUG ("serialized to %s", serialized);
+  fail_unless (gst_value_deserialize (&val2, serialized));
+  cmp = gst_value_serialize (&val2);
+  fail_unless_equals_string (cmp, serialized);
+
+  markerlist2 = GES_MARKER_LIST (g_value_get_object (&val2));
+  ASSERT_OBJECT_REFCOUNT (markerlist2, "GValue", 1);
+
+  g_object_get (markerlist2, "flags", &flags, NULL);
+  fail_unless (flags == GES_MARKER_FLAG_SNAPPABLE);
+
+  fail_unless_equals_int (ges_marker_list_size (markerlist2), 2);
+  markers = ges_marker_list_get_markers (markerlist2);
+  marker = GES_MARKER (markers->data);
+  fail_unless (marker != NULL);
+
+  g_object_get (marker, "position", &position, NULL);
+  fail_unless_equals_uint64 (position, 0);
+  str_val =
+      ges_meta_container_get_string (GES_META_CONTAINER (marker), "str-val");
+  fail_unless_equals_string (str_val, test_string);
+
+  marker = GES_MARKER (markers->next->data);
+  fail_unless (marker != NULL);
+  fail_unless (markers->next->next == NULL);
+
+  g_object_get (marker, "position", &position, NULL);
+  fail_unless_equals_uint64 (position, 10);
+  str_val =
+      ges_meta_container_get_string (GES_META_CONTAINER (marker), "first");
+  fail_unless_equals_string (str_val, test_string);
+  fail_unless (ges_meta_container_get_uint (GES_META_CONTAINER (marker),
+          "second", &uint_val));
+  fail_unless_equals_int (uint_val, 43);
+
+  g_list_free_full (markers, g_object_unref);
+  g_value_unset (&val1);
+  g_value_unset (&val2);
+  ASSERT_OBJECT_REFCOUNT (markerlist1, "local ref", 1);
+  g_object_unref (markerlist1);
+  g_free (serialized);
+  g_free (cmp);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_marker_color)
+{
+  GESMarkerList *mlist;
+  GESMarker *marker;
+  const guint yellow_rgb = 16776960;
+  guint color;
+
+  ges_init ();
+
+  mlist = ges_marker_list_new ();
+  marker = ges_marker_list_add (mlist, 0);
+  /* getting the color should fail since no value should be set yet */
+  fail_unless (ges_meta_container_get_meta (GES_META_CONTAINER (marker),
+          GES_META_MARKER_COLOR) == NULL);
+  /* trying to set the color field to something other than a uint should
+   * fail */
+  fail_unless (ges_meta_container_set_float (GES_META_CONTAINER (marker),
+          GES_META_MARKER_COLOR, 0.0) == FALSE);
+  fail_unless (ges_meta_container_set_uint (GES_META_CONTAINER (marker),
+          GES_META_MARKER_COLOR, yellow_rgb));
+  fail_unless (ges_meta_container_get_uint (GES_META_CONTAINER (marker),
+          GES_META_MARKER_COLOR, &color));
+  fail_unless_equals_int (color, yellow_rgb);
+
+  g_object_unref (mlist);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+static Suite *
+ges_suite (void)
+{
+  Suite *s = suite_create ("ges-marker-list");
+  TCase *tc_chain = tcase_create ("markerlist");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, test_add);
+  tcase_add_test (tc_chain, test_remove);
+  tcase_add_test (tc_chain, test_signal_marker_added);
+  tcase_add_test (tc_chain, test_signal_marker_removed);
+  tcase_add_test (tc_chain, test_signal_marker_moved);
+  tcase_add_test (tc_chain, test_get_markers);
+  tcase_add_test (tc_chain, test_move_marker);
+  tcase_add_test (tc_chain, test_serialize_deserialize_in_timeline);
+  tcase_add_test (tc_chain, test_serialize_deserialize_in_value);
+  tcase_add_test (tc_chain, test_marker_color);
+
+  return s;
+}
+
+GST_CHECK_MAIN (ges);
index a9b041b..b169e41 100644 (file)
@@ -79,10 +79,11 @@ GST_START_TEST (test_overlay_properties)
   /* Check that trackelement has the same properties */
   assert_equals_uint64 (_START (trackelement), 42);
   assert_equals_uint64 (_DURATION (trackelement), 51);
-  assert_equals_uint64 (_INPOINT (trackelement), 12);
+  /* in-point is 0 since it does not have has-internal-source */
+  assert_equals_uint64 (_INPOINT (trackelement), 0);
 
   /* And let's also check that it propagated correctly to GNonLin */
-  nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12,
+  nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 0,
       51, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE);
 
   /* Change more properties, see if they propagate */
@@ -94,11 +95,11 @@ GST_START_TEST (test_overlay_properties)
   assert_equals_uint64 (_INPOINT (clip), 120);
   assert_equals_uint64 (_START (trackelement), 420);
   assert_equals_uint64 (_DURATION (trackelement), 510);
-  assert_equals_uint64 (_INPOINT (trackelement), 120);
+  assert_equals_uint64 (_INPOINT (trackelement), 0);
 
   /* And let's also check that it propagated correctly to GNonLin */
   nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510,
-      120, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE);
+      0, 510, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 0, TRUE);
 
   ges_container_remove (GES_CONTAINER (clip),
       GES_TIMELINE_ELEMENT (trackelement));
index 44b26bc..d5d7c73 100644 (file)
@@ -342,6 +342,8 @@ _add_properties (GESTimeline * timeline)
                 (source), 5 * GST_SECOND, 0.);
             gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
                 (source), 10 * GST_SECOND, 1.);
+
+            gst_object_unref (source);
           } else if (GES_IS_VIDEO_SOURCE (element)) {
             /* Adding children properties */
             gint64 posx = 42;
@@ -386,7 +388,7 @@ _check_properties (GESTimeline * timeline)
           if (GES_IS_EFFECT (element)) {
             GstControlBinding *binding;
             GstControlSource *source;
-            GList *timed_values;
+            GList *timed_values, *tmpvalue;
             GstTimedValue *value;
 
             binding =
@@ -397,20 +399,22 @@ _check_properties (GESTimeline * timeline)
             fail_unless (source != NULL);
 
             /* Now check keyframe position */
-            timed_values =
+            tmpvalue = timed_values =
                 gst_timed_value_control_source_get_all
                 (GST_TIMED_VALUE_CONTROL_SOURCE (source));
-            value = timed_values->data;
+            value = tmpvalue->data;
             fail_unless (value->value == 0.);
             fail_unless (value->timestamp == 0 * GST_SECOND);
-            timed_values = timed_values->next;
-            value = timed_values->data;
+            tmpvalue = tmpvalue->next;
+            value = tmpvalue->data;
             fail_unless (value->value == 0.);
             fail_unless (value->timestamp == 5 * GST_SECOND);
-            timed_values = timed_values->next;
-            value = timed_values->data;
+            tmpvalue = tmpvalue->next;
+            value = tmpvalue->data;
             fail_unless (value->value == 1.);
             fail_unless (value->timestamp == 10 * GST_SECOND);
+            g_list_free (timed_values);
+            gst_object_unref (source);
           }
           /* Checking children properties */
           else if (GES_IS_VIDEO_SOURCE (element)) {
index 0021040..4cc776e 100644 (file)
@@ -30,8 +30,6 @@ GST_START_TEST (test_tempochange)
   GESEffect *effect;
   GESTestClip *clip;
   GESClip *clip2, *clip3;
-  GList *tmp;
-  int found;
 
   ges_init ();
 
@@ -78,49 +76,6 @@ GST_START_TEST (test_tempochange)
   fail_unless_equals_int64 (_INPOINT (clip3), 7.5 * GST_SECOND);
   fail_unless_equals_int64 (_DURATION (clip3), 3 * GST_SECOND);
 
-#define MDF_OF_CLIP(x) \
-  ((NleObject *) ges_track_element_get_nleobject \
-    (ges_clip_find_track_element (GES_CLIP (x), track_audio, \
-    G_TYPE_NONE)))->media_duration_factor
-
-  fail_unless_equals_float (MDF_OF_CLIP (clip), 1.0);
-  fail_unless_equals_float (MDF_OF_CLIP (clip2), 1.5);
-  fail_unless_equals_float (MDF_OF_CLIP (clip3), 1.5);
-
-  found = 0;
-
-  for (tmp = GES_CONTAINER_CHILDREN (clip2); tmp; tmp = tmp->next) {
-    // A clip may have children other than the effect we added. As such, we
-    // need to find the child which is the pitch effect in order to check its
-    // value.
-    GstElement *nle = ges_track_element_get_nleobject (tmp->data);
-    if (strncmp (GST_OBJECT_NAME (nle), "GESEffect:", 10) == 0) {
-      fail_if (found);
-      found = 1;
-      fail_unless_equals_float (((NleObject *) nle)->media_duration_factor,
-          1.5);
-    }
-  }
-
-  fail_unless (found);
-
-  found = 0;
-  for (tmp = GES_CONTAINER_CHILDREN (clip3); tmp; tmp = tmp->next) {
-    GstElement *nle = ges_track_element_get_nleobject (tmp->data);
-    if (strncmp (GST_OBJECT_NAME (nle), "GESEffect:", 10) == 0) {
-      fail_if (found);
-      found = 1;
-      fail_unless_equals_float (((NleObject *) nle)->media_duration_factor,
-          1.5);
-    }
-  }
-
-  fail_unless (found);
-
-  ges_layer_remove_clip (layer, (GESClip *) clip);
-  ges_layer_remove_clip (layer, clip2);
-  ges_layer_remove_clip (layer, clip3);
-
   gst_object_unref (timeline);
 
   ges_deinit ();
@@ -132,11 +87,11 @@ static Suite *
 ges_suite (void)
 {
   Suite *s = suite_create ("ges");
-  TCase *tc_chain = tcase_create ("tempochange");
   GstPluginFeature *pitch = gst_registry_find_feature (gst_registry_get (),
       "pitch", GST_TYPE_ELEMENT_FACTORY);
 
   if (pitch) {
+    TCase *tc_chain = tcase_create ("tempochange");
     gst_object_unref (pitch);
 
     suite_add_tcase (s, tc_chain);
index bfb299b..de38ae3 100644 (file)
@@ -31,20 +31,7 @@ typedef struct _DestroyedObjectStruct
 gchar *
 ges_test_get_audio_only_uri (void)
 {
-  gchar *uri;
-  GFile *cfile, *fdir, *f_audio_only;
-
-  cfile = g_file_new_for_path (__FILE__);
-  fdir = g_file_get_parent (cfile);
-
-  f_audio_only = g_file_get_child (fdir, "audio_only.ogg");
-  uri = g_file_get_uri (f_audio_only);
-
-  gst_object_unref (cfile);
-  gst_object_unref (fdir);
-  gst_object_unref (f_audio_only);
-
-  return uri;
+  return ges_test_file_uri ("audio_only.ogg");
 }
 
 gchar *
@@ -80,9 +67,10 @@ ges_test_create_pipeline (GESTimeline * timeline)
   pipeline = ges_pipeline_new ();
   fail_unless (ges_pipeline_set_timeline (pipeline, timeline));
 
-  g_object_set (pipeline, "audio-sink", gst_element_factory_make ("fakesink",
-          "test-audiofakesink"), "video-sink",
-      gst_element_factory_make ("fakesink", "test-videofakesink"), NULL);
+  g_object_set (pipeline, "audio-sink",
+      gst_element_factory_make ("fakeaudiosink", "test-audiofakesink"),
+      "video-sink", gst_element_factory_make ("fakevideosink",
+          "test-videofakesink"), NULL);
 
   return pipeline;
 }
@@ -277,38 +265,68 @@ print_timeline (GESTimeline * timeline)
 {
   GList *layer, *clip, *clips, *group;
 
-  g_printerr
+  gst_printerr
       ("\n\n=========================== GESTimeline: %p ==================\n",
       timeline);
   for (layer = timeline->layers; layer; layer = layer->next) {
     clips = ges_layer_get_clips (layer->data);
 
-    g_printerr ("layer %04d: ", ges_layer_get_priority (layer->data));
+    gst_printerr ("layer %04d: ", ges_layer_get_priority (layer->data));
     for (clip = clips; clip; clip = clip->next) {
-      g_printerr ("{ %s [ %" G_GUINT64_FORMAT "(%" G_GUINT64_FORMAT ") %"
+      gst_printerr ("{ %s [ %" G_GUINT64_FORMAT "(%" G_GUINT64_FORMAT ") %"
           G_GUINT64_FORMAT "] } ", GES_TIMELINE_ELEMENT_NAME (clip->data),
           GES_TIMELINE_ELEMENT_START (clip->data),
           GES_TIMELINE_ELEMENT_INPOINT (clip->data),
           GES_TIMELINE_ELEMENT_END (clip->data));
     }
     if (layer->next)
-      g_printerr ("\n--------------------------------------------------\n");
+      gst_printerr ("\n--------------------------------------------------\n");
 
     g_list_free_full (clips, gst_object_unref);
   }
 
   if (ges_timeline_get_groups (timeline)) {
-    g_printerr ("\n--------------------------------------------------\n");
-    g_printerr ("\nGROUPS:");
-    g_printerr ("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
+    gst_printerr ("\n--------------------------------------------------\n");
+    gst_printerr ("\nGROUPS:");
+    gst_printerr ("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
   }
 
   for (group = ges_timeline_get_groups (timeline); group; group = group->next) {
-    g_printerr ("%" GES_FORMAT ": ", GES_ARGS (group->data));
+    gst_printerr ("%" GES_FORMAT ": ", GES_ARGS (group->data));
     for (clip = GES_CONTAINER_CHILDREN (group->data); clip; clip = clip->next)
-      g_printerr ("[ %s ]", GES_TIMELINE_ELEMENT_NAME (clip->data));
+      gst_printerr ("[ %s ]", GES_TIMELINE_ELEMENT_NAME (clip->data));
   }
 
-  g_printerr
+  gst_printerr
       ("\n=====================================================================\n");
 }
+
+/* append the properties found in element to list, num_props should point
+ * to the current list length.
+ */
+GParamSpec **
+append_children_properties (GParamSpec ** list, GESTimelineElement * element,
+    guint * num_props)
+{
+  guint i, num;
+  GParamSpec **props =
+      ges_timeline_element_list_children_properties (element, &num);
+  fail_unless (props);
+  list = g_realloc_n (list, num + *num_props, sizeof (GParamSpec *));
+
+  for (i = 0; i < num; i++)
+    list[*num_props + i] = props[i];
+
+  g_free (props);
+  *num_props += num;
+  return list;
+}
+
+void
+free_children_properties (GParamSpec ** list, guint num_props)
+{
+  guint i;
+  for (i = 0; i < num_props; i++)
+    g_param_spec_unref (list[i]);
+  g_free (list);
+}
index f7382c0..85ee03a 100644 (file)
  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  */
-
-#ifndef _GES_TEST_UTILS
-#define _GES_TEST_UTILS
+#pragma once
 
 #include <ges/ges.h>
 #include <gst/check/gstcheck.h>
+#include "../../../ges/ges-internal.h"
 
 GESPipeline * ges_test_create_pipeline (GESTimeline *timeline);
 /*  The first 2 NLE priorities are used for:
@@ -50,6 +49,11 @@ ges_generate_test_file_audio_video (const gchar * filedest,
 gboolean
 play_timeline (GESTimeline * timeline);
 
+GParamSpec **
+append_children_properties (GParamSpec ** list, GESTimelineElement * element, guint * num_props);
+void
+free_children_properties (GParamSpec ** list, guint num_props);
+
 #define nle_object_check(nleobj, start, duration, mstart, mduration, priority, active) { \
   guint64 pstart, pdur, inpoint, pprio, pact;                  \
   g_object_get (nleobj, "start", &pstart, "duration", &pdur,           \
@@ -82,6 +86,7 @@ G_STMT_START {                                          \
 #define _START(obj) GES_TIMELINE_ELEMENT_START (obj)
 #define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj)
 #define _INPOINT(obj) GES_TIMELINE_ELEMENT_INPOINT (obj)
+#define _MAX_DURATION(obj) GES_TIMELINE_ELEMENT_MAX_DURATION (obj)
 #define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj)
 #ifndef _END
 #define _END(obj) (_START(obj) + _DURATION(obj))
@@ -93,28 +98,319 @@ G_STMT_START {                                          \
   fail_unless (_DURATION (obj) == duration, "%s duration is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_DURATION(obj)), GST_TIME_ARGS (duration));\
 }
 
+#define CHECK_OBJECT_PROPS_MAX(obj, start, inpoint, duration, max_duration) {\
+  CHECK_OBJECT_PROPS (obj, start, inpoint, duration); \
+  fail_unless (_MAX_DURATION(obj) == max_duration, "%s max-duration is " \
+      "%" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, \
+      GES_TIMELINE_ELEMENT_NAME(obj), \
+      GST_TIME_ARGS (_MAX_DURATION(obj)), GST_TIME_ARGS (max_duration)); \
+}
+
+#define __assert_timeline_element_set(obj, prop, val) \
+  fail_unless (ges_timeline_element_set_ ## prop ( \
+        GES_TIMELINE_ELEMENT (obj), val), "Could not set the " # prop \
+        " of " #obj "(%s) to " #val, GES_TIMELINE_ELEMENT_NAME (obj))
+
+#define __fail_timeline_element_set(obj, prop, val) \
+  fail_if (ges_timeline_element_set_ ## prop ( \
+        GES_TIMELINE_ELEMENT (obj), val), "Setting the " # prop \
+        " of " #obj "(%s) to " #val " did not fail as expected", \
+        GES_TIMELINE_ELEMENT_NAME (obj))
+
+
+#define assert_set_start(obj, val) \
+  __assert_timeline_element_set (obj, start, val)
+
+#define assert_set_duration(obj, val) \
+  __assert_timeline_element_set (obj, duration, val)
+
+#define assert_set_inpoint(obj, val) \
+  __assert_timeline_element_set (obj, inpoint, val)
+
+#define assert_set_max_duration(obj, val) \
+  __assert_timeline_element_set (obj, max_duration, val)
+
+
+#define assert_fail_set_start(obj, val) \
+  __fail_timeline_element_set (obj, start, val)
+
+#define assert_fail_set_duration(obj, val) \
+  __fail_timeline_element_set (obj, duration, val)
+
+#define assert_fail_set_inpoint(obj, val) \
+  __fail_timeline_element_set (obj, inpoint, val)
+
+#define assert_fail_set_max_duration(obj, val) \
+  __fail_timeline_element_set (obj, max_duration, val)
+
+
+#define assert_num_in_track(track, val) \
+{ \
+  GList *tmp = ges_track_get_elements (track); \
+  guint length = g_list_length (tmp); \
+  fail_unless (length == val, "Track %" GST_PTR_FORMAT \
+      " contains %u track elements, rather than %u", track, length, val); \
+  g_list_free_full (tmp, gst_object_unref); \
+}
+
+#define assert_num_children(clip, cmp) \
+{ \
+  guint num_children = g_list_length (GES_CONTAINER_CHILDREN (clip)); \
+  fail_unless (cmp == num_children, \
+      "clip %s contains %u children rather than %u", \
+      GES_TIMELINE_ELEMENT_NAME (clip), num_children, cmp); \
+}
+
+/* assert that the time property (start, duration or in-point) is the
+ * same as @cmp for the clip and all its children */
+#define assert_clip_children_time_val(clip, property, cmp) \
+{ \
+  GList *tmp; \
+  GstClockTime read_val; \
+  gchar *name = GES_TIMELINE_ELEMENT (clip)->name; \
+  gboolean is_inpoint = (g_strcmp0 (property, "in-point") == 0); \
+  g_object_get (clip, property, &read_val, NULL); \
+  fail_unless (read_val == cmp, "The %s property for clip %s is %" \
+      GST_TIME_FORMAT ", rather than the expected value of %" \
+      GST_TIME_FORMAT, property, name, GST_TIME_ARGS (read_val), \
+      GST_TIME_ARGS (cmp)); \
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp != NULL; \
+      tmp = tmp->next) { \
+    GESTimelineElement *child = tmp->data; \
+    g_object_get (child, property, &read_val, NULL); \
+    if (!is_inpoint || ges_track_element_has_internal_source ( \
+          GES_TRACK_ELEMENT (child))) \
+      fail_unless (read_val == cmp, "The %s property for the child %s " \
+          "of clip %s is %" GST_TIME_FORMAT ", rather than the expected" \
+          " value of %" GST_TIME_FORMAT, property, child->name, name, \
+          GST_TIME_ARGS (read_val), GST_TIME_ARGS (cmp)); \
+    else \
+      fail_unless (read_val == 0, "The %s property for the child %s " \
+          "of clip %s is %" GST_TIME_FORMAT ", rather than 0", \
+          property, child->name, name, GST_TIME_ARGS (read_val)); \
+  } \
+}
+
 #define check_layer(clip, layer_prio) {                                      \
   fail_unless (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip) ==  (layer_prio),  \
     "%s in layer %d instead of %d", GES_TIMELINE_ELEMENT_NAME (clip),        \
     GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio);                 \
 }
 
-#define GES_TIMELINE_ELEMENT_FORMAT \
-    "s<%p>" \
-    " [ %" GST_TIME_FORMAT \
-    " (%" GST_TIME_FORMAT \
-    ") - %" GST_TIME_FORMAT "(%" GST_TIME_FORMAT") layer: %" G_GINT32_FORMAT "] "
-
-#define GES_TIMELINE_ELEMENT_ARGS(element) \
-    GES_TIMELINE_ELEMENT_NAME(element), element, \
-    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_START(element)), \
-    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_INPOINT(element)), \
-    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)), \
-    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_MAX_DURATION(element)), \
-    GES_TIMELINE_ELEMENT_LAYER_PRIORITY(element)
-#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
-#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
+#define assert_layer(clip, layer) \
+{ \
+  GESLayer *tmp_layer = ges_clip_get_layer (GES_CLIP (clip)); \
+  fail_unless (tmp_layer == GES_LAYER (layer), "clip %s belongs to " \
+      "layer %u (timeline %" GST_PTR_FORMAT ") rather than layer %u " \
+      "(timeline %" GST_PTR_FORMAT ")", \
+      tmp_layer ? ges_layer_get_priority (tmp_layer) : 0, \
+      tmp_layer ? tmp_layer->timeline : NULL, \
+      layer ? ges_layer_get_priority (GES_LAYER (layer)) : 0, \
+      layer ? GES_LAYER (layer)->timeline : NULL); \
+  if (tmp_layer) \
+    gst_object_unref (tmp_layer); \
+  if (layer) { \
+    GList *layer_clips = ges_layer_get_clips (GES_LAYER (layer)); \
+    fail_unless (g_list_find (layer_clips, clip), "clip %s not found " \
+        "in layer %u (timeline %" GST_PTR_FORMAT ")", \
+        ges_layer_get_priority (GES_LAYER (layer)), layer->timeline); \
+    g_list_free_full (layer_clips, gst_object_unref); \
+  } \
+}
 
-void print_timeline(GESTimeline *timeline);
+/* test that the two property lists contain the same properties the same
+ * number of times */
+#define assert_property_list_match(list1, len1, list2, len2) \
+  { \
+    gboolean *found_count_in_list2; \
+    guint *count_list1; \
+    guint i, j; \
+    found_count_in_list2 = g_new0 (gboolean, len1); \
+    count_list1 = g_new0 (guint, len1); \
+    for (i = 0; i < len1; i++) { \
+      found_count_in_list2[i] = 0; \
+      count_list1[i] = 0; \
+      for (j = 0; j < len1; j++) { \
+        if (list1[i] == list1[j]) \
+          count_list1[i] ++; \
+      } \
+    } \
+    for (j = 0; j < len2; j++) { \
+      guint count_list2 = 0; \
+      guint found_count_in_list1 = 0; \
+      GParamSpec *prop = list2[j]; \
+      for (i = 0; i < len2; i++) { \
+        if (list2[i] == prop) \
+          count_list2 ++; \
+      } \
+      for (i = 0; i < len1; i++) { \
+        if (list1[i] == prop) { \
+          found_count_in_list2[i] ++; \
+          found_count_in_list1 ++; \
+        } \
+      } \
+      fail_unless (found_count_in_list1 == count_list2, \
+          "Found property '%s' %u times, rather than %u times, in " #list1, \
+          prop->name, found_count_in_list1, count_list2); \
+    } \
+    /* make sure we found each one once */ \
+    for (i = 0; i < len1; i++) { \
+      GParamSpec *prop = list1[i]; \
+      fail_unless (found_count_in_list2[i] == count_list1[i], \
+          "Found property '%s' %u times, rather than %u times, in " #list2, \
+          prop->name, found_count_in_list2[i], count_list1[i]); \
+    } \
+    g_free (found_count_in_list2); \
+    g_free (count_list1); \
+  }
+
+#define assert_equal_children_properties(el1, el2) \
+{ \
+  guint i, num1, num2; \
+  const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \
+  const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \
+  GParamSpec **el_props1 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el1), &num1); \
+  GParamSpec **el_props2 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el2), &num2); \
+  assert_property_list_match (el_props1, num1, el_props2, num2); \
+  \
+  for (i = 0; i < num1; i++) { \
+    gchar *ser1, *ser2; \
+    GParamSpec *prop = el_props1[i]; \
+    GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT; \
+    /* name property can be different */ \
+    if (g_strcmp0 (prop->name, "name") == 0) \
+      continue; \
+    if (g_strcmp0 (prop->name, "parent") == 0) \
+      continue; \
+    g_value_init (&val1, prop->value_type); \
+    g_value_init (&val2, prop->value_type); \
+    ges_timeline_element_get_child_property_by_pspec ( \
+        GES_TIMELINE_ELEMENT (el1), prop, &val1); \
+    ges_timeline_element_get_child_property_by_pspec ( \
+        GES_TIMELINE_ELEMENT (el2), prop, &val2); \
+    ser1 = gst_value_serialize (&val1); \
+    ser2 = gst_value_serialize (&val2); \
+    fail_unless (gst_value_compare (&val1, &val2) == GST_VALUE_EQUAL, \
+        "Child property '%s' for %s does not match that for %s (%s vs %s)", \
+        prop->name, name1, name2, ser1, ser2); \
+    g_free (ser1); \
+    g_free (ser2); \
+    g_value_unset (&val1); \
+    g_value_unset (&val2); \
+  } \
+  free_children_properties (el_props1, num1); \
+  free_children_properties (el_props2, num2); \
+}
+
+#define assert_equal_bindings(el1, el2) \
+{ \
+  guint i, num1, num2; \
+  const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \
+  const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \
+  GParamSpec **props1 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el1), &num1); \
+  GParamSpec **props2 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el2), &num2); \
+  assert_property_list_match (props1, num1, props2, num2); \
+  \
+  for (i = 0; i < num1; i++) { \
+    const gchar *prop = props1[i]->name; \
+    GList *tmp1, *tmp2; \
+    GList *timed_vals1, *timed_vals2; \
+    GObject *object1, *object2; \
+    gboolean abs1, abs2; \
+    GstControlSource *source1, *source2; \
+    GstInterpolationMode mode1, mode2; \
+    GstControlBinding *binding1, *binding2; \
+    guint j; \
+    \
+    binding1 = ges_track_element_get_control_binding ( \
+        GES_TRACK_ELEMENT (el1), prop); \
+    binding2 = ges_track_element_get_control_binding ( \
+        GES_TRACK_ELEMENT (el2), prop); \
+    if (binding1 == NULL) { \
+      fail_unless (binding2 == NULL, "%s has a binding for property " \
+          " '%s', whilst %s does not", name2, prop, name1); \
+      continue; \
+    } \
+    if (binding2 == NULL) { \
+      fail_unless (binding1 == NULL, "%s has a binding for property " \
+          "'%s', whilst %s does not", name1, prop, name2); \
+      continue; \
+    } \
+    \
+    fail_unless (G_OBJECT_TYPE (binding1) == GST_TYPE_DIRECT_CONTROL_BINDING, \
+        "%s binding for property '%s' is not a direct control binding, " \
+        "so cannot be handled", prop, name1); \
+    fail_unless (G_OBJECT_TYPE (binding2) == GST_TYPE_DIRECT_CONTROL_BINDING, \
+        "%s binding for property '%s' is not a direct control binding, " \
+        "so cannot be handled", prop, name2); \
+    \
+    g_object_get (G_OBJECT (binding1), "control-source", &source1, \
+        "absolute", &abs1, "object", &object1, NULL); \
+    g_object_get (G_OBJECT (binding2), "control-source", &source2, \
+        "absolute", &abs2, "object", &object2, NULL); \
+    \
+    fail_unless (G_OBJECT_TYPE (object1) == G_OBJECT_TYPE (object2), \
+        "The child object for property '%s' for %s and %s correspond " \
+        "to different object types (%s vs %s)", prop, name1, name2, \
+        G_OBJECT_TYPE_NAME (object1), G_OBJECT_TYPE_NAME (object2)); \
+    gst_object_unref (object1); \
+    gst_object_unref (object2); \
+    \
+    fail_unless (abs1 == abs2, "control biding for property '%s' " \
+        " is %s absolute for %s, but %s absolute for %s", prop, \
+        abs1 ? "" : "not", name1, abs2 ? "" : "not", name2); \
+    \
+    fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source1), \
+        "%s does not have an interpolation control source for " \
+        "property '%s', so cannot be handled", name1, prop); \
+    fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source2), \
+        "%s does not have an interpolation control source for " \
+        "property '%s', so cannot be handled", name2, prop); \
+    g_object_get (G_OBJECT (source1), "mode", &mode1, NULL); \
+    g_object_get (G_OBJECT (source2), "mode", &mode2, NULL); \
+    fail_unless (mode1 == mode2, "control source for property '%s' " \
+        "has different modes for %s and %s (%i vs %i)", prop, \
+        name1, name2, mode1, mode2); \
+    \
+    timed_vals1 = gst_timed_value_control_source_get_all ( \
+      GST_TIMED_VALUE_CONTROL_SOURCE (source1)); \
+    timed_vals2 = gst_timed_value_control_source_get_all ( \
+      GST_TIMED_VALUE_CONTROL_SOURCE (source2)); \
+    \
+    for (j = 0, tmp1 = timed_vals1, tmp2 = timed_vals2; tmp1 && tmp2; \
+        j++, tmp1 = tmp1->next, tmp2 = tmp2->next) { \
+      GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \
+      fail_unless (val1->timestamp == val2->timestamp && \
+          val1->value == val2->value, "The %uth timed value for property " \
+          "'%s' is different for %s and %s: (%" G_GUINT64_FORMAT ": %g) vs " \
+          "(%" G_GUINT64_FORMAT ": %g)", j, prop, name1, name2, \
+          val1->timestamp, val1->value, val2->timestamp, val2->value); \
+    } \
+    fail_unless (tmp1 == NULL, "Found too many timed values for " \
+        "property '%s' for %s", prop, name1); \
+    fail_unless (tmp2 == NULL, "Found too many timed values for " \
+        "property '%s' for %s", prop, name2); \
+    \
+    g_list_free (timed_vals1); \
+    g_list_free (timed_vals2); \
+    gst_object_unref (source1); \
+    gst_object_unref (source2); \
+  } \
+  free_children_properties (props1, num1); \
+  free_children_properties (props2, num2); \
+}
+
+#define assert_GESError(error, error_code) \
+{ \
+  fail_unless (error); \
+  fail_unless (error->domain == GES_ERROR); \
+  assert_equals_int (error->code, error_code); \
+  g_error_free (error); \
+  error = NULL; \
+}
 
-#endif /* _GES_TEST_UTILS */
+void print_timeline(GESTimeline *timeline);
index 3cab417..2d4e5dd 100644 (file)
@@ -376,8 +376,6 @@ GST_START_TEST (test_snapping)
    */
   fail_unless (ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip2),
           5));
-  fail_unless (ges_timeline_element_roll_start (GES_TIMELINE_ELEMENT (clip2),
-          60));
   DEEP_CHECK (clip, 30, 5, 32);
   DEEP_CHECK (clip1, 20, 0, 10);
   DEEP_CHECK (clip2, 62, 5, 60);
@@ -400,7 +398,7 @@ GST_START_TEST (test_snapping)
    *                        30-------+0-------------+
    * inpoints               5  clip  ||  clip2      |-------------+
    *                        +------- 62 -----------122  clip1     |
-   * time                                           +------------132 
+   * time                                           +------------132
    * Check that clip1 snaps with the end of clip2 */
   fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
           GES_EDGE_NONE, 125) == TRUE);
@@ -912,9 +910,6 @@ GST_START_TEST (test_timeline_edition_mode)
   CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47);
   CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50);
 
-  fail_if (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_ROLL,
-          GES_EDGE_START, 59));
-
   ges_deinit ();
 }
 
@@ -1010,8 +1005,6 @@ GST_START_TEST (test_groups)
 
   fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
           GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 40) == TRUE);
-  fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
-          GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 30) == TRUE);
   CHECK_CLIP (c, 10, 0, 10, 1);
   CHECK_CLIP (c1, 20, 0, 10, 2);
   CHECK_CLIP (c2, 30, 0, 10, 2);
@@ -1146,252 +1139,137 @@ GST_START_TEST (test_snapping_groups)
 
 GST_END_TEST;
 
-static void
-_set_track_element_width_height (GESTrackElement * trksrc, gint wvalue,
-    gint hvalue)
-{
-  GValue width = { 0 };
-  GValue height = { 0 };
-
-  g_value_init (&width, G_TYPE_INT);
-  g_value_init (&height, G_TYPE_INT);
-  g_value_set_int (&width, wvalue);
-  g_value_set_int (&height, hvalue);
-  if (wvalue >= 0)
-    ges_timeline_element_set_child_property (GES_TIMELINE_ELEMENT (trksrc),
-        "width", &width);
-  if (hvalue >= 0)
-    ges_timeline_element_set_child_property (GES_TIMELINE_ELEMENT (trksrc),
-        "height", &height);
-}
-
-static gboolean
-check_frame_positioner_size (GESClip * clip, gint width, gint height)
-{
-  GESTrackElement *trksrc;
-  GValue val_width = { 0 };
-  GValue val_height = { 0 };
-  gint real_width, real_height;
-
-  trksrc = GES_CONTAINER_CHILDREN (clip)->data;
-  if (!GES_IS_VIDEO_SOURCE (trksrc))
-    return FALSE;
-
-  g_value_init (&val_width, G_TYPE_INT);
-  g_value_init (&val_height, G_TYPE_INT);
-
-  ges_timeline_element_get_child_property (GES_TIMELINE_ELEMENT (trksrc),
-      "width", &val_width);
-  ges_timeline_element_get_child_property (GES_TIMELINE_ELEMENT (trksrc),
-      "height", &val_height);
-
-  real_width = g_value_get_int (&val_width);
-  real_height = g_value_get_int (&val_height);
-
-  assert_equals_int (real_width, width);
-  assert_equals_int (real_height, height);
-
-  return (width == real_width && height == real_height);
-}
-
-GST_START_TEST (test_scaling)
+GST_START_TEST (test_marker_snapping)
 {
+  GESTrack *track;
   GESTimeline *timeline;
+  GESTrackElement *trackelement1, *trackelement2;
+  GESContainer *clip1, *clip2;
   GESLayer *layer;
-  GESAsset *asset1, *asset2;
-  GESClip *clip;
-  GESTrack *trackv;
-  GstCaps *caps;
+  GList *trackelements;
+  GESMarkerList *marker_list1, *marker_list2;
 
   ges_init ();
 
-  trackv = GES_TRACK (ges_video_track_new ());
-  caps =
-      gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, 1200, "height",
-      G_TYPE_INT, 1000, NULL);
+  track = GES_TRACK (ges_video_track_new ());
+  fail_unless (track != NULL);
 
   timeline = ges_timeline_new ();
-  ges_timeline_add_track (timeline, trackv);
-  layer = ges_layer_new ();
-  fail_unless (ges_timeline_add_layer (timeline, layer));
-
-  g_object_set (layer, "auto-transition", TRUE, NULL);
-
-  asset1 = GES_ASSET (ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL));
-  asset2 = GES_ASSET (ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL));
-
-  fail_unless (asset1 != NULL && asset2 != NULL);
-
-  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (timeline),
-      GST_DEBUG_GRAPH_SHOW_ALL, "ges-integration-timeline");
-
-  ges_track_set_restriction_caps (trackv, caps);
-  gst_caps_unref (caps);
-
-  GST_DEBUG ("adding clip, should be 1200 x 1000");
-  clip =
-      ges_layer_add_asset (layer, GES_ASSET (asset1), 0 * GST_SECOND,
-      0 * GST_SECOND, 4 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
-  gst_object_unref (asset1);
-  gst_object_unref (asset2);
-  g_object_set (clip, "vpattern", (gint) GES_VIDEO_TEST_PATTERN_SMPTE75, NULL);
-
-  /**
-   * Our track: 1200 x 1000
-   *
-   * 0--------------0
-   * | width : 1200 |
-   * | height: 1000 |
-   * 0--------------2
-   */
-
-  /* clip takes the size set on the track as a default */
-  fail_unless (check_frame_positioner_size (clip, 1200, 1000));
-
-  if (GES_IS_VIDEO_SOURCE (GES_CONTAINER_CHILDREN (clip)->data))
-    _set_track_element_width_height (GES_CONTAINER_CHILDREN (clip)->data, 1024,
-        768);
-
-  GST_DEBUG ("Setting clip size, should be 1024 x 768");
-
-  /**
-   * Our timeline : 1200 x 1000
-   *
-   * 0--------------0
-   * | width : 1024 |
-   * | height: 768  |
-   * 0--------------2
-   */
+  fail_unless (timeline != NULL);
 
-  /* Clip has to comply to direct orders */
-  fail_unless (check_frame_positioner_size (clip, 1024, 768));
+  fail_unless (ges_timeline_add_track (timeline, track));
 
-  GST_DEBUG ("Changing caps, should still be 1024 x 768");
+  clip1 = GES_CONTAINER (ges_test_clip_new ());
+  clip2 = GES_CONTAINER (ges_test_clip_new ());
 
-  caps =
-      gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, 1400, "height",
-      G_TYPE_INT, 1200, NULL);
-  ges_track_set_restriction_caps (trackv, caps);
-  gst_caps_unref (caps);
+  fail_unless (clip1 && clip2);
 
   /**
-   * Our timeline : 1400 x 1200
+   * Our timeline
+   * ------------
+   *               30
+   * markers  -----|----------------
+   *          |  clip1  ||  clip2  |
+   * time    20 ------- 50 ------ 110
    *
-   * 0--------------0
-   * | width : 1024 |
-   * | height: 768  |
-   * 0--------------2
    */
+  g_object_set (clip1, "start", (guint64) 20, "duration", (guint64) 30,
+      "in-point", (guint64) 0, NULL);
+  g_object_set (clip2, "start", (guint64) 50, "duration", (guint64) 60,
+      "in-point", (guint64) 0, NULL);
 
-  /* Clip still has to be the same size */
-  fail_unless (check_frame_positioner_size (clip, 1024, 768));
-
-  GST_DEBUG ("Setting width to 0, should be 1400 x 768");
-
-  /* -1 means don't set it, only valid here */
-  if (GES_IS_VIDEO_SOURCE (GES_CONTAINER_CHILDREN (clip)->data))
-    _set_track_element_width_height (GES_CONTAINER_CHILDREN (clip)->data, 0,
-        -1);
-
-  /**
-   * Our timeline : 1400 x 1200
-   *
-   * 0--------------0
-   * | width : 1400 |
-   * | height: 768  |
-   * 0--------------2
-   */
+  fail_unless ((layer = ges_timeline_append_layer (timeline)) != NULL);
+  assert_equals_int (ges_layer_get_priority (layer), 0);
 
-  /* Clip width was set to 0 so it has to use track width */
-  /* Clip height is still directly set by the user */
-  fail_unless (check_frame_positioner_size (clip, 1400, 768));
+  fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip1)));
+  fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip1)) != NULL);
+  fail_unless ((trackelement1 =
+          GES_TRACK_ELEMENT (trackelements->data)) != NULL);
+  fail_unless (ges_track_element_get_track (trackelement1) == track);
 
-  GST_DEBUG ("Setting height to 0, should be 1400 x 1200");
+  fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip2)));
+  fail_unless ((trackelements = GES_CONTAINER_CHILDREN (clip2)) != NULL);
+  fail_unless ((trackelement2 =
+          GES_TRACK_ELEMENT (trackelements->data)) != NULL);
+  fail_unless (ges_track_element_get_track (trackelement2) == track);
 
-  if (GES_IS_VIDEO_SOURCE (GES_CONTAINER_CHILDREN (clip)->data))
-    _set_track_element_width_height (GES_CONTAINER_CHILDREN (clip)->data, -1,
-        0);
+  marker_list1 = ges_marker_list_new ();
+  g_object_set (marker_list1, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL);
+  ges_marker_list_add (marker_list1, 10);
+  ges_marker_list_add (marker_list1, 20);
+  fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER
+          (trackelement1), "ges-test", marker_list1));
 
   /**
-   * Our timeline : 1400 x 1200
-   *
-   * 0--------------0
-   * | width : 1400 |
-   * | height: 1200 |
-   * 0--------------2
+   * Snapping clip2 to a marker on clip1
+   * ------------
+   *               30 40
+   * markers  -----|--|--
+   *          |  clip1  |
+   * time    20 ------ 50
+   *              -----------
+   *              |  clip2  |
+   *             30 ------ 90
    */
-
-  /* Clip width still has to use track width */
-  /* Clip height was set to 0 so it has to use track height */
-  fail_unless (check_frame_positioner_size (clip, 1400, 1200));
-
-  GST_DEBUG ("Removing restriction on track height, should be 1400 x 240");
-
-  caps =
-      gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, 1400, "height",
-      G_TYPE_INT, 0, NULL);
-  ges_track_set_restriction_caps (trackv, caps);
-  gst_caps_unref (caps);
+  g_object_set (timeline, "snapping-distance", (guint64) 3, NULL);
+  /* Move within 2 units of marker timestamp */
+  fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL,
+          GES_EDGE_NONE, 32) == TRUE);
+  /* Clip nr. 2 should snap to marker at timestamp 30 */
+  DEEP_CHECK (clip1, 20, 0, 30);
+  DEEP_CHECK (clip2, 30, 0, 60);
 
   /**
-   * Our timeline : 1400 x no restriction
-   *
-   * 0--------------0
-   * | width : 1400 |
-   * | height: 240  |
-   * 0--------------2
+   * Snapping clip1 to a marker on clip2
+   * ------------
+   *                           90
+   * markers                 --|--------
+   *                         |  clip1  |
+   * time                   80 ------ 110
+   * markers      ----------|--
+   *              |   clip2   |
+   *             30 -------- 90
    */
+  marker_list2 = ges_marker_list_new ();
+  g_object_set (marker_list2, "flags", GES_MARKER_FLAG_SNAPPABLE, NULL);
+  ges_marker_list_add (marker_list2, 40);
+  ges_marker_list_add (marker_list2, 50);
+  fail_unless (ges_meta_container_set_marker_list (GES_META_CONTAINER
+          (trackelement2), "ges-test", marker_list2));
 
-  /* Clip width still has to use track width */
-  /* Clip height was set to 0 so it has to use natural clip height */
-  fail_unless (check_frame_positioner_size (clip, 1400, 0));
-
-  GST_DEBUG ("Removing restriction on track width, should be 320 x 240");
-
-  caps = gst_caps_new_empty_simple ("video/x-raw");
-  ges_track_set_restriction_caps (trackv, caps);
-  gst_caps_unref (caps);
+  fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
+          GES_EDGE_NONE, 77) == TRUE);
+  DEEP_CHECK (clip1, 80, 0, 30);
+  DEEP_CHECK (clip2, 30, 0, 60);
 
   /**
-   * Our timeline : no restriction x no restriction
-   *
-   * 0--------------0
-   * | width : 320  |
-   * | height: 240  |
-   * 0--------------2
+   * Checking if clip's own markers are properly ignored when snapping
+   * (moving clip1 close to where one of its markers is)
+   * ------------
+   *                     100     112     122
+   * markers              |     --|-------|--
+   *                old m.pos.  |   clip1   |
+   * time                      102 ------- 132
    */
-
-  /* Clip width was set to 0 so it has to use natural clip width */
-  /* Clip height was set to 0 so it has to use natural clip height */
-  fail_unless (check_frame_positioner_size (clip, 0, 0));
-
+  fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
+          GES_EDGE_NONE, 102) == TRUE);
+  DEEP_CHECK (clip1, 102, 0, 30);
+  DEEP_CHECK (clip2, 30, 0, 60);
 
   /**
-   * Our timeline : 320 * 240
-   *
-   * 0--------------0
-   * | width : 320  |
-   * | height: 240  |
-   * 0--------------2
+   * Checking if non-snappable marker lists are correctly ignored.
+   * (moving clip1 close to clip2's non-snappable marker)
    */
-
-  /* We set the restriction caps video size to the same as the video source
-   * size. */
-  caps = gst_caps_from_string ("video/x-raw,height=240,width=320");
-  ges_track_set_restriction_caps (trackv, caps);
-  gst_caps_unref (caps);
-  _set_track_element_width_height (GES_CONTAINER_CHILDREN (clip)->data, 320,
-      240);
-
-  /* The video source has the same size as the track restriction caps but we
-   * are changing the aspect ratio, the video should thus not be rescaled. */
-  caps = gst_caps_from_string ("video/x-raw,height=1080,width=1920");
-  ges_track_set_restriction_caps (trackv, caps);
-  gst_caps_unref (caps);
-  fail_unless (check_frame_positioner_size (clip, 320, 240));
-
-  gst_object_unref (timeline);
-
+  g_object_set (marker_list2, "flags", GES_MARKER_FLAG_NONE, NULL);
+  fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
+          GES_EDGE_NONE, 82) == TRUE);
+  DEEP_CHECK (clip1, 82, 0, 30);
+  DEEP_CHECK (clip2, 30, 0, 60);
+
+  g_object_unref (marker_list1);
+  g_object_unref (marker_list2);
+  check_destroyed (G_OBJECT (timeline), G_OBJECT (trackelement1),
+      trackelement2, clip1, clip2, layer, marker_list1, marker_list2, NULL);
   ges_deinit ();
 }
 
@@ -1411,7 +1289,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_simple_triming);
   tcase_add_test (tc_chain, test_groups);
   tcase_add_test (tc_chain, test_snapping_groups);
-  tcase_add_test (tc_chain, test_scaling);
+  tcase_add_test (tc_chain, test_marker_snapping);
 
   return s;
 }
index b9bf4a6..e5b19df 100644 (file)
@@ -65,7 +65,10 @@ GST_START_TEST (test_title_source_properties)
       "in-point", (guint64) 12, NULL);
   assert_equals_uint64 (_START (clip), 42);
   assert_equals_uint64 (_DURATION (clip), 51);
-  assert_equals_uint64 (_INPOINT (clip), 0);
+  /* the clip can have a non-zero in-point, but this won't affect any
+   * of the core children because they have their has-internal-source set
+   * to FALSE */
+  assert_equals_uint64 (_INPOINT (clip), 12);
 
   ges_layer_add_clip (layer, GES_CLIP (clip));
   ges_timeline_commit (timeline);
@@ -91,7 +94,7 @@ GST_START_TEST (test_title_source_properties)
   ges_timeline_commit (timeline);
   assert_equals_uint64 (_START (clip), 420);
   assert_equals_uint64 (_DURATION (clip), 510);
-  assert_equals_uint64 (_INPOINT (clip), 0);
+  assert_equals_uint64 (_INPOINT (clip), 120);
   assert_equals_uint64 (_START (trackelement), 420);
   assert_equals_uint64 (_DURATION (trackelement), 510);
   assert_equals_uint64 (_INPOINT (trackelement), 0);
index 9c826d4..16b2880 100644 (file)
@@ -109,10 +109,11 @@ GST_START_TEST (test_transition_properties)
   /* Check that trackelement has the same properties */
   assert_equals_uint64 (_START (trackelement), 42);
   assert_equals_uint64 (_DURATION (trackelement), 51);
-  assert_equals_uint64 (_INPOINT (trackelement), 12);
+  /* in-point is 0 since it does not have has-internal-source */
+  assert_equals_uint64 (_INPOINT (trackelement), 0);
 
   /* And let's also check that it propagated correctly to GNonLin */
-  nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 12,
+  nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 51, 0,
       51, MIN_NLE_PRIO, TRUE);
 
   /* Change more properties, see if they propagate */
@@ -124,11 +125,11 @@ GST_START_TEST (test_transition_properties)
   assert_equals_uint64 (_INPOINT (clip), 120);
   assert_equals_uint64 (_START (trackelement), 420);
   assert_equals_uint64 (_DURATION (trackelement), 510);
-  assert_equals_uint64 (_INPOINT (trackelement), 120);
+  assert_equals_uint64 (_INPOINT (trackelement), 0);
 
   /* And let's also check that it propagated correctly to GNonLin */
   nle_object_check (ges_track_element_get_nleobject (trackelement), 420, 510,
-      120, 510, MIN_NLE_PRIO + 0, TRUE);
+      0, 510, MIN_NLE_PRIO + 0, TRUE);
 
   /* test changing vtype */
   GST_DEBUG ("Setting to crossfade");
index 3eb9dfe..2298089 100644 (file)
@@ -260,7 +260,7 @@ GST_START_TEST (test_filesource_images)
   fail_unless (GES_TIMELINE_ELEMENT_PARENT (track_element) ==
       GES_TIMELINE_ELEMENT (clip));
   fail_unless (ges_track_element_get_track (track_element) == v);
-  fail_unless (GES_IS_IMAGE_SOURCE (track_element));
+  fail_unless (GES_IS_VIDEO_URI_SOURCE (track_element));
 
   ASSERT_OBJECT_REFCOUNT (track_element, "1 in track, 1 in clip 2 in timeline",
       3);
index 6147992..4b5c398 100644 (file)
@@ -17,6 +17,7 @@ ges_tests = [
     ['ges/track'],
     ['ges/tempochange'],
     ['ges/negative'],
+    ['ges/markerlist'],
     ['nle/simple'],
     ['nle/complex'],
     ['nle/nleoperation'],
@@ -27,7 +28,7 @@ ges_tests = [
 test_defines = [
   '-UG_DISABLE_ASSERT',
   '-UG_DISABLE_CAST_CHECKS',
-  '-DGES_TEST_FILES_PATH="' + meson.current_source_dir() + '/ges/"',
+  '-DGES_TEST_FILES_PATH="@0@"'.format(join_paths(meson.current_source_dir(), 'assets')),
   '-DGST_CHECK_TEST_ENVIRONMENT_BEACON="GST_STATE_IGNORE_ELEMENTS"',
   '-DTESTFILE="' + meson.current_source_dir() + '/meson.build"',
   '-DGST_USE_UNSTABLE_API',
@@ -41,7 +42,11 @@ if gst_dep.type_name() == 'pkgconfig'
   pluginsdirs = [gst_dep.get_pkgconfig_variable('pluginsdir'),
                  pbase.get_pkgconfig_variable('pluginsdir'),
                  pbad.get_pkgconfig_variable('pluginsdir')]
+ gst_plugin_scanner_dir = gst_dep.get_pkgconfig_variable('pluginscannerdir')
+else
+ gst_plugin_scanner_dir = subproject('gstreamer').get_variable('gst_scanner_dir')
 endif
+gst_plugin_scanner_path = join_paths(gst_plugin_scanner_dir, 'gst-plugin-scanner')
 
 foreach t : ges_tests
   fname = '@0@.c'.format(t.get(0))
@@ -70,8 +75,48 @@ foreach t : ges_tests
   endif
 endforeach
 
+if gstvalidate_dep.found()
+  # filename: is .validatetest
+  scenarios = {
+    'check_video_track_restriction_scale': false,
+    'check_video_track_restriction_scale_with_keyframes': false,
+    'check_edit_in_frames': false,
+    'check_edit_in_frames_with_framerate_mismatch': false,
+    'check_layer_activness_gaps': false,
+    'seek_with_stop': true,
+    'seek_with_stop.check_clock_sync': true,
+    'edit_while_seeked_with_stop': true,
+    'complex_effect_bin_desc': true,
+    'check_keyframes_in_compositor_two_sources': true,
+    'check-clip-positioning': true,
+    'set-layer-on-command-line': true,
+  }
+
+  foreach scenario, is_validatetest: scenarios
+
+    env = environment()
+    env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '')
+    env.set('GST_STATE_IGNORE_ELEMENTS', '')
+    env.set('CK_DEFAULT_TIMEOUT', '20')
+    env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), 'scenarios'))
+    env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs)
+    env.set('GST_VALIDATE_LOGSDIR', meson.current_build_dir() / scenario)
+    env.set('GST_PLUGIN_SCANNER_1_0', gst_plugin_scanner_path)
+
+    if is_validatetest
+      testfile = meson.current_source_dir() / 'scenarios' / scenario + '.validatetest'
+      test(scenario, ges_launch, env: env, args: ['--no-interactive', '--set-test-file', testfile, '--mute'])
+    else
+      scenario_file = meson.current_source_dir() / 'scenarios' / scenario + '.scenario'
+      test(scenario, ges_launch, env: env, args: ['--no-interactive', '--set-scenario', scenario_file])
+    endif
+
+  endforeach
+  test('simple_playback_test', ges_launch, env: env, args: ['+test-clip', 'blue', 'd=0.1', '--videosink=fakevideosink', '--audiosink=fakeaudiosink'])
+endif
+
 if build_gir
-  # Make sure to use the subproject gst-validate-launcher if avalaible.
+  # Make sure to use the subproject gst-validate-launcher if available.
   if gstvalidate_dep.found() and gstvalidate_dep.type_name() == 'internal'
     runtests = subproject('gst-devtools').get_variable('launcher')
   else
@@ -79,6 +124,14 @@ if build_gir
   endif
 
   if runtests.found()
+    env = environment()
+    env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '')
+    env.set('GST_STATE_IGNORE_ELEMENTS', '')
+    env.set('CK_DEFAULT_TIMEOUT', '20')
+    env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), 'scenarios'))
+    env.set('GST_PLUGIN_PATH_1_0', [meson.build_root()] + pluginsdirs)
+    env.set('GI_TYPELIB_PATH', meson.current_build_dir() / '..' / '..' / 'ges')
+
     test('pythontests', runtests, args: ['--pyunittest-dir', meson.current_source_dir(), 'pyunittest', '--dump-on-failure'],
          env: env)
   endif
index 86ab9df..b7358fb 100644 (file)
@@ -13,7 +13,7 @@ fill_pipeline_and_check (GstElement * comp, GList * segments,
   GList *listcopy = copy_segment_list (segments);
 
   pipeline = gst_pipeline_new ("test_pipeline");
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
@@ -562,12 +562,12 @@ GST_START_TEST (test_renegotiation)
   ASSERT_OBJECT_REFCOUNT (source3, "source3", 1);
 
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakeaudiosink", "sink");
   audioconvert = gst_element_factory_make_or_warn ("audioconvert", "aconv");
 
   gst_bin_add_many (GST_BIN (pipeline), comp, audioconvert, sink, NULL);
   caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE");
-  gst_element_link_filtered (audioconvert, sink, caps);
+  fail_unless (gst_element_link_filtered (audioconvert, sink, caps));
   gst_caps_unref (caps);
 
   /* Shared data */
index eb60e47..3607ddb 100644 (file)
@@ -53,7 +53,7 @@ GST_START_TEST (test_change_object_start_stop_in_current_stack)
 
   gst_element_set_state (comp, GST_STATE_READY);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
 
   gst_element_link (comp, sink);
@@ -217,7 +217,7 @@ GST_START_TEST (test_remove_last_object)
 
   gst_element_set_state (GST_ELEMENT (composition), GST_STATE_READY);
 
-  fakesink = gst_element_factory_make ("fakesink", NULL);
+  fakesink = gst_element_factory_make ("fakeaudiosink", NULL);
   gst_bin_add_many (GST_BIN (pipeline), GST_ELEMENT (composition), fakesink,
       NULL);
   gst_element_link (GST_ELEMENT (composition), fakesink);
@@ -308,7 +308,7 @@ GST_START_TEST (test_dispose_on_commit)
 
   composition = gst_element_factory_make ("nlecomposition", "composition");
   pipeline = GST_ELEMENT (gst_pipeline_new (NULL));
-  fakesink = gst_element_factory_make ("fakesink", NULL);
+  fakesink = gst_element_factory_make ("fakevideosink", NULL);
 
   nlesource = gst_element_factory_make ("nlesource", "nlesource1");
   audiotestsrc = gst_element_factory_make ("audiotestsrc", "audiotestsrc1");
@@ -352,7 +352,7 @@ GST_START_TEST (test_simple_audiomixer)
 
   composition = gst_element_factory_make ("nlecomposition", "composition");
   gst_element_set_state (composition, GST_STATE_READY);
-  fakesink = gst_element_factory_make ("fakesink", NULL);
+  fakesink = gst_element_factory_make ("fakeaudiosink", NULL);
 
   /* nle_audiomixer */
   nle_audiomixer = gst_element_factory_make ("nleoperation", "nle_audiomixer");
@@ -438,6 +438,316 @@ GST_START_TEST (test_simple_audiomixer)
 
 GST_END_TEST;
 
+static GstElement *
+create_nested_source (gint nesting_depth)
+{
+  gint i;
+  GstElement *source, *nested_comp, *bin;
+
+  source = videotest_nle_src ("source", 0, 2 * GST_SECOND, 2, 2);
+  for (i = 0; i < nesting_depth; i++) {
+    gchar *name = g_strdup_printf ("nested_comp%d", i);
+    gchar *desc = g_strdup_printf ("nlecomposition name=%s ! queue", name);
+    bin = gst_parse_bin_from_description (desc, TRUE, NULL);
+    nested_comp = gst_bin_get_by_name (GST_BIN (bin), name);
+    g_free (name);
+    g_free (desc);
+
+    nle_composition_add (GST_BIN (nested_comp), source);
+    gst_object_unref (nested_comp);
+
+    name = g_strdup_printf ("nested_src%d", i);
+    source = gst_element_factory_make_or_warn ("nlesource", name);
+    g_free (name);
+    g_object_set (source, "start", 0, "duration", 2 * GST_SECOND, NULL);
+    gst_bin_add (GST_BIN (source), bin);
+  }
+
+
+  return source;
+}
+
+GST_START_TEST (test_seek_on_nested)
+{
+  GstBus *bus;
+  GstPad *srcpad;
+  GstElement *pipeline, *comp, *nested_source, *sink;
+  GstMessage *message;
+  gboolean carry_on, ret = FALSE;
+  GstClockTime position;
+
+  ges_init ();
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp = gst_element_factory_make_or_warn ("nlecomposition", NULL);
+
+  gst_element_set_state (comp, GST_STATE_READY);
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  gst_element_link (comp, sink);
+
+  nested_source = create_nested_source (1);
+  srcpad = gst_element_get_static_pad (nested_source, "src");
+  gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
+      (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL);
+  gst_object_unref (srcpad);
+
+  /* Add nested source */
+  nle_composition_add (GST_BIN (comp), nested_source);
+  commit_and_wait (comp, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  carry_on = TRUE;
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_ASYNC_DONE:
+        {
+          carry_on = FALSE;
+          GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
+          break;
+        }
+        case GST_MESSAGE_EOS:
+        {
+          GST_WARNING ("Saw EOS");
+
+          fail_if (TRUE);
+        }
+        case GST_MESSAGE_ERROR:
+          GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline),
+              GST_DEBUG_GRAPH_SHOW_ALL, "error");
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  gst_element_seek_simple (pipeline,
+      GST_FORMAT_TIME,
+      GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, 1 * GST_SECOND);
+
+  message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
+      GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
+  gst_mini_object_unref (GST_MINI_OBJECT (message));
+
+  ret =
+      gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
+      (gint64 *) & position);
+  fail_unless_equals_uint64 (position, 1 * GST_SECOND);
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Resetted pipeline to NULL");
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+  gst_check_objects_destroyed_on_unref (pipeline, comp, nested_source, NULL);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_error_in_nested_timeline)
+{
+  GstBus *bus;
+  GstPad *srcpad;
+  GstElement *pipeline, *comp, *nested_source, *sink;
+  GstMessage *message;
+  gboolean carry_on, ret = FALSE;
+
+  ges_init ();
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp = gst_element_factory_make_or_warn ("nlecomposition", NULL);
+
+  gst_element_set_state (comp, GST_STATE_READY);
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  gst_element_link (comp, sink);
+
+  nested_source = create_nested_source (1);
+  srcpad = gst_element_get_static_pad (nested_source, "src");
+  gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
+      (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL);
+  gst_object_unref (srcpad);
+
+  /* Add nested source */
+  nle_composition_add (GST_BIN (comp), nested_source);
+  commit_and_wait (comp, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  carry_on = TRUE;
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_ASYNC_DONE:
+        {
+          carry_on = FALSE;
+          GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
+          break;
+        }
+        case GST_MESSAGE_EOS:
+        {
+          GST_WARNING ("Saw EOS");
+
+          fail_if (TRUE);
+        }
+        case GST_MESSAGE_ERROR:
+          GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline),
+              GST_DEBUG_GRAPH_SHOW_ALL, "error");
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_ELEMENT_ERROR (nested_source, STREAM, FAILED,
+      ("Faking an error message"), ("Nothing"));
+
+  message =
+      gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR);
+  gst_mini_object_unref (GST_MINI_OBJECT (message));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Resetted pipeline to NULL");
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_nest_deep)
+{
+  GstBus *bus;
+  GstPad *srcpad;
+  GstElement *pipeline, *comp, *nested_source, *sink;
+  GstMessage *message;
+  gboolean carry_on, ret = FALSE;
+  GstClockTime position;
+
+  ges_init ();
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp = gst_element_factory_make_or_warn ("nlecomposition", NULL);
+
+  gst_element_set_state (comp, GST_STATE_READY);
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  gst_element_link (comp, sink);
+
+  nested_source = create_nested_source (2);
+  srcpad = gst_element_get_static_pad (nested_source, "src");
+  gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
+      (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL);
+  gst_object_unref (srcpad);
+
+  /* Add nested source */
+  nle_composition_add (GST_BIN (comp), nested_source);
+  commit_and_wait (comp, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  carry_on = TRUE;
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline),
+        GST_DEBUG_GRAPH_SHOW_ALL, "nothing");
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_ASYNC_DONE:
+        {
+          carry_on = FALSE;
+          GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
+          break;
+        }
+        case GST_MESSAGE_EOS:
+        {
+          GST_WARNING ("Saw EOS");
+
+          fail_if (TRUE);
+        }
+        case GST_MESSAGE_ERROR:
+          GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipeline),
+              GST_DEBUG_GRAPH_SHOW_ALL, "error");
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  gst_element_seek_simple (pipeline,
+      GST_FORMAT_TIME,
+      GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, 1 * GST_SECOND);
+
+  message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
+      GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
+  gst_mini_object_unref (GST_MINI_OBJECT (message));
+
+  ret =
+      gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
+      (gint64 *) & position);
+  fail_unless_equals_uint64 (position, 1 * GST_SECOND);
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Resetted pipeline to NULL");
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_check_objects_destroyed_on_unref (pipeline, comp, nested_source, NULL);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
+
+
 static Suite *
 gnonlin_suite (void)
 {
@@ -449,6 +759,9 @@ gnonlin_suite (void)
   tcase_add_test (tc_chain, test_change_object_start_stop_in_current_stack);
   tcase_add_test (tc_chain, test_remove_invalid_object);
   tcase_add_test (tc_chain, test_remove_last_object);
+  tcase_add_test (tc_chain, test_seek_on_nested);
+  tcase_add_test (tc_chain, test_error_in_nested_timeline);
+  tcase_add_test (tc_chain, test_nest_deep);
 
   tcase_add_test (tc_chain, test_dispose_on_commit);
 
index 8a425e6..2e8be8b 100644 (file)
@@ -12,7 +12,7 @@ fill_pipeline_and_check (GstElement * comp, GList * segments)
   GList *listcopy = copy_segment_list (segments);
 
   pipeline = gst_pipeline_new ("test_pipeline");
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
index 102fed8..72aee07 100644 (file)
@@ -24,7 +24,7 @@ GST_START_TEST (test_simple_videotestsrc)
   check_start_stop_duration (nlesource, 1 * GST_SECOND, 2 * GST_SECOND,
       1 * GST_SECOND);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), nlesource, sink, NULL);
@@ -124,7 +124,7 @@ GST_START_TEST (test_videotestsrc_in_bin)
   if (nlesource == NULL)
     return;
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), nlesource, sink, NULL);
index 53d3e17..8bd530c 100644 (file)
@@ -35,7 +35,7 @@ fill_pipeline_and_check (GstElement * comp, GList * segments, GList * seeks)
   GList *ltofree = seeks;
 
   pipeline = gst_pipeline_new ("test_pipeline");
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
index 040d929..e6cbfe7 100644 (file)
@@ -39,7 +39,7 @@ test_simplest_full (void)
   check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
   ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
@@ -256,7 +256,7 @@ test_one_after_other_full (void)
 
   ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
@@ -459,7 +459,7 @@ test_one_under_another_full (void)
   check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
   gst_object_unref (source1);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
@@ -613,7 +613,7 @@ test_one_bin_after_other_full (void)
 
   ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  sink = gst_element_factory_make_or_warn ("fakevideosink", "sink");
   fail_if (sink == NULL);
 
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
index f1ccf4e..e3d8208 100644 (file)
 #include "common.h"
 #include "plugins/nle/nleobject.h"
 
-GST_START_TEST (test_tempochange)
+typedef struct _PadEventData
 {
-  GstElement *pipeline;
-  GstElement *comp, *source1, *def, *sink, *oper;
-  GList *segments = NULL;
-  GstBus *bus;
-  GstMessage *message;
-  gboolean carry_on, ret = FALSE;
-  CollectStructure *collect;
-  GstPad *sinkpad;
+  gchar *name;
+  guint expect_num_segments;
+  guint num_segments;
+  GArray *expect_segment_time;
+  GArray *expect_segment_num_seeks;
+  guint expect_num_seeks;
+  guint num_seeks;
+  GArray *expect_seek_start;
+  GArray *expect_seek_stop;
+  GArray *expect_seek_num_segments;
+  guint num_eos;
+  guint expect_num_eos;
+} PadEventData;
+
+#define _SEGMENT_FORMAT "flags: %i, rate: %g, applied_rate: %g, format: %i" \
+  ", base: %" G_GUINT64_FORMAT ", offset: %" G_GUINT64_FORMAT ", start: %" \
+  G_GUINT64_FORMAT ", stop: %" G_GUINT64_FORMAT ", time: %" G_GUINT64_FORMAT \
+  ", position: %" G_GUINT64_FORMAT ", duration: %" G_GUINT64_FORMAT
+
+#define _SEGMENT_ARGS(seg) (seg).flags, (seg).rate, (seg).applied_rate, \
+  (seg).format, (seg).base, (seg).offset, (seg).start, (seg).stop, \
+  (seg).time, (seg).position, (seg).duration
+
+static GstPadProbeReturn
+_test_pad_events (GstPad * pad, GstPadProbeInfo * info, PadEventData * data)
+{
+  guint num;
+  GstEvent *event = info->data;
+  if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
+    guint expect_num_seeks;
+    const GstSegment *segment;
+    /* copy the segment start, stop, position and duration since these are
+     * not yet translated by nleghostpad. Also, don't care about flags. */
+    GstSegment expect_segment;
+
+    gst_event_parse_segment (event, &segment);
+    GST_DEBUG ("%s segment: " _SEGMENT_FORMAT, data->name,
+        _SEGMENT_ARGS (*segment));
+
+    if (!GST_CLOCK_TIME_IS_VALID (segment->stop)) {
+      GST_DEBUG ("%s: ignoring pre-roll segment", data->name);
+      return GST_PAD_PROBE_OK;
+    }
+
+    data->num_segments++;
+    num = data->num_segments;
+
+    fail_unless (num <= data->expect_num_segments, "%s received %u "
+        "segments, more than the expected %u segments", data->name, num,
+        data->expect_num_segments);
+
+    expect_num_seeks =
+        g_array_index (data->expect_segment_num_seeks, gint, num - 1);
+
+    fail_unless (data->num_seeks == expect_num_seeks, "%s has received %u "
+        "segments, compared to %u seeks, but expected %u seeks",
+        data->name, num, data->num_seeks, expect_num_seeks);
+
+    /* copy the segment start, stop, position, duration, offset, base
+     * since these are not yet translated by nleghostpad. */
+    gst_segment_copy_into (segment, &expect_segment);
+    expect_segment.rate = 1.0;
+    expect_segment.applied_rate = 1.0;
+    expect_segment.format = GST_FORMAT_TIME;
+    expect_segment.time = g_array_index (data->expect_segment_time,
+        GstClockTime, num - 1);
+
+    fail_unless (gst_segment_is_equal (segment, &expect_segment),
+        "%s %uth segment is not equal to the expected. Received:\n"
+        _SEGMENT_FORMAT "\nExpected\n" _SEGMENT_FORMAT, data->name,
+        num - 1, _SEGMENT_ARGS (*segment), _SEGMENT_ARGS (expect_segment));
+
+  } else if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
+    gdouble rate;
+    GstFormat format;
+    GstClockTime expect;
+    gint64 start, stop;
+    GstSeekType start_type, stop_type;
+    guint expect_num_segments;
+
+    gst_event_parse_seek (event, &rate, &format, NULL, &start_type, &start,
+        &stop_type, &stop);
+
+    GST_DEBUG ("%s seek: rate: %g, start: %" G_GINT64_FORMAT ", stop: %"
+        G_GINT64_FORMAT, data->name, rate, start, stop);
+
+    data->num_seeks++;
+    num = data->num_seeks;
+
+    fail_unless (num <= data->expect_num_seeks, "%s received %u "
+        "seeks, more than the expected %u seeks", data->name, num,
+        data->expect_num_seeks);
+
+    expect_num_segments =
+        g_array_index (data->expect_seek_num_segments, gint, num - 1);
+
+    fail_unless (data->num_segments == expect_num_segments, "%s has "
+        "received %u seeks, compared to %u segments, but expected %u "
+        "segments", data->name, num, data->num_segments, expect_num_segments);
+
+    fail_unless (rate == 1.0, "%s %uth seek has a rate of %g rather than 1.0",
+        data->name, num - 1, rate);
+    fail_unless (format == GST_FORMAT_TIME, "%s %uth seek has a format of %i "
+        " than a time format", data->name, num - 1, format);
+
+    /* expect seek-set or seek-none */
+    fail_if (start_type == GST_SEEK_TYPE_END, "%s %uth seek-start is "
+        "seek-end", data->name, num - 1);
+    fail_if (stop_type == GST_SEEK_TYPE_END, "%s %uth seek-stop is "
+        "seek-end", data->name, num - 1);
+
+    expect = g_array_index (data->expect_seek_start, GstClockTime, num - 1);
+    fail_unless (start == expect, "%s %uth seek start is %" GST_TIME_FORMAT
+        ", rather than the expected %" GST_TIME_FORMAT, data->name, num - 1,
+        GST_TIME_ARGS (start), GST_TIME_ARGS (expect));
+
+    expect = g_array_index (data->expect_seek_stop, GstClockTime, num - 1);
+    fail_unless (stop == expect, "%s %uth seek stop is %" GST_TIME_FORMAT
+        ", rather than the expected %" GST_TIME_FORMAT, data->name, num - 1,
+        GST_TIME_ARGS (stop), GST_TIME_ARGS (expect));
+
+  } else if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+    data->num_eos++;
+    fail_unless (data->num_eos <= data->expect_num_eos, "%s received %u "
+        "EOS, more than the expected %u EOS", data->name, data->num_eos,
+        data->expect_num_seeks);
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+static void
+_pad_event_data_check_received (PadEventData * data)
+{
+  fail_unless (data->num_eos == data->expect_num_eos, "%s received %u "
+      "EOS, rather than %u", data->num_eos, data->expect_num_eos);
+  fail_unless (data->num_segments == data->expect_num_segments,
+      "%s received %u segments, rather than %u", data->name,
+      data->num_segments, data->expect_num_segments);
+  fail_unless (data->num_seeks == data->expect_num_seeks,
+      "%s received %u seeks, rather than %u", data->name, data->num_seeks,
+      data->expect_num_seeks);
+}
+
+static void
+_pad_event_data_free (PadEventData * data)
+{
+  g_free (data->name);
+  g_array_unref (data->expect_segment_time);
+  g_array_unref (data->expect_seek_start);
+  g_array_unref (data->expect_seek_stop);
+  g_array_unref (data->expect_segment_num_seeks);
+  g_array_unref (data->expect_seek_num_segments);
+  g_free (data);
+}
+
+static PadEventData *
+_pad_event_data_new (GstElement * element, const gchar * pad_name,
+    const gchar * suffix)
+{
+  GstPad *pad;
+  PadEventData *data = g_new0 (PadEventData, 1);
+  data->expect_segment_time = g_array_new (FALSE, FALSE, sizeof (GstClockTime));
+  data->expect_seek_start = g_array_new (FALSE, FALSE, sizeof (GstClockTime));
+  data->expect_seek_stop = g_array_new (FALSE, FALSE, sizeof (GstClockTime));
+  data->expect_seek_num_segments = g_array_new (FALSE, FALSE, sizeof (guint));
+  data->expect_segment_num_seeks = g_array_new (FALSE, FALSE, sizeof (guint));
+  data->name = g_strdup_printf ("%s:%s(%s):%s", G_OBJECT_TYPE_NAME (element),
+      GST_ELEMENT_NAME (element), pad_name, suffix);
+
+  pad = gst_element_get_static_pad (element, pad_name);
+  fail_unless (pad, "%s not found", data->name);
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM |
+      GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, (GstPadProbeCallback) _test_pad_events,
+      data, (GDestroyNotify) _pad_event_data_free);
+  gst_object_unref (pad);
+
+  return data;
+}
+
+static void
+_pad_event_data_add_expect_segment (PadEventData * data, GstClockTime time)
+{
+  data->expect_num_segments++;
+  g_array_append_val (data->expect_segment_time, time);
+  g_array_append_val (data->expect_segment_num_seeks, data->expect_num_seeks);
+}
+
+static void
+_pad_event_data_add_expect_seek (PadEventData * data, GstClockTime start,
+    GstClockTime stop)
+{
+  data->expect_num_seeks++;
+  g_array_append_val (data->expect_seek_start, start);
+  g_array_append_val (data->expect_seek_stop, stop);
+  g_array_append_val (data->expect_seek_num_segments,
+      data->expect_num_segments);
+}
+
+static void
+_pad_event_data_add_expect_seek_then_segment (PadEventData * data,
+    GstClockTime start, GstClockTime stop)
+{
+  _pad_event_data_add_expect_seek (data, start, stop);
+  _pad_event_data_add_expect_segment (data, start);
+}
+
+#define _EXPECT_SEEK_SEGMENT(data, start, stop) \
+  _pad_event_data_add_expect_seek_then_segment (data, start, stop)
+
+static GstElement *
+_get_source (GstElement * nle_source)
+{
+  GList *tmp;
+  GstElement *bin, *src = NULL;
+
+  fail_unless (g_list_length (GST_BIN_CHILDREN (nle_source)), 1);
+  bin = GST_BIN_CHILDREN (nle_source)->data;
+  fail_unless (GST_IS_BIN (bin));
+
+  for (tmp = GST_BIN_CHILDREN (bin); src == NULL && tmp; tmp = tmp->next) {
+    if (g_strrstr (GST_ELEMENT_NAME (tmp->data), "audiotestsrc"))
+      src = tmp->data;
+  }
+  fail_unless (src);
+  return src;
+}
+
+enum
+{
+  NLE_PREV_SRC,
+  NLE_POST_SRC,
+  NLE_SOURCE_SRC,
+  NLE_OPER_SRC,
+  NLE_OPER_SINK,
+  NLE_IDENTITY_SRC,
+  PREV_SRC,
+  POST_SRC,
+  SOURCE_SRC,
+  PITCH_SRC,
+  PITCH_SINK,
+  IDENTITY_SRC,
+  SINK_SINK,
+  NUM_DATA
+};
+
+static PadEventData **
+_setup_test (GstElement * pipeline, gdouble rate)
+{
+  GstElement *sink, *pitch, *src, *prev, *post, *identity;
+  GstElement *comp, *nle_source, *nle_prev, *nle_post, *nle_oper, *nle_identity;
+  gboolean ret;
+  gchar *suffix;
+  PadEventData **data = g_new0 (PadEventData *, NUM_DATA);
 
-  pipeline = gst_pipeline_new ("test_pipeline");
+  /* composition */
   comp =
       gst_element_factory_make_or_warn ("nlecomposition", "test_composition");
-
   gst_element_set_state (comp, GST_STATE_READY);
 
-  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  /* sink */
+  sink = gst_element_factory_make_or_warn ("fakeaudiosink", "sink");
   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
 
   gst_element_link (comp, sink);
 
-  /*
-     source1
-     Start : 0s
-     Duration : 2s
-     Priority : 2
-   */
-
-  source1 = audiotest_bin_src ("source1", 0, 2 * GST_SECOND, 2, 2);
-
-  /*
-     def (default source)
-     Priority = G_MAXUINT32
-   */
-  def =
-      audiotest_bin_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, G_MAXUINT32,
-      1);
-  g_object_set (def, "expandable", TRUE, NULL);
-
-  /* Operation */
-  oper = new_operation ("oper", "identity", 0, 2 * GST_SECOND, 1);
-  fail_if (oper == NULL);
-  ((NleObject *) oper)->media_duration_factor = 2.0;
-
-  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
-  ASSERT_OBJECT_REFCOUNT (def, "default", 1);
-  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
-
-  /* Add source 1 */
-
-  nle_composition_add (GST_BIN (comp), source1);
-  nle_composition_add (GST_BIN (comp), def);
-  nle_composition_add (GST_BIN (comp), oper);
+  /* sources */
+  nle_source =
+      audiotest_bin_src ("nle_source", 3 * GST_SECOND, 4 * GST_SECOND, 3,
+      FALSE);
+  g_object_set (nle_source, "inpoint", 7 * GST_SECOND, NULL);
+  src = _get_source (nle_source);
+  g_object_set (src, "name", "middle-source", NULL);
+
+  nle_prev =
+      audiotest_bin_src ("nle_previous", 0 * GST_SECOND, 3 * GST_SECOND, 2,
+      FALSE);
+  g_object_set (nle_prev, "inpoint", 99 * GST_SECOND, NULL);
+  prev = _get_source (nle_prev);
+  g_object_set (src, "name", "previous-source", NULL);
+
+  nle_post =
+      audiotest_bin_src ("post", 7 * GST_SECOND, 5 * GST_SECOND, 2, FALSE);
+  g_object_set (nle_post, "inpoint", 20 * GST_SECOND, NULL);
+  post = _get_source (nle_post);
+  g_object_set (src, "name", "post-source", NULL);
+
+  /* Operation, must share the same start and duration as the upstream
+   * source */
+  nle_oper =
+      new_operation ("nle_oper", "pitch", 3 * GST_SECOND, 4 * GST_SECOND, 2);
+  fail_unless (g_list_length (GST_BIN_CHILDREN (nle_oper)) == 1);
+  pitch = GST_ELEMENT (GST_BIN_CHILDREN (nle_oper)->data);
+  g_object_set (pitch, "rate", rate, NULL);
+
+  /* cover with an identity operation
+   * rate effect has lower priority, so we don't need the same start or
+   * duration */
+  nle_identity =
+      new_operation ("nle_identity", "identity", 0, 12 * GST_SECOND, 1);
+  g_object_set (nle_identity, "inpoint", 5 * GST_SECOND, NULL);
+  fail_unless (g_list_length (GST_BIN_CHILDREN (nle_oper)) == 1);
+  identity = GST_ELEMENT (GST_BIN_CHILDREN (nle_identity)->data);
+
+  nle_composition_add (GST_BIN (comp), nle_source);
+  nle_composition_add (GST_BIN (comp), nle_prev);
+  nle_composition_add (GST_BIN (comp), nle_post);
+  nle_composition_add (GST_BIN (comp), nle_oper);
+  nle_composition_add (GST_BIN (comp), nle_identity);
+  ret = FALSE;
   commit_and_wait (comp, &ret);
-  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
-  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
-  check_start_stop_duration (oper, 0, 2 * GST_SECOND, 2 * GST_SECOND);
-
-  /* Define expected segments */
-  segments = g_list_append (segments,
-      segment_new (1.0, GST_FORMAT_TIME, 0 * GST_SECOND, 4.0 * GST_SECOND, 0));
-  collect = g_new0 (CollectStructure, 1);
-  collect->comp = comp;
-  collect->sink = sink;
-
-  collect->expected_segments = segments;
-  collect->keep_expected_segments = FALSE;
-
-  sinkpad = gst_element_get_static_pad (sink, "sink");
-  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
-      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
-  gst_object_unref (sinkpad);
-
-  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
-
-  GST_DEBUG ("Setting pipeline to PAUSED");
-  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
-
-  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
-          GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
-
-  GST_DEBUG ("Let's poll the bus");
-
-  carry_on = TRUE;
-  while (carry_on) {
-    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
-    if (message) {
-      switch (GST_MESSAGE_TYPE (message)) {
-        case GST_MESSAGE_ASYNC_DONE:
-        {
-          carry_on = FALSE;
-          GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
-          break;
-        }
-        case GST_MESSAGE_EOS:
-        {
-          GST_WARNING ("Saw EOS");
+  fail_unless (ret);
+
+  check_start_stop_duration (nle_source, 3 * GST_SECOND, 7 * GST_SECOND,
+      4 * GST_SECOND);
+  check_start_stop_duration (nle_oper, 3 * GST_SECOND, 7 * GST_SECOND,
+      4 * GST_SECOND);
+  check_start_stop_duration (nle_prev, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  check_start_stop_duration (nle_post, 7 * GST_SECOND, 12 * GST_SECOND,
+      5 * GST_SECOND);
+  check_start_stop_duration (nle_identity, 0, 12 * GST_SECOND, 12 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 12 * GST_SECOND, 12 * GST_SECOND);
+
+  /* create data */
+  suffix = g_strdup_printf ("rate=%g", rate);
+
+  /* source */
+  data[NLE_SOURCE_SRC] = _pad_event_data_new (nle_source, "src", suffix);
+  data[NLE_PREV_SRC] = _pad_event_data_new (nle_prev, "src", suffix);
+  data[NLE_POST_SRC] = _pad_event_data_new (nle_post, "src", suffix);
+  data[SOURCE_SRC] = _pad_event_data_new (src, "src", suffix);
+  data[PREV_SRC] = _pad_event_data_new (prev, "src", suffix);
+  data[POST_SRC] = _pad_event_data_new (post, "src", suffix);
+
+  /* rate operation */
+  data[NLE_OPER_SRC] = _pad_event_data_new (nle_oper, "src", suffix);
+  data[NLE_OPER_SINK] = _pad_event_data_new (nle_oper, "sink", suffix);
+  data[PITCH_SRC] = _pad_event_data_new (pitch, "src", suffix);
+  data[PITCH_SINK] = _pad_event_data_new (pitch, "sink", suffix);
+
+  /* identity: only care about the source pads */
+  data[NLE_IDENTITY_SRC] = _pad_event_data_new (nle_identity, "src", suffix);
+  data[IDENTITY_SRC] = _pad_event_data_new (identity, "src", suffix);
+
+  /* sink */
+  data[SINK_SINK] = _pad_event_data_new (sink, "sink", suffix);
+
+  g_free (suffix);
+
+  return data;
+}
+
 
-          fail_if (TRUE);
+GST_START_TEST (test_tempochange_play)
+{
+  GstElement *pipeline;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on;
+  PadEventData **data;
+  gdouble rates[3] = { 0.5, 4.0, 1.0 };
+  guint i, j;
+
+  for (i = 0; i < G_N_ELEMENTS (rates); i++) {
+    gdouble rate = rates[i];
+    GST_DEBUG ("rate = %g", rate);
+
+    pipeline = gst_pipeline_new ("test_pipeline");
+
+    data = _setup_test (pipeline, rate);
+
+    /* initial seek */
+    _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 0, 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 0, 3 * GST_SECOND);
+    /* nleobject will convert the seek by removing start and adding inpoint */
+    _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 5 * GST_SECOND, 8 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_PREV_SRC], 0, 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PREV_SRC], 99 * GST_SECOND, 102 * GST_SECOND);
+
+    /* rate-stack seek */
+    _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 3 * GST_SECOND, 7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 3 * GST_SECOND,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 8 * GST_SECOND, 12 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 3 * GST_SECOND, 7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], 0, 4 * GST_SECOND);
+    /* pitch element will change the stop time, e.g. if rate=2.0, then we
+     * want to use up twice as much source, so the stop time doubles */
+    _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], 0, rate * 4 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], 3 * GST_SECOND,
+        (GstClockTime) (rate * 4 * GST_SECOND) + 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], 3 * GST_SECOND,
+        (GstClockTime) (rate * 4 * GST_SECOND) + 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], 7 * GST_SECOND,
+        (GstClockTime) (rate * 4 * GST_SECOND) + 7 * GST_SECOND);
+
+    /* final part only involves post source */
+    _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 7 * GST_SECOND, 12 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 7 * GST_SECOND,
+        12 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 12 * GST_SECOND, 17 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_POST_SRC], 7 * GST_SECOND, 12 * GST_SECOND);
+    /* nleobject will convert the seek by removing start and adding
+     * inpoint */
+    _EXPECT_SEEK_SEGMENT (data[POST_SRC], 20 * GST_SECOND, 25 * GST_SECOND);
+
+    /* expect 1 EOS from each, apart from identity, which will get 3 since
+     * part of 3 stacks */
+    for (j = 0; j < NUM_DATA; j++)
+      data[j]->expect_num_eos = 1;
+
+    data[IDENTITY_SRC]->expect_num_eos = 3;
+    data[NLE_IDENTITY_SRC]->expect_num_eos = 3;
+
+    bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+    GST_DEBUG ("Setting pipeline to PLAYING");
+    fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+            GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+    GST_DEBUG ("Let's poll the bus");
+
+    carry_on = TRUE;
+    while (carry_on) {
+      message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+      if (message) {
+        switch (GST_MESSAGE_TYPE (message)) {
+          case GST_MESSAGE_EOS:
+            if (message->src == GST_OBJECT (pipeline)) {
+              GST_DEBUG ("Setting pipeline to NULL");
+              fail_unless (gst_element_set_state (GST_ELEMENT (pipeline),
+                      GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
+              carry_on = FALSE;
+            }
+            break;
+          case GST_MESSAGE_ERROR:
+            fail_error_message (message);
+            break;
+          default:
+            break;
         }
-        case GST_MESSAGE_ERROR:
-          fail_error_message (message);
-        default:
-          break;
+        gst_message_unref (message);
       }
-      gst_mini_object_unref (GST_MINI_OBJECT (message));
     }
-  }
-
-  fail_unless_equals_float (((NleObject *) source1)->media_duration_factor,
-      1.0f);
-  fail_unless_equals_float (((NleObject *)
-          source1)->recursive_media_duration_factor, 2.0f);
-  fail_unless_equals_float (((NleObject *) oper)->media_duration_factor, 2.0f);
-  fail_unless_equals_float (((NleObject *)
-          oper)->recursive_media_duration_factor, 2.0f);
-
-  GST_DEBUG ("Setting pipeline to READY");
 
-  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
-          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+    for (j = 0; j < NUM_DATA; j++)
+      _pad_event_data_check_received (data[j]);
 
-  fail_if (collect->expected_segments != NULL);
+    ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+    gst_object_unref (pipeline);
+    ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+    gst_object_unref (bus);
+    g_free (data);
+  }
+}
 
-  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
-          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+GST_END_TEST;
 
-  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
-  gst_object_unref (pipeline);
-  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
-  gst_object_unref (bus);
+#define _WAIT_UNTIL_ASYNC_DONE \
+{ \
+  GST_DEBUG ("Let's poll the bus"); \
+  carry_on = TRUE; \
+  while (carry_on) { \
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); \
+    if (message) { \
+      switch (GST_MESSAGE_TYPE (message)) { \
+        case GST_MESSAGE_EOS: \
+          fail_if (TRUE, "Received EOS"); \
+          break; \
+        case GST_MESSAGE_ERROR: \
+          fail_error_message (message); \
+          break; \
+        case GST_MESSAGE_ASYNC_DONE: \
+          carry_on = FALSE; \
+          break; \
+        default: \
+          break; \
+      } \
+      gst_message_unref (message); \
+    } \
+  } \
+}
 
-  collect_free (collect);
+GST_START_TEST (test_tempochange_seek)
+{
+  GstElement *pipeline;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on;
+  PadEventData **data;
+  gdouble rates[3] = { 2.0, 0.25, 1.0 };
+  guint i, j;
+  GstClockTime offset = 0.1 * GST_SECOND;
+
+  for (i = 0; i < G_N_ELEMENTS (rates); i++) {
+    gdouble rate = rates[i];
+    GST_DEBUG ("rate = %g", rate);
+
+    pipeline = gst_pipeline_new ("test_pipeline");
+
+    data = _setup_test (pipeline, rate);
+
+    /* initial seek from the pause */
+    _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 0, 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 0, 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 5 * GST_SECOND, 8 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_PREV_SRC], 0, 3 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PREV_SRC], 99 * GST_SECOND, 102 * GST_SECOND);
+
+    GST_DEBUG ("Setting pipeline to PAUSED");
+    fail_unless (gst_element_set_state (GST_ELEMENT (pipeline),
+            GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC);
+
+    bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+    _WAIT_UNTIL_ASYNC_DONE;
+
+    for (j = 0; j < NUM_DATA; j++)
+      _pad_event_data_check_received (data[j]);
+
+    /* first seek for just after the start of the rate effect */
+    /* NOTE: neither prev nor post should receive anything */
+
+    /* sink will receive two seeks: one that initiates the pre-roll, and
+     * then the seek with the stop set */
+    /* expect no segment for the first seek */
+    _pad_event_data_add_expect_seek (data[SINK_SINK], 3 * GST_SECOND + offset,
+        GST_CLOCK_TIME_NONE);
+    _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 3 * GST_SECOND + offset,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 3 * GST_SECOND + offset,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 8 * GST_SECOND + offset,
+        12 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 3 * GST_SECOND + offset,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], offset, 4 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], rate * offset,
+        rate * 4 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK],
+        3 * GST_SECOND + (GstClockTime) (rate * offset),
+        3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND));
+    _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC],
+        3 * GST_SECOND + (GstClockTime) (rate * offset),
+        3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND));
+    _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC],
+        7 * GST_SECOND + (GstClockTime) (rate * offset),
+        7 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND));
+
+    /* perform seek */
+    fail_unless (gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
+            GST_SEEK_FLAG_FLUSH, 3 * GST_SECOND + offset));
+
+    _WAIT_UNTIL_ASYNC_DONE;
+
+    for (j = 0; j < NUM_DATA; j++)
+      _pad_event_data_check_received (data[j]);
+
+    /* now seek to just before the end */
+    _pad_event_data_add_expect_seek (data[SINK_SINK], 7 * GST_SECOND - offset,
+        GST_CLOCK_TIME_NONE);
+    _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 7 * GST_SECOND - offset,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 7 * GST_SECOND - offset,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 12 * GST_SECOND - offset,
+        12 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 7 * GST_SECOND - offset,
+        7 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], 4 * GST_SECOND - offset,
+        4 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[PITCH_SINK],
+        rate * (4 * GST_SECOND) - rate * offset, rate * 4 * GST_SECOND);
+    _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK],
+        3 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)),
+        3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND));
+    _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC],
+        3 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)),
+        3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND));
+    _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC],
+        7 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)),
+        7 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND));
+
+    /* perform seek */
+    fail_unless (gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
+            GST_SEEK_FLAG_FLUSH, 7 * GST_SECOND - offset));
+
+    _WAIT_UNTIL_ASYNC_DONE;
+
+    for (j = 0; j < NUM_DATA; j++)
+      _pad_event_data_check_received (data[j]);
+
+    GST_DEBUG ("Setting pipeline to NULL");
+    fail_unless (gst_element_set_state (GST_ELEMENT (pipeline),
+            GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
+
+    ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+    gst_object_unref (pipeline);
+    ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+    gst_object_unref (bus);
+    g_free (data);
+  }
 }
 
 GST_END_TEST;
@@ -171,7 +641,13 @@ gnonlin_suite (void)
   ges_init ();
   suite_add_tcase (s, tc_chain);
 
-  tcase_add_test (tc_chain, test_tempochange);
+  /* give the tests a little more time than the default
+   * CK_DEFAULT_TIMEOUT=20, this is sometimes needed for running under
+   * valgrind */
+  tcase_set_timeout (tc_chain, 40.0);
+
+  tcase_add_test (tc_chain, test_tempochange_play);
+  tcase_add_test (tc_chain, test_tempochange_seek);
 
   return s;
 }
index 52d62c8..1593d68 100644 (file)
@@ -26,11 +26,18 @@ gi.require_version("GES", "1.0")
 from gi.repository import Gst  # noqa
 from gi.repository import GES  # noqa
 from gi.repository import GLib  # noqa
+from gi.repository import GObject  # noqa
 import contextlib  # noqa
 import os  #noqa
 import unittest  # noqa
 import tempfile  # noqa
 
+try:
+    gi.require_version("GstTranscoder", "1.0")
+    from gi.repository import GstTranscoder
+except ValueError:
+    GstTranscoder = None
+
 Gst.init(None)
 GES.init()
 
@@ -62,24 +69,27 @@ def create_main_loop():
 
 def create_project(with_group=False, saved=False):
     """Creates a project with two clips in a group."""
-    project = GES.Project.new(None)
-    timeline = project.extract()
+    timeline = GES.Timeline.new_audio_video()
     layer = timeline.append_layer()
 
     if with_group:
         clip1 = GES.TitleClip()
         clip1.set_start(0)
-        clip1.set_duration(10)
+        clip1.set_duration(10*Gst.SECOND)
         layer.add_clip(clip1)
         clip2 = GES.TitleClip()
-        clip2.set_start(100)
-        clip2.set_duration(10)
+        clip2.set_start(100 * Gst.SECOND)
+        clip2.set_duration(10*Gst.SECOND)
         layer.add_clip(clip2)
         group = GES.Container.group([clip1, clip2])
 
     if saved:
-        uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name
-        project.save(timeline, uri, None, overwrite=True)
+        if isinstance(saved, str):
+            suffix = "-%s.xges" % saved
+        else:
+            suffix = ".xges"
+        uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=suffix).name
+        timeline.get_asset().save(timeline, uri, None, overwrite=True)
 
     return timeline
 
@@ -95,6 +105,28 @@ def created_project_file(xges):
     os.remove(xges_path)
 
 
+def can_generate_assets():
+    if GstTranscoder is None:
+        return False, "GstTranscoder is not available"
+
+    if not Gst.ElementFactory.make("testsrcbin"):
+        return False, "testbinsrc is not available"
+
+    return True, None
+
+
+@contextlib.contextmanager
+def created_video_asset(uri=None, num_bufs=30):
+    with tempfile.NamedTemporaryFile(suffix=".ogg") as f:
+        if not uri:
+            uri = Gst.filename_to_uri(f.name)
+        transcoder = GstTranscoder.Transcoder.new("testbin://video,num-buffers=%s" % num_bufs,
+            uri, "application/ogg:video/x-theora:audio/x-vorbis")
+        transcoder.run()
+
+        yield uri
+
+
 def get_asset_uri(name):
     python_tests_dir = os.path.dirname(os.path.abspath(__file__))
     assets_dir = os.path.join(python_tests_dir, "..", "assets")
@@ -145,6 +177,21 @@ class GESTest(unittest.TestCase):
                    for effect in effects]
         self.assertEqual(indexes, list(range(len(effects))))
 
+    def assertGESError(self, error, code, message=""):
+        if error is None:
+            raise AssertionError(
+                "{}{}Received no error".format(message, message and ": "))
+        if error.domain != "GES_ERROR":
+            raise AssertionError(
+                "{}{}Received error ({}) in domain {} rather than "
+                "GES_ERROR".format(
+                    message, message and ": ", error.message, error.domain))
+        err_code = GES.Error(error.code)
+        if err_code != code:
+            raise AssertionError(
+                "{}{}Received {} error ({}) rather than {}".format(
+                    message, message and ": ", err_code.value_name,
+                    error.message, code.value_name))
 
 class GESSimpleTimelineTest(GESTest):
 
@@ -185,8 +232,8 @@ class GESSimpleTimelineTest(GESTest):
                          len(self.track_types))
         self.layer = self.timeline.append_layer()
 
-    def add_clip(self, start, in_point, duration):
-        clip = GES.TestClip()
+    def add_clip(self, start, in_point, duration, asset_type=GES.TestClip):
+        clip = GES.Asset.request(asset_type, None).extract()
         clip.props.start = start
         clip.props.in_point = in_point
         clip.props.duration = duration
@@ -194,15 +241,108 @@ class GESSimpleTimelineTest(GESTest):
 
         return clip
 
-    def append_clip(self, layer=0):
+    def append_clip(self, layer=0, asset_type=GES.TestClip, asset_id=None):
+        while len(self.timeline.get_layers()) < layer + 1:
+            self.timeline.append_layer()
         layer = self.timeline.get_layers()[layer]
-        clip = GES.TestClip()
+        if asset_type == GES.UriClip:
+            asset = GES.UriClipAsset.request_sync(asset_id)
+        else:
+            asset = GES.Asset.request(asset_type, asset_id)
+        clip = asset.extract()
         clip.props.start = layer.get_duration()
         clip.props.duration = 10
         self.assertTrue(layer.add_clip(clip))
 
         return clip
 
+    def assertElementAreEqual(self, ref, element):
+        self.assertTrue(isinstance(element, type(ref)), "%s and %s do not have the same type!" % (ref, element))
+
+        props = [p for p in ref.list_properties() if p.name not in ['name']
+            and not GObject.type_is_a(p.value_type, GObject.Object)]
+        for p in props:
+            pname = p.name
+            refval = GObject.Value()
+            refval.init(p.value_type)
+            refval.set_value(ref.get_property(pname))
+
+            value = GObject.Value()
+            value.init(p.value_type)
+            value.set_value(element.get_property(pname))
+
+            self.assertTrue(Gst.value_compare(refval, value) == Gst.VALUE_EQUAL,
+                "%s are not equal: %s != %s\n    %s != %s" % (pname, value, refval, element, ref))
+
+        if isinstance(ref, GES.TrackElement):
+            self.assertElementAreEqual(ref.get_nleobject(), element.get_nleobject())
+            return
+
+        if not isinstance(ref, GES.Clip):
+            return
+
+        ttypes = [track.type for track in self.timeline.get_tracks()]
+        for ttype in ttypes:
+            if ttypes.count(ttype) > 1:
+                self.warning("Can't deeply check %s and %s "
+                    "(only one track per type supported %s %s found)" % (ref,
+                    element, ttypes.count(ttype), ttype))
+                return
+
+        children = element.get_children(False)
+        for ref_child in ref.get_children(False):
+            ref_track = ref_child.get_track()
+            if not ref_track:
+                self.warning("Can't check %s as not in a track" % (ref_child))
+                continue
+
+            child = None
+            for tmpchild in children:
+                if not isinstance(tmpchild, type(ref_child)):
+                    continue
+
+                if ref_track.type != tmpchild.get_track().type:
+                    continue
+
+                if not isinstance(ref_child, GES.Effect):
+                    child = tmpchild
+                    break
+                elif ref_child.props.bin_description == tmpchild.props.bin_description:
+                    child = tmpchild
+                    break
+
+            self.assertIsNotNone(child, "Could not find equivalent child %s in %s(%s)" % (ref_child,
+                element, children))
+
+            self.assertElementAreEqual(ref_child, child)
+
+    def check_reload_timeline(self):
+        tmpf = tempfile.NamedTemporaryFile(suffix='.xges')
+        uri = Gst.filename_to_uri(tmpf.name)
+        self.assertTrue(self.timeline.save_to_uri(uri, None, True))
+        project = GES.Project.new(uri)
+        mainloop = create_main_loop()
+        def loaded_cb(unused_project, unused_timeline):
+            mainloop.quit()
+
+        project.connect("loaded", loaded_cb)
+        reloaded_timeline = project.extract()
+
+        mainloop.run()
+        self.assertIsNotNone(reloaded_timeline)
+
+        layers = self.timeline.get_layers()
+        reloaded_layers = reloaded_timeline.get_layers()
+        self.assertEqual(len(layers), len(reloaded_layers))
+        for layer, reloaded_layer in zip(layers, reloaded_layers):
+            clips = layer.get_clips()
+            reloaded_clips = reloaded_layer.get_clips()
+            self.assertEqual(len(clips), len(reloaded_clips))
+            for clip, reloaded_clip in zip(clips, reloaded_clips):
+                self.assertElementAreEqual(clip, reloaded_clip)
+
+        return reloaded_timeline
+
     def assertTimelineTopology(self, topology, groups=[]):
         res = []
         for layer in self.timeline.get_layers():
@@ -210,6 +350,9 @@ class GESSimpleTimelineTest(GESTest):
             for clip in layer.get_clips():
                 layer_timings.append(
                     (type(clip), clip.props.start, clip.props.duration))
+                for child in clip.get_children(True):
+                    self.assertEqual(child.props.start, clip.props.start)
+                    self.assertEqual(child.props.duration, clip.props.duration)
 
             res.append(layer_timings)
         if topology != res:
@@ -223,3 +366,402 @@ class GESSimpleTimelineTest(GESTest):
             self.assertEqual(len(timeline_groups), i + 1)
 
         return res
+
+
+class GESTimelineConfigTest(GESTest):
+    """
+    Tests where all the configuration changes, snapping positions and
+    auto-transitions are accounted for.
+    """
+
+    def setUp(self):
+        timeline = GES.Timeline.new()
+        self.timeline = timeline
+        timeline.set_auto_transition(True)
+
+        self.snap_occured = False
+        self.snap = None
+
+        def snap_started(tl, el1, el2, pos):
+            if self.snap_occured:
+                raise AssertionError(
+                    "Previous snap {} not accounted for".format(self.snap))
+            self.snap_occured = True
+            if self.snap is not None:
+                raise AssertionError(
+                    "Previous snap {} not ended".format(self.snap))
+            self.snap = (el1.get_parent(), el2.get_parent(), pos)
+
+        def snap_ended(tl, el1, el2, pos):
+            self.assertEqual(
+                self.snap, (el1.get_parent(), el2.get_parent(), pos))
+            self.snap = None
+
+        timeline.connect("snapping-started", snap_started)
+        timeline.connect("snapping-ended", snap_ended)
+
+        self.lost_clips = []
+
+        def unrecord_lost_clip(layer, clip):
+            if clip in self.lost_clips:
+                self.lost_clips.remove(clip)
+
+        def record_lost_clip(layer, clip):
+            self.lost_clips.append(clip)
+
+        def layer_added(tl, layer):
+            layer.connect("clip-added", unrecord_lost_clip)
+            layer.connect("clip-removed", record_lost_clip)
+
+        timeline.connect("layer-added", layer_added)
+
+        self.clips = []
+        self.auto_transitions = {}
+        self.config = {}
+
+    @staticmethod
+    def new_config(start, duration, inpoint, maxduration, layer):
+        return {"start": start, "duration": duration, "in-point": inpoint,
+                "max-duration": maxduration, "layer": layer}
+
+    def add_clip(self, name, layer, tracks, start, duration, inpoint=0,
+                 maxduration=Gst.CLOCK_TIME_NONE, clip_type=GES.TestClip,
+                 asset_id=None, effects=None):
+        """
+        Create a clip with the given @name and properties and add it to the
+        layer of priority @layer to the tracks in @tracks. Also registers
+        its expected configuration.
+        """
+        if effects is None:
+            effects = []
+
+        lay = self.timeline.get_layer(layer)
+        while lay is None:
+            self.timeline.append_layer()
+            lay = self.timeline.get_layer(layer)
+
+        asset = GES.Asset.request(clip_type, asset_id)
+        clip = asset.extract()
+        self.assertTrue(clip.set_name(name))
+        # FIXME: would be better to use select-tracks-for-object
+        # hack around the fact that we cannot use select-tracks-for-object
+        # in python by setting start to large number to ensure no conflict
+        # when adding a clip
+        self.assertTrue(clip.set_start(10000))
+        self.assertTrue(clip.set_duration(duration))
+        self.assertTrue(clip.set_inpoint(inpoint))
+
+        for effect in effects:
+            self.assertTrue(clip.add(effect))
+
+        if lay.add_clip(clip) != True:
+            raise AssertionError(
+                "Failed to add clip {} to layer {}".format(name, layer))
+
+        # then remove the children not in the selected tracks, which may
+        # now allow some clips to fully/triple overlap because they do
+        # not share a track
+        for child in clip.get_children(False):
+            if child.get_track() not in tracks:
+                clip.remove(child)
+
+        # then move to the desired start
+        prev_snap = self.timeline.get_snapping_distance()
+        self.timeline.set_snapping_distance(0)
+        self.assertTrue(clip.set_start(start))
+        self.timeline.set_snapping_distance(prev_snap)
+
+        self.assertTrue(clip.set_max_duration(maxduration))
+
+        self.config[clip] = self.new_config(
+            start, duration, inpoint, maxduration, layer)
+        self.clips.append(clip)
+
+        return clip
+
+    def add_group(self, name, to_group):
+        """
+        Create a group with the given @name and the elements in @to_group.
+        Also registers its expected configuration.
+        """
+        group = GES.Group.new()
+        self.assertTrue(group.set_name(name))
+        start = None
+        end = None
+        layer = None
+        for element in to_group:
+            if start is None:
+                start = element.start
+                end = element.start + element.duration
+                layer = element.get_layer_priority()
+            else:
+                start = min(start, element.start)
+                end = max(end, element.start + element.duration)
+                layer = min(layer, element.get_layer_priority())
+            self.assertTrue(group.add(element))
+
+        self.config[group] = self.new_config(
+            start, end - start, 0, Gst.CLOCK_TIME_NONE, layer)
+        return group
+
+    def register_auto_transition(self, clip1, clip2, track):
+        """
+        Register that we expect an auto-transition to exist between
+        @clip1 and @clip2 in @track.
+        """
+        transition = self._find_transition(clip1, clip2, track)
+        if transition is None:
+            raise AssertionError(
+                "{} and {} have no auto-transition in track {}".format(
+                    clip1, clip2, track))
+        if transition in self.auto_transitions.values():
+            raise AssertionError(
+                "Auto-transition between {} and {} in track {} already "
+                "registered".format(clip1, clip2, track))
+        key = (clip1, clip2, track)
+        if key in self.auto_transitions:
+            raise AssertionError(
+                "Auto-transition already registered for {}".format(key))
+
+        self.auto_transitions[key] = transition
+
+    def add_video_track(self):
+        track = GES.VideoTrack.new()
+        self.assertTrue(self.timeline.add_track(track))
+        return track
+
+    def add_audio_track(self):
+        track = GES.AudioTrack.new()
+        self.assertTrue(self.timeline.add_track(track))
+        return track
+
+    def assertElementConfig(self, element, config):
+        for prop in config:
+            if prop == "layer":
+                val = element.get_layer_priority()
+            else:
+                val = element.get_property(prop)
+
+            if val != config[prop]:
+                raise AssertionError("{} property {}: {} != {}".format(
+                    element, prop, val, config[prop]))
+
+    @staticmethod
+    def _source_in_track(clip, track):
+        if clip.find_track_element(track, GES.Source):
+            return True
+        return False
+
+    def _find_transition(self, clip1, clip2, track):
+        """find transition from earlier clip1 to later clip2"""
+        if not self._source_in_track(clip1, track) or \
+                not self._source_in_track(clip2, track):
+            return None
+
+        layer_prio = clip1.get_layer_priority()
+        if layer_prio != clip2.get_layer_priority():
+            return None
+
+        if clip1.start >= clip2.start:
+            return None
+
+        start = clip2.start
+        end = clip1.start + clip1.duration
+        if start >= end:
+            return None
+        duration = end - start
+
+        layer = self.timeline.get_layer(layer_prio)
+        self.assertIsNotNone(layer)
+
+        for clip in layer.get_clips():
+            children = clip.get_children(False)
+            if len(children) == 1:
+                child = children[0]
+            else:
+                continue
+            if isinstance(clip, GES.TransitionClip) and clip.start == start \
+                    and clip.duration == duration and child.get_track() == track:
+                return clip
+
+        raise AssertionError(
+            "No auto-transition between {} and {} in track {}".format(
+                clip1, clip2, track))
+
+    def _transition_between(self, new, existing, clip1, clip2, track):
+        if clip1.start < clip2.start:
+            entry = (clip1, clip2, track)
+        else:
+            entry = (clip2, clip1, track)
+        trans = self._find_transition(*entry)
+
+        if trans is None:
+            return
+
+        if entry in new:
+            new.remove(entry)
+            self.auto_transitions[entry] = trans
+        elif entry in existing:
+            existing.remove(entry)
+            expect = self.auto_transitions[entry]
+            if trans != expect:
+                raise AssertionError(
+                    "Auto-transition between {} and {} in track {} changed "
+                    "from {} to {}".format(
+                        clip1, clip2, track, expect, trans))
+        else:
+            raise AssertionError(
+                "Unexpected transition found between {} and {} in track {}"
+                "".format(clip1, clip2, track))
+
+    def assertTimelineConfig(
+            self, new_props=None, snap_position=None, snap_froms=None,
+            snap_tos=None, new_transitions=None, lost_transitions=None):
+        """
+        Check that the timeline configuration has only changed by the
+        differences present in @new_props.
+        Check that a snap occurred at @snap_position between one of the
+        clips in @snap_froms and one of the clips in @snap_tos.
+        Check that all new transitions in the timeline are present in
+        @new_transitions.
+        Checl that all the transitions that were lost are in
+        @lost_transitions.
+        """
+        if new_props is None:
+            new_props = {}
+        if snap_froms is None:
+            snap_froms = []
+        if snap_tos is None:
+            snap_tos = []
+        if new_transitions is None:
+            new_transitions = []
+        if lost_transitions is None:
+            lost_transitions = []
+
+        for element, config in new_props.items():
+            if element not in self.config:
+                self.config[element] = {}
+
+            for prop in config:
+                self.config[element][prop] = new_props[element][prop]
+
+        for element, config in self.config.items():
+            self.assertElementConfig(element, config)
+
+        # check that snapping occurred
+        snaps = []
+        for snap_from in snap_froms:
+            for snap_to in snap_tos:
+                snaps.append((snap_from, snap_to, snap_position))
+
+        if self.snap is None:
+            if snaps:
+                raise AssertionError(
+                    "No snap occurred, but expected a snap in {}".format(snaps))
+        elif not snaps:
+            if self.snap_occured:
+                raise AssertionError(
+                    "Snap {} occurred, but expected no snap".format(self.snap))
+        elif self.snap not in snaps:
+            raise AssertionError(
+                "Snap {} occurred, but expected a snap in {}".format(
+                    self.snap, snaps))
+        self.snap_occured = False
+
+        # check that lost transitions are not part of the layer
+        for clip1, clip2, track in lost_transitions:
+            key = (clip1, clip2, track)
+            if key not in self.auto_transitions:
+                raise AssertionError(
+                    "No such auto-transition between {} and {} in track {} "
+                    "is registered".format(clip1, clip2, track))
+            # make sure original transition was removed from the layer
+            trans = self.auto_transitions[key]
+            if trans not in self.lost_clips:
+                raise AssertionError(
+                    "The auto-transition {} between {} and {} track {} was "
+                    "not removed from the layers, but expect it to be lost"
+                    "".format(trans, clip1, clip2, track))
+            self.lost_clips.remove(trans)
+            # make sure a new one wasn't created
+            trans = self._find_transition(clip1, clip2, track)
+            if trans is not None:
+                raise AssertionError(
+                    "Found auto-transition between {} and {} in track {} "
+                    "is present, but expected it to be lost".format(
+                        clip1, clip2, track))
+            # since it was lost, remove it
+            del self.auto_transitions[key]
+
+        # check that all lost clips are accounted for
+        if self.lost_clips:
+            raise AssertionError(
+                "Clips were lost that are not accounted for: {}".format(
+                    self.lost_clips))
+
+        # check that all other transitions are either existing ones or
+        # new ones
+        new = set(new_transitions)
+        existing = set(self.auto_transitions.keys())
+        for i, clip1 in enumerate(self.clips):
+            for clip2 in self.clips[i+1:]:
+                for track in self.timeline.get_tracks():
+                    self._transition_between(
+                        new, existing, clip1, clip2, track)
+
+        # make sure we are not missing any expected transitions
+        if new:
+            raise AssertionError(
+                "Did not find new transitions for {}".format(new))
+        if existing:
+            raise AssertionError(
+                "Did not find existing transitions for {}".format(existing))
+
+        # make sure there aren't any clips we are unaware of
+        transitions = self.auto_transitions.values()
+        for layer in self.timeline.get_layers():
+            for clip in layer.get_clips():
+                if clip not in self.clips and clip not in transitions:
+                    raise AssertionError("Unknown clip {}".format(clip))
+
+    def assertEdit(self, element, layer, mode, edge, position, snap,
+                   snap_froms, snap_tos, new_props, new_transitions,
+                   lost_transitions):
+        if not element.edit_full(layer, mode, edge, position):
+            raise AssertionError(
+                "Edit of {} to layer {}, mode {}, edge {}, at position {} "
+                "failed when a success was expected".format(
+                    element, layer, mode, edge, position))
+        self.assertTimelineConfig(
+            new_props=new_props, snap_position=snap, snap_froms=snap_froms,
+            snap_tos=snap_tos, new_transitions=new_transitions,
+            lost_transitions=lost_transitions)
+
+    def assertFailEdit(self, element, layer, mode, edge, position, err_code):
+        res = None
+        error = None
+        try:
+            res = element.edit_full(layer, mode, edge, position)
+        except GLib.Error as exception:
+            error = exception
+
+        if err_code is None:
+            if res is not False:
+                raise AssertionError(
+                    "Edit of {} to layer {}, mode {}, edge {}, at "
+                    "position {} succeeded when a failure was expected"
+                    "".format(
+                        element, layer, mode, edge, position))
+            if error is not None:
+                raise AssertionError(
+                    "Edit of {} to layer {}, mode {}, edge {}, at "
+                    "position {} did produced an error when none was "
+                    "expected".format(
+                        element, layer, mode, edge, position))
+        else:
+            self.assertGESError(
+                error, err_code,
+                "Edit of {} to layer {}, mode {}, edge {}, at "
+                "position {}".format(element, layer, mode, edge, position))
+        # should be no change or snapping if edit fails
+        self.assertTimelineConfig()
index afe1590..e33c623 100644 (file)
@@ -21,6 +21,7 @@ from . import overrides_hack
 
 import os
 import gi
+import tempfile
 
 gi.require_version("Gst", "1.0")
 gi.require_version("GES", "1.0")
@@ -31,13 +32,14 @@ from gi.repository import GES  # noqa
 import unittest  # noqa
 from unittest import mock
 
+from . import common
 from .common import GESSimpleTimelineTest  # noqa
 
 Gst.init(None)
 GES.init()
 
 
-class TestTimeline(unittest.TestCase):
+class TestTimeline(GESSimpleTimelineTest):
 
     def test_request_relocated_assets_sync(self):
         path = os.path.join(__file__, "../../../", "png.png")
@@ -47,4 +49,66 @@ class TestTimeline(unittest.TestCase):
         GES.add_missing_uri_relocation_uri(Gst.filename_to_uri(os.path.join(__file__, "../../assets")), False)
         path = os.path.join(__file__, "../../", "png.png")
         self.assertEqual(GES.UriClipAsset.request_sync(Gst.filename_to_uri(path)).props.id,
-            Gst.filename_to_uri(os.path.join(__file__, "../../assets/png.png")))
\ No newline at end of file
+            Gst.filename_to_uri(os.path.join(__file__, "../../assets/png.png")))
+
+    def test_request_relocated_twice(self):
+        GES.add_missing_uri_relocation_uri(Gst.filename_to_uri(os.path.join(__file__, "../../")), True)
+        proj = GES.Project.new()
+
+        asset = proj.create_asset_sync("file:///png.png", GES.UriClip)
+        self.assertIsNotNone(asset)
+        asset = proj.create_asset_sync("file:///png.png", GES.UriClip)
+        self.assertIsNotNone(asset)
+
+    @unittest.skipUnless(*common.can_generate_assets())
+    def test_reload_asset(self):
+        with common.created_video_asset() as uri:
+            asset0 = GES.UriClipAsset.request_sync(uri)
+            self.assertEqual(asset0.props.duration, Gst.SECOND)
+
+            with common.created_video_asset(uri, 60) as uri:
+                GES.Asset.needs_reload(GES.UriClip, uri)
+                asset1 = GES.UriClipAsset.request_sync(uri)
+                self.assertEqual(asset1.props.duration, 2 * Gst.SECOND)
+                self.assertEqual(asset1, asset0)
+
+                with common.created_video_asset(uri, 90) as uri:
+                    mainloop = common.create_main_loop()
+                    def asset_loaded_cb(_, res, mainloop):
+                        asset2 = GES.Asset.request_finish(res)
+                        self.assertEqual(asset2.props.duration, 3 * Gst.SECOND)
+                        self.assertEqual(asset2, asset0)
+                        mainloop.quit()
+
+                    GES.Asset.needs_reload(GES.UriClip, uri)
+                    GES.Asset.request_async(GES.UriClip, uri, None, asset_loaded_cb, mainloop)
+                    mainloop.run()
+
+    def test_asset_metadata_on_reload(self):
+            mainloop = GLib.MainLoop()
+
+            unused, xges_path = tempfile.mkstemp(suffix=".xges")
+            project_uri = Gst.filename_to_uri(os.path.abspath(xges_path))
+
+            asset_uri = Gst.filename_to_uri(os.path.join(__file__, "../../assets/audio_video.ogg"))
+            xges = """<ges version='0.3'>
+                <project properties='properties;' metadatas='metadatas;'>
+                    <ressources>
+                        <asset id='%(uri)s' extractable-type-name='GESUriClip' properties='properties, supported-formats=(int)6, duration=(guint64)2003000000;' metadatas='metadatas, container-format=(string)Matroska, language-code=(string)und, application-name=(string)Lavc56.60.100, encoder-version=(uint)0, audio-codec=(string)Vorbis, nominal-bitrate=(uint)80000, bitrate=(uint)80000, video-codec=(string)&quot;On2\ VP8&quot;, file-size=(guint64)223340, foo=(string)bar;' >
+                        </asset>
+                    </ressources>
+                </project>
+                </ges>"""% {"uri": asset_uri}
+            with open(xges_path, "w") as xges_file:
+                xges_file.write(xges)
+
+
+            def loaded_cb(project, timeline):
+                asset = project.list_assets(GES.Extractable)[0]
+                self.assertEqual(asset.get_meta("foo"), "bar")
+                mainloop.quit()
+
+            loaded_project = GES.Project(uri=project_uri, extractable_type=GES.Timeline)
+            loaded_project.connect("loaded", loaded_cb)
+            timeline = loaded_project.extract()
+            mainloop.run()
index eeb7b8d..e0784bf 100644 (file)
@@ -19,6 +19,7 @@
 
 from . import overrides_hack
 
+import os
 import tempfile
 
 import gi
@@ -92,6 +93,10 @@ class TestTransitionClip(unittest.TestCase):
             timeline.save_to_uri(uri, None, True)
 
             timeline = GES.Timeline.new_from_uri(uri)
+            project = timeline.get_asset()
+            mainloop = common.create_main_loop()
+            project.connect("loaded", lambda _, __: mainloop.quit())
+            mainloop.run()
             self.assertIsNotNone(timeline)
             layer, = timeline.get_layers()
             clip, = layer.get_clips()
@@ -139,7 +144,15 @@ class TestTitleClip(unittest.TestCase):
                             children2[1].props.priority)
 
 
-class TestTrackElements(common.GESTest):
+class TestUriClip(common.GESSimpleTimelineTest):
+    def test_max_duration_on_extract(self):
+        asset = GES.UriClipAsset.request_sync(common.get_asset_uri("audio_video.ogg"))
+        clip = asset.extract()
+
+        self.assertEqual(clip.props.max_duration, Gst.SECOND)
+
+
+class TestTrackElements(common.GESSimpleTimelineTest):
 
     def test_add_to_layer_with_effect_remove_add(self):
         timeline = GES.Timeline.new_audio_video()
@@ -205,6 +218,42 @@ class TestTrackElements(common.GESTest):
         test_clip.remove(effect1)
         self.assert_effects(test_clip, effect2)
 
+    def test_effects_index(self):
+        timeline = GES.Timeline.new_audio_video()
+        layer = timeline.append_layer()
+
+        test_clip = GES.TestClip.new()
+        layer.add_clip(test_clip)
+        self.assert_effects(test_clip)
+
+        ref_effects_list = []
+
+        def add_effect(effect):
+            test_clip.add(effect)
+            ref_effects_list.append(effect)
+
+            self.assert_effects(test_clip, *ref_effects_list)
+
+        def move_effect(old_index, new_index):
+            effect = ref_effects_list[old_index]
+            self.assertTrue(test_clip.set_top_effect_index(effect, new_index))
+
+            ref_effects_list.insert(new_index, ref_effects_list.pop(old_index))
+
+            self.assert_effects(test_clip, *ref_effects_list)
+
+        effects = ["agingtv", "dicetv", "burn", "gamma", "edgetv", "alpha",
+            "exclusion", "chromahold", "coloreffects", "videobalance"]
+
+        for effect in effects:
+            add_effect(GES.Effect.new(effect))
+
+        move_effect(3, 8)
+        move_effect(5, 6)
+        move_effect(0, 9)
+
+        self.assert_effects(test_clip, *ref_effects_list)
+
     def test_signal_order_when_removing_effect(self):
         timeline = GES.Timeline.new_audio_video()
         layer = timeline.append_layer()
@@ -236,3 +285,94 @@ class TestTrackElements(common.GESTest):
         mainloop.run(until_empty=True)
 
         self.assertEqual(signals, ["child-removed", "notify::priority"])
+
+    def test_moving_core_track_elements(self):
+        clip = self.append_clip()
+        clip1 = self.append_clip()
+        title_clip = self.append_clip(asset_type=GES.TitleClip)
+
+        track_element = clip.find_track_element(None, GES.VideoSource)
+        self.assertTrue(clip.remove(track_element))
+
+        track_element1 = clip1.find_track_element(None, GES.VideoSource)
+        self.assertTrue(clip1.remove(track_element1))
+
+        self.assertTrue(clip1.add(track_element))
+        self.assertIsNotNone(track_element.get_track())
+        # We can add another TestSource to the clip as it has the same parent
+        # asset
+        self.assertTrue(clip1.add(track_element1))
+        # We already have a core TrackElement for the video track, not adding
+        # a second one.
+        self.assertIsNone(track_element1.get_track())
+
+        clip1.remove(track_element)
+        clip1.remove(track_element1)
+        title = title_clip.find_track_element(None, GES.VideoSource)
+        self.assertTrue(title_clip.remove(title))
+        # But we can't add an element that has been created by a TitleClip
+        self.assertFalse(clip.add(title))
+        self.assertFalse(title_clip.add(track_element))
+        self.assertTrue(clip.add(track_element))
+        self.assertTrue(clip1.add(track_element1))
+
+    def test_ungroup_regroup(self):
+        clip = self.append_clip()
+        children = clip.get_children(True)
+
+        clip1, clip2 = GES.Container.ungroup(clip, True)
+
+        self.assertEqual(clip, clip1)
+        clip1_child, = clip1.get_children(True)
+        clip2_child, = clip2.get_children(True)
+        self.assertCountEqual (children, [clip1_child, clip2_child])
+
+        # can freely move children between the ungrouped clips
+        self.assertTrue(clip1.remove(clip1_child))
+        self.assertTrue(clip2.add(clip1_child))
+
+        self.assertTrue(clip2.remove(clip2_child))
+        self.assertTrue(clip1.add(clip2_child))
+
+        grouped = GES.Container.group([clip1, clip2])
+        self.assertEqual(grouped, clip1)
+
+        self.assertCountEqual(clip1.get_children(True),
+                [clip1_child, clip2_child])
+        self.assertEqual(clip2.get_children(True), [])
+
+        # can freely move children between the grouped clips
+        self.assertTrue(clip1.remove(clip2_child))
+        self.assertTrue(clip2.add(clip2_child))
+
+        self.assertTrue(clip1.remove(clip1_child))
+        self.assertTrue(clip2.add(clip1_child))
+
+        self.assertTrue(clip2.remove(clip1_child))
+        self.assertTrue(clip1.add(clip1_child))
+
+        self.assertTrue(clip2.remove(clip2_child))
+        self.assertTrue(clip1.add(clip2_child))
+
+        # clip2 no longer part of the timeline
+        self.assertIsNone(clip2.props.layer)
+        self.assertEqual(clip1.props.layer, self.layer)
+        self.assertIsNone(clip2.props.timeline)
+        self.assertEqual(clip1.props.timeline, self.timeline)
+
+    def test_image_source_asset(self):
+        asset = GES.UriClipAsset.request_sync(common.get_asset_uri("png.png"))
+        clip = self.layer.add_asset(asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN)
+
+        image_src, = clip.get_children(True)
+
+        self.assertTrue(image_src.get_asset().is_image())
+        self.assertTrue(isinstance(image_src, GES.VideoUriSource))
+        imagefreeze, = [e for e in image_src.get_nleobject().iterate_recurse()
+            if e.get_factory().get_name() == "imagefreeze"]
+
+        asset = GES.UriClipAsset.request_sync(common.get_asset_uri("audio_video.ogg"))
+        clip = self.layer.add_asset(asset, Gst.SECOND, 0, Gst.SECOND, GES.TrackType.VIDEO)
+        video_src, = clip.get_children(True)
+        self.assertEqual([e for e in video_src.get_nleobject().iterate_recurse()
+            if e.get_factory().get_name() == "imagefreeze"], [])
index eb5a874..ab026eb 100644 (file)
@@ -358,4 +358,50 @@ class TestGroup(common.GESSimpleTimelineTest):
                 (GES.TestClip, 20, 10),
                 (GES.TestClip, 30, 10),
             ],
-        ], groups=[group_clips])
\ No newline at end of file
+        ], groups=[group_clips])
+
+    def test_group_priority(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        self.setUp()
+
+        clip0 = self.append_clip()
+        clip1 = self.append_clip(1)
+        clip1.props.start = 20
+
+        group = GES.Group.new()
+        group.add(clip0)
+        group.add(clip1)
+        self.assertEqual(group.get_layer_priority(), 0)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+            ],
+            [
+                (GES.TestClip, 20, 10),
+            ]
+        ], groups=[(clip0, clip1)])
+        group.remove(clip0)
+        self.assertEqual(group.get_layer_priority(), 1)
+
+        clip1.edit(self.timeline.get_layers(), 2, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip1.start)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+            ],
+            [ ],
+            [
+                (GES.TestClip, 20, 10),
+            ]
+        ], groups=[(clip1,)])
+
+        self.assertEqual(group.get_layer_priority(), 2)
+        self.assertTrue(clip1.edit(self.timeline.get_layers(), 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, clip1.start))
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 20, 10),
+            ],
+            [ ],
+            [ ]
+        ], groups=[(clip1,)])
index 4a02c4e..e28607b 100644 (file)
@@ -19,6 +19,7 @@
 
 from . import overrides_hack
 
+import tempfile  # noqa
 import gi
 
 gi.require_version("Gst", "1.0")
@@ -26,6 +27,7 @@ gi.require_version("GES", "1.0")
 
 from gi.repository import Gst  # noqa
 from gi.repository import GES  # noqa
+from gi.repository import GLib  # noqa
 import unittest  # noqa
 from unittest import mock
 
@@ -45,7 +47,6 @@ class TestTimeline(common.GESSimpleTimelineTest):
         project = GES.Project.new(uri=timeline.get_asset().props.uri)
 
         loaded_called = False
-
         def loaded(unused_project, unused_timeline):
             nonlocal loaded_called
             loaded_called = True
@@ -63,6 +64,86 @@ class TestTimeline(common.GESSimpleTimelineTest):
         self.assertTrue(loaded_called)
         handle.assert_not_called()
 
+    def test_deeply_nested_serialization(self):
+        deep_timeline = common.create_project(with_group=True, saved="deep")
+        deep_project = deep_timeline.get_asset()
+
+        deep_asset = GES.UriClipAsset.request_sync(deep_project.props.id)
+
+        nested_timeline = common.create_project(with_group=False, saved=False)
+        nested_project = nested_timeline.get_asset()
+        nested_project.add_asset(deep_project)
+        nested_timeline.append_layer().add_asset(deep_asset, 0, 0, 5 * Gst.SECOND, GES.TrackType.UNKNOWN)
+
+        uri = "file://%s" % tempfile.NamedTemporaryFile(suffix="-nested.xges").name
+        nested_timeline.get_asset().save(nested_timeline, uri, None, overwrite=True)
+
+        asset = GES.UriClipAsset.request_sync(nested_project.props.id)
+        project = self.timeline.get_asset()
+        project.add_asset(nested_project)
+        refclip = self.layer.add_asset(asset, 0, 0, 5 * Gst.SECOND, GES.TrackType.VIDEO)
+
+        uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name
+        project.save(self.timeline, uri, None, overwrite=True)
+        self.assertEqual(len(project.list_assets(GES.Extractable)), 2)
+
+        mainloop = common.create_main_loop()
+        def loaded_cb(unused_project, unused_timeline):
+            mainloop.quit()
+        project.connect("loaded", loaded_cb)
+
+        # Extract again the timeline and compare with previous one.
+        timeline = project.extract()
+        mainloop.run()
+        layer, = timeline.get_layers()
+        clip, = layer.get_clips()
+        self.assertEqual(clip.props.uri, refclip.props.uri)
+        self.assertEqual(timeline.props.duration, self.timeline.props.duration)
+
+        self.assertEqual(timeline.get_asset(), project)
+        self.assertEqual(len(project.list_assets(GES.Extractable)), 2)
+
+    def test_iter_timeline(self):
+        all_clips = set()
+        for l in range(5):
+            self.timeline.append_layer()
+            for _ in range(5):
+                all_clips.add(self.append_clip(l))
+        self.assertEqual(set(self.timeline.iter_clips()), all_clips)
+
+
+    def test_nested_serialization(self):
+        nested_timeline = common.create_project(with_group=True, saved=True)
+        nested_project = nested_timeline.get_asset()
+        layer = nested_timeline.append_layer()
+
+        asset = GES.UriClipAsset.request_sync(nested_project.props.id)
+        refclip = self.layer.add_asset(asset, 0, 0, 110 * Gst.SECOND, GES.TrackType.UNKNOWN)
+        nested_project.save(nested_timeline, nested_project.props.id, None, True)
+
+        project = self.timeline.get_asset()
+        project.add_asset(nested_project)
+        uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name
+        self.assertEqual(len(project.list_assets(GES.Extractable)), 2)
+        project.save(self.timeline, uri, None, overwrite=True)
+        self.assertEqual(len(project.list_assets(GES.Extractable)), 2)
+
+        mainloop = common.create_main_loop()
+        def loaded(unused_project, unused_timeline):
+            mainloop.quit()
+        project.connect("loaded", loaded)
+
+        # Extract again the timeline and compare with previous one.
+        timeline = project.extract()
+        mainloop.run()
+        layer, = timeline.get_layers()
+        clip, = layer.get_clips()
+        self.assertEqual(clip.props.uri, refclip.props.uri)
+        self.assertEqual(timeline.props.duration, self.timeline.props.duration)
+
+        self.assertEqual(timeline.get_asset(), project)
+        self.assertEqual(len(project.list_assets(GES.Extractable)), 2)
+
     def test_timeline_duration(self):
         self.append_clip()
         self.append_clip()
@@ -120,6 +201,152 @@ class TestTimeline(common.GESSimpleTimelineTest):
             ]
         ])
 
+    @unittest.skipUnless(*common.can_generate_assets())
+    def test_auto_transition_type_after_setting_proxy_asset(self):
+        self.track_types = [GES.TrackType.VIDEO]
+        super().setUp()
+
+        self.timeline.props.auto_transition = True
+        with common.created_video_asset() as uri:
+            self.append_clip(asset_type=GES.UriClip, asset_id=uri)
+            self.append_clip(asset_type=GES.UriClip, asset_id=uri).props.start = 5
+            clip1, transition, clip2 = self.layer.get_clips()
+            video_transition, = transition.get_children(True)
+            video_transition.set_transition_type(GES.VideoStandardTransitionType.BAR_WIPE_LR)
+            self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR)
+
+            with common.created_video_asset() as uri2:
+                proxy_asset = GES.UriClipAsset.request_sync(uri2)
+                clip1.set_asset(proxy_asset)
+                clip1, transition1, clip2 = self.layer.get_clips()
+
+                video_transition1, = transition1.get_children(True)
+                self.assertEqual(video_transition, video_transition1)
+                self.assertEqual(video_transition.get_transition_type(), GES.VideoStandardTransitionType.BAR_WIPE_LR)
+
+    def test_frame_info(self):
+        self.track_types = [GES.TrackType.VIDEO]
+        super().setUp()
+
+        vtrack, = self.timeline.get_tracks()
+        vtrack.update_restriction_caps(Gst.Caps("video/x-raw,framerate=60/1"))
+        self.assertEqual(self.timeline.get_frame_time(60), Gst.SECOND)
+
+        layer = self.timeline.append_layer()
+        asset = GES.Asset.request(GES.TestClip, "framerate=120/1,height=500,width=500,max-duration=f120")
+        clip = layer.add_asset( asset, 0, 0, Gst.SECOND, GES.TrackType.UNKNOWN)
+        self.assertEqual(clip.get_id(), "GESTestClip, framerate=(fraction)120/1, height=(int)500, width=(int)500, max-duration=(string)f120;")
+
+        test_source, = clip.get_children(True)
+        self.assertEqual(test_source.get_natural_size(), (True, 500, 500))
+        self.assertEqual(test_source.get_natural_framerate(), (True, 120, 1))
+        self.assertEqual(test_source.props.max_duration, Gst.SECOND)
+        self.assertEqual(clip.get_natural_framerate(), (True, 120, 1))
+
+        self.assertEqual(self.timeline.get_frame_at(Gst.SECOND), 60)
+        self.assertEqual(clip.props.max_duration, Gst.SECOND)
+
+    def test_layer_active(self):
+        def check_nle_object_activeness(clip, track_type, active=None, ref_clip=None):
+            assert ref_clip is not None or active is not None
+
+            if ref_clip:
+                ref_elem, = ref_clip.find_track_elements(None, track_type, GES.Source)
+                active = ref_elem.get_nleobject().props.active
+
+            elem, = clip.find_track_elements(None, track_type, GES.Source)
+            self.assertIsNotNone(elem)
+            self.assertEqual(elem.get_nleobject().props.active, active)
+
+        def get_tracks(timeline):
+            for track in self.timeline.get_tracks():
+                if track.props.track_type == GES.TrackType.VIDEO:
+                    video_track = track
+                else:
+                    audio_track = track
+            return video_track, audio_track
+
+
+        def check_set_active_for_tracks(layer, active, tracks, expected_changed_tracks):
+            callback_called = []
+            def _check_active_changed_cb(layer, active, tracks, expected_tracks, expected_active):
+                self.assertEqual(set(tracks), set(expected_tracks))
+                self.assertEqual(active, expected_active)
+                callback_called.append(True)
+
+            layer.connect("active-changed", _check_active_changed_cb, expected_changed_tracks, active)
+            self.assertTrue(layer.set_active_for_tracks(active, tracks))
+            self.layer.disconnect_by_func(_check_active_changed_cb)
+            self.assertEqual(callback_called, [True])
+
+        c0 = self.append_clip()
+        check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c0, GES.TrackType.AUDIO, True)
+
+        elem, = c0.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+        elem.props.active = False
+        check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c0, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+        elem.props.active = True
+
+        # Muting audio track
+        video_track, audio_track = get_tracks(self.timeline)
+
+        check_set_active_for_tracks(self.layer, False, [audio_track], [audio_track])
+
+        check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c0, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
+        c1 = self.append_clip()
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+        l1 = self.timeline.append_layer()
+        c1.move_to_layer(l1)
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        self.assertTrue(c1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL,
+                   GES.Edge.EDGE_NONE, c1.props.start))
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
+        self.assertTrue(self.layer.remove_clip(c1))
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        self.assertTrue(self.layer.add_clip(c1))
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+        check_set_active_for_tracks(self.layer, True, None, [audio_track])
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        elem, = c1.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        # Force deactivating a specific TrackElement
+        elem.props.active = False
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
+        # Try activating a specific TrackElement, that won't change the
+        # underlying nleobject activness
+        check_set_active_for_tracks(self.layer, False, None, [audio_track, video_track])
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, False)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+        elem.props.active = True
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, False)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
 
 class TestEditing(common.GESSimpleTimelineTest):
 
@@ -141,12 +368,10 @@ class TestEditing(common.GESSimpleTimelineTest):
 
         def _snapped_cb(timeline, elem1, elem2, position):
             self.snapped_at.append(position)
-            Gst.error('%s' % position)
 
         def _snapped_end_cb(timeline, elem1, elem2, position):
             if self.snapped_at:  # Ignoring first snap end.
                 self.snapped_at.append(Gst.CLOCK_TIME_NONE)
-                Gst.error('%s' % position)
 
         self.timeline.connect("snapping-started", _snapped_cb)
         self.timeline.connect("snapping-ended", _snapped_end_cb)
@@ -163,6 +388,7 @@ class TestEditing(common.GESSimpleTimelineTest):
             ]
         ])
 
+        # snap to 20
         clip.props.start = 18
         self.assertTimelineTopology([
             [  # Unique layer
@@ -172,6 +398,7 @@ class TestEditing(common.GESSimpleTimelineTest):
         ])
         self.assertEqual(self.snapped_at, [20])
 
+        # no snapping
         clip.props.start = 30
         self.assertTimelineTopology([
             [  # Unique layer
@@ -181,6 +408,7 @@ class TestEditing(common.GESSimpleTimelineTest):
         ])
         self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE])
 
+        # snap to 20
         clip.props.start = 18
         self.assertTimelineTopology([
             [  # Unique layer
@@ -188,8 +416,8 @@ class TestEditing(common.GESSimpleTimelineTest):
                 (GES.TestClip, 20, 10),
             ]
         ])
-        self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE,
-            Gst.CLOCK_TIME_NONE, 20])
+        self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE, 20])
+        # snap to 20 again
         clip.props.start = 19
         self.assertTimelineTopology([
             [  # Unique layer
@@ -197,8 +425,9 @@ class TestEditing(common.GESSimpleTimelineTest):
                 (GES.TestClip, 20, 10),
             ]
         ])
-        self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE,
-            Gst.CLOCK_TIME_NONE, 20])
+        self.assertEqual(
+            self.snapped_at,
+            [20, Gst.CLOCK_TIME_NONE, 20, Gst.CLOCK_TIME_NONE, 20])
 
     def test_rippling_snaps(self):
         self.timeline.props.auto_transition = True
@@ -301,20 +530,187 @@ class TestEditing(common.GESSimpleTimelineTest):
 
     def test_trim_start(self):
         clip = self.append_clip()
-        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10))
+        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 10))
         self.assertTimelineTopology([
             [  # Unique layer
                 (GES.TestClip, 10, 10),
             ]
         ])
 
-        self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_NONE, 0))
+        self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0))
         self.assertTimelineTopology([
             [  # Unique layer
                 (GES.TestClip, 10, 10),
             ]
         ])
 
+    def test_trim_non_core(self):
+        clip = self.append_clip()
+        self.assertTrue(clip.set_inpoint(12))
+        self.assertTrue(clip.set_max_duration(30))
+        self.assertEqual(clip.get_duration_limit(), 18)
+        for child in clip.get_children(False):
+            self.assertEqual(child.get_inpoint(), 12)
+            self.assertEqual(child.get_max_duration(), 30)
+
+        effect0 = GES.Effect.new("textoverlay")
+        effect0.set_has_internal_source(True)
+        self.assertTrue(effect0.set_inpoint(5))
+        self.assertTrue(effect0.set_max_duration(20))
+        self.assertTrue(clip.add(effect0))
+        self.assertEqual(clip.get_duration_limit(), 15)
+
+        effect1 = GES.Effect.new("agingtv")
+        effect1.set_has_internal_source(False)
+        self.assertTrue(clip.add(effect1))
+
+        effect2 = GES.Effect.new("textoverlay")
+        effect2.set_has_internal_source(True)
+        self.assertTrue(effect2.set_inpoint(8))
+        self.assertTrue(effect2.set_max_duration(18))
+        self.assertTrue(clip.add(effect2))
+        self.assertEqual(clip.get_duration_limit(), 10)
+
+        effect3 = GES.Effect.new("textoverlay")
+        effect3.set_has_internal_source(True)
+        self.assertTrue(effect3.set_inpoint(20))
+        self.assertTrue(effect3.set_max_duration(22))
+        self.assertTrue(effect3.set_active(False))
+        self.assertTrue(clip.add(effect3))
+        self.assertEqual(clip.get_duration_limit(), 10)
+
+        self.assertTrue(clip.set_start(10))
+        self.assertTrue(clip.set_duration(10))
+
+        # cannot trim to a 0 because effect0 would have a negative in-point
+        error = None
+        try:
+            clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0)
+        except GLib.Error as err:
+            error = err
+        self.assertGESError(error, GES.Error.NEGATIVE_TIME)
+
+        self.assertEqual(clip.start, 10)
+        self.assertEqual(clip.inpoint, 12)
+        self.assertEqual(effect0.inpoint, 5)
+        self.assertEqual(effect1.inpoint, 0)
+        self.assertEqual(effect2.inpoint, 8)
+        self.assertEqual(effect3.inpoint, 20)
+
+        self.assertTrue(
+            clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5))
+
+        self.assertEqual(clip.start, 5)
+        self.assertEqual(clip.duration, 15)
+        self.assertEqual(clip.get_duration_limit(), 15)
+
+        for child in clip.get_children(False):
+            self.assertEqual(child.start, 5)
+            self.assertEqual(child.duration, 15)
+
+        self.assertEqual(clip.inpoint, 7)
+        self.assertEqual(effect0.inpoint, 0)
+        self.assertEqual(effect1.inpoint, 0)
+        self.assertEqual(effect2.inpoint, 3)
+        self.assertEqual(effect3.inpoint, 20)
+
+        self.assertTrue(
+            clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15))
+
+        self.assertEqual(clip.start, 15)
+        self.assertEqual(clip.duration, 5)
+        self.assertEqual(clip.get_duration_limit(), 5)
+
+        for child in clip.get_children(False):
+            self.assertEqual(child.start, 15)
+            self.assertEqual(child.duration, 5)
+
+        self.assertEqual(clip.inpoint, 17)
+        self.assertEqual(effect0.inpoint, 10)
+        self.assertEqual(effect1.inpoint, 0)
+        self.assertEqual(effect2.inpoint, 13)
+        self.assertEqual(effect3.inpoint, 20)
+
+    def test_trim_time_effects(self):
+        self.track_types = [GES.TrackType.VIDEO]
+        super().setUp()
+        clip = self.append_clip(asset_id="max-duration=30")
+        self.assertTrue(clip.set_inpoint(12))
+        self.assertEqual(clip.get_duration_limit(), 18)
+
+        children = clip.get_children(False)
+        self.assertTrue(children)
+        self.assertEqual(len(children), 1)
+
+        source = children[0]
+        self.assertEqual(source.get_inpoint(), 12)
+        self.assertEqual(source.get_max_duration(), 30)
+
+        rate0 = GES.Effect.new("videorate rate=0.25")
+
+        overlay = GES.Effect.new("textoverlay")
+        overlay.set_has_internal_source(True)
+        self.assertTrue(overlay.set_inpoint(5))
+        self.assertTrue(overlay.set_max_duration(16))
+
+        rate1 = GES.Effect.new("videorate rate=2.0")
+
+        self.assertTrue(clip.add(rate0))
+        self.assertTrue(clip.add(overlay))
+        self.assertTrue(clip.add(rate1))
+
+        #                   source -> rate1 -> overlay -> rate0
+        # in-point/max-dur  12-30              5-16
+        # internal
+        # start/end         12-30     0-9      5-14       0-36
+        self.assertEqual(clip.get_duration_limit(), 36)
+        self.assertTrue(clip.set_start(40))
+        self.assertTrue(clip.set_duration(10))
+        self.check_reload_timeline()
+
+        # cannot trim to a 16 because overlay would have a negative in-point
+        error = None
+        try:
+            clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16)
+        except GLib.Error as err:
+            error = err
+        self.assertGESError(error, GES.Error.NEGATIVE_TIME)
+
+        self.assertEqual(clip.get_start(), 40)
+        self.assertEqual(clip.get_duration(), 10)
+        self.assertEqual(source.get_inpoint(), 12)
+        self.assertEqual(source.get_max_duration(), 30)
+        self.assertEqual(overlay.get_inpoint(), 5)
+        self.assertEqual(overlay.get_max_duration(), 16)
+
+        self.check_reload_timeline()
+
+        # trim backwards to 20
+        self.assertTrue(
+            clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20))
+
+        self.assertEqual(clip.get_start(), 20)
+        self.assertEqual(clip.get_duration(), 30)
+        # reduced by 10
+        self.assertEqual(source.get_inpoint(), 2)
+        self.assertEqual(source.get_max_duration(), 30)
+        # reduced by 5
+        self.assertEqual(overlay.get_inpoint(), 0)
+        self.assertEqual(overlay.get_max_duration(), 16)
+
+        # trim forwards to 28
+        self.assertTrue(
+            clip.edit_full(-1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 28))
+        self.assertEqual(clip.get_start(), 28)
+        self.assertEqual(clip.get_duration(), 22)
+        # increased by 4
+        self.assertEqual(source.get_inpoint(), 6)
+        self.assertEqual(source.get_max_duration(), 30)
+        # increased by 2
+        self.assertEqual(overlay.get_inpoint(), 2)
+        self.assertEqual(overlay.get_max_duration(), 16)
+        self.check_reload_timeline()
+
     def test_ripple_end(self):
         clip = self.append_clip()
         clip.set_max_duration(20)
@@ -407,6 +803,32 @@ class TestEditing(common.GESSimpleTimelineTest):
         self.assertEqual(group.props.start, 5)
         self.assertEqual(group.props.duration, 15)
 
+        group1 = GES.Group.new ()
+        group1.add(group)
+        clips[0].trim(0)
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+        self.assertEqual(group.props.start, 0)
+        self.assertEqual(group.props.duration, 20)
+        self.assertEqual(group1.props.start, 0)
+        self.assertEqual(group1.props.duration, 20)
+
+        self.assertTrue(clips[1].edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 5),
+            ]
+        ])
+        self.assertEqual(group.props.start, 0)
+        self.assertEqual(group.props.duration, 15)
+        self.assertEqual(group1.props.start, 0)
+        self.assertEqual(group1.props.duration, 15)
+
     def test_trim_end_past_max_duration(self):
         clip = self.append_clip()
         max_duration = clip.props.duration
@@ -425,6 +847,160 @@ class TestEditing(common.GESSimpleTimelineTest):
             ]
         ])
 
+    def test_illegal_effect_move(self):
+        c0 = self.append_clip()
+        self.append_clip()
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        effect = GES.Effect.new("agingtv")
+        c0.add(effect)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        self.assertFalse(effect.set_start(10))
+        self.assertEqual(effect.props.start, 0)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+
+        self.assertFalse(effect.set_duration(20))
+        self.assertEqual(effect.props.duration, 10)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+    def test_moving_overlay_clip_in_group(self):
+        c0 = self.append_clip()
+        overlay = self.append_clip(asset_type=GES.TextOverlayClip)
+        group = GES.Group.new()
+        group.add(c0)
+        group.add(overlay)
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TextOverlayClip, 10, 10),
+            ]
+        ], groups=[(c0, overlay)])
+
+        self.assertTrue(overlay.set_start(20))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 10, 10),
+                (GES.TextOverlayClip, 20, 10),
+            ]
+        ], groups=[(c0, overlay)])
+
+    def test_moving_group_in_group(self):
+        c0 = self.append_clip()
+        overlay = self.append_clip(asset_type=GES.TextOverlayClip)
+        group0 = GES.Group.new()
+        group0.add(c0)
+        group0.add(overlay)
+
+        c1 = self.append_clip()
+        group1 = GES.Group.new()
+        group1.add(group0)
+        group1.add(c1)
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TextOverlayClip, 10, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ], groups=[(c1, group0), (c0, overlay)])
+
+        self.assertTrue(group0.set_start(10))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 10, 10),
+                (GES.TextOverlayClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ], groups=[(c1, group0), (c0, overlay)])
+        self.check_element_values(group0, 10, 0, 20)
+        self.check_element_values(group1, 10, 0, 30)
+
+    def test_illegal_group_child_move(self):
+        clip0 = self.append_clip()
+        _clip1 = self.add_clip(20, 0, 10)
+        overlay = self.add_clip(20, 0, 10, asset_type=GES.TextOverlayClip)
+
+        group = GES.Group.new()
+        group.add(clip0)
+        group.add(overlay)
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TextOverlayClip, 20, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ], groups=[(clip0, overlay),])
+
+        # Can't move as clip0 and clip1 would fully overlap
+        self.assertFalse(overlay.set_start(40))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TextOverlayClip, 20, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ], groups=[(clip0, overlay)])
+
+    def test_child_duration_change(self):
+        c0 = self.append_clip()
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+            ]
+        ])
+        self.assertTrue(c0.set_duration(40))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 40),
+            ]
+        ])
+
+        c0.children[0].set_duration(10)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+            ]
+        ])
+
+        self.assertTrue(c0.set_start(40))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 40, 10),
+            ]
+        ])
+
+        c0.children[0].set_start(10)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
 
 class TestInvalidOverlaps(common.GESSimpleTimelineTest):
 
@@ -475,11 +1051,65 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest):
         clip2 = self.add_clip(start=10, in_point=0, duration=4)
         clip3 = self.add_clip(start=12, in_point=0, duration=3)
 
-        self.assertIsNone(clip1.split(13))
-        self.assertIsNone(clip1.split(8))
+        self.assertIsNone(clip1.split_full(13))
+        self.assertIsNone(clip1.split_full(8))
+        self.assertIsNone(clip3.split_full(12))
+        self.assertIsNone(clip3.split_full(15))
+
+    def _fail_split(self, clip, position):
+        split = None
+        error = None
+        try:
+            split = clip.split_full(position)
+        except GLib.Error as err:
+            error = err
+        self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self.assertIsNone(split)
+
+    def test_split_with_transition(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+        self.timeline.set_auto_transition(True)
+
+        clip0 = self.add_clip(start=0, in_point=0, duration=50)
+        clip1 = self.add_clip(start=20, in_point=0, duration=50)
+        clip2 = self.add_clip(start=60, in_point=0, duration=20)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 50),
+                (GES.TransitionClip, 20, 30),
+                (GES.TestClip, 20, 50),
+                (GES.TransitionClip, 60, 10),
+                (GES.TestClip, 60, 20),
+            ]
+        ])
+
+        # Split should fail as the first part of the split
+        # would be fully overlapping clip0
+        self._fail_split(clip1, 40)
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 50),
+                (GES.TransitionClip, 20, 30),
+                (GES.TestClip, 20, 50),
+                (GES.TransitionClip, 60, 10),
+                (GES.TestClip, 60, 20),
+            ]
+        ])
+
+        # same with end of the clip
+        self._fail_split(clip1, 65)
 
-        self.assertIsNone(clip3.split(12))
-        self.assertIsNone(clip3.split(15))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 50),
+                (GES.TransitionClip, 20, 30),
+                (GES.TestClip, 20, 50),
+                (GES.TransitionClip, 60, 10),
+                (GES.TestClip, 60, 20),
+            ]
+        ])
 
     def test_changing_duration(self):
         clip1 = self.add_clip(start=9, in_point=0, duration=2)
@@ -779,6 +1409,87 @@ class TestInvalidOverlaps(common.GESSimpleTimelineTest):
         ])
 
 
+class TestConfigurationRules(common.GESSimpleTimelineTest):
+
+    def _try_add_clip(self, start, duration, layer=None, error=None):
+        if layer is None:
+            layer = self.layer
+        asset = GES.Asset.request(GES.TestClip, None)
+        found_err = None
+        clip = None
+        # large inpoint to allow trims
+        try:
+            clip = layer.add_asset_full(
+                asset, start, 1000, duration, GES.TrackType.UNKNOWN)
+        except GLib.Error as err:
+            found_err = err
+        if error is None:
+            self.assertIsNotNone(clip)
+        else:
+            self.assertIsNone(clip)
+            self.assertGESError(found_err, error)
+        return clip
+
+    def test_full_overlap_add(self):
+        clip1 = self._try_add_clip(50, 50)
+        self._try_add_clip(50, 50, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self._try_add_clip(49, 51, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self._try_add_clip(51, 49, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+    def test_triple_overlap_add(self):
+        clip1 = self._try_add_clip(0, 50)
+        clip2 = self._try_add_clip(40, 50)
+        self._try_add_clip(39, 12, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self._try_add_clip(30, 30, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self._try_add_clip(1, 88, error=GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+    def test_full_overlap_move(self):
+        clip1 = self._try_add_clip(0, 50)
+        clip2 = self._try_add_clip(50, 50)
+        self.assertFalse(clip2.set_start(0))
+
+    def test_triple_overlap_move(self):
+        clip1 = self._try_add_clip(0, 50)
+        clip2 = self._try_add_clip(40, 50)
+        clip3 = self._try_add_clip(100, 60)
+        self.assertFalse(clip3.set_start(30))
+
+    def test_full_overlap_move_into_layer(self):
+        clip1 = self._try_add_clip(0, 50)
+        layer2 = self.timeline.append_layer()
+        clip2 = self._try_add_clip(0, 50, layer2)
+        res = None
+        try:
+            res = clip2.move_to_layer_full(self.layer)
+        except GLib.Error as error:
+            self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self.assertIsNone(res)
+
+    def test_triple_overlap_move_into_layer(self):
+        clip1 = self._try_add_clip(0, 50)
+        clip2 = self._try_add_clip(40, 50)
+        layer2 = self.timeline.append_layer()
+        clip3 = self._try_add_clip(30, 30, layer2)
+        res = None
+        try:
+            res = clip3.move_to_layer_full(self.layer)
+        except GLib.Error as error:
+            self.assertGESError(error, GES.Error.INVALID_OVERLAP_IN_TRACK)
+        self.assertIsNone(res)
+
+    def test_full_overlap_trim(self):
+        clip1 = self._try_add_clip(0, 50)
+        clip2 = self._try_add_clip(50, 50)
+        self.assertFalse(clip2.trim(0))
+        self.assertFalse(clip1.set_duration(100))
+
+    def test_triple_overlap_trim(self):
+        clip1 = self._try_add_clip(0, 20)
+        clip2 = self._try_add_clip(10, 30)
+        clip3 = self._try_add_clip(30, 20)
+        self.assertFalse(clip3.trim(19))
+        self.assertFalse(clip1.set_duration(31))
+
 class TestSnapping(common.GESSimpleTimelineTest):
 
     def test_snapping(self):
@@ -882,6 +1593,2064 @@ class TestSnapping(common.GESSimpleTimelineTest):
         self.assertEqual(clip1.props.duration, split_position)
         self.assertEqual(clip2.props.start, split_position)
 
+class TestComplexEditing(common.GESTimelineConfigTest):
+
+    def test_normal_move(self):
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                            .................g1..................
+                            :                                   :
+                            :                         *=========*
+                            :                         |   c0    |
+                            :                         *=========*
+        ____________________:___________________________________:____
+        layer1______________:___________________________________:____
+                            :                                   :
+                            ..............g0...............     :
+                            :                             :     :
+                            *=======================*     :     :
+                            |           c1          |     :     :
+                            *=========*=============*=====*     :
+                            :         |        c2         |     :
+                            :         *===================*     :
+                            :.............................:     :
+                            :                                   :
+                            :...................................:
+        _____________________________________________________________
+        layer2_______________________________________________________
+
+            *=============================*
+            |              c3             |
+            *=============================*
+        """
+        track = self.add_video_track()
+        c0 = self.add_clip("c0", 0, [track], 23, 5)
+        c1 = self.add_clip("c1", 1, [track], 10, 12)
+        c2 = self.add_clip("c2", 1, [track], 15, 10)
+        self.register_auto_transition(c1, c2, track)
+        c3 = self.add_clip("c3", 2, [track], 2, 15)
+        g0 = self.add_group("g0", [c1, c2])
+        g1 = self.add_group("g1", [c0, g0])
+
+        self.assertTimelineConfig()
+
+        # test invalid edits
+
+        # cannot move c0 up one layer because it would cause a triple
+        # overlap between c1, c2 and c3 when g0 moves
+        self.assertFailEdit(
+            c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 23,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot move c0, without moving g1, to 21 layer 1 because it
+        # would be completely overlapped by c2
+        self.assertFailEdit(
+            c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 20,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot move c1, without moving g1, with end 25 because it
+        # would be completely overlapped by c2
+        self.assertFailEdit(
+            c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 25,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot move g0 to layer 0 because it would make c0 go to a
+        # negative layer
+        self.assertFailEdit(
+            g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10,
+            GES.Error.NEGATIVE_LAYER)
+
+        # cannot move c1 for same reason
+        error = None
+        try:
+            c1.move_to_layer_full(self.timeline.get_layer(0))
+        except GLib.Error as err:
+            error = err
+        self.assertGESError(error, GES.Error.NEGATIVE_LAYER)
+        self.assertTimelineConfig({}, [])
+
+        # failure with snapping
+        self.timeline.set_snapping_distance(1)
+
+        # cannot move to 0 because end edge of c0 would snap with end of
+        # c3, making the new start become negative
+        self.assertFailEdit(
+            g0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0,
+            GES.Error.NEGATIVE_TIME)
+
+        # cannot move start of c1 to 14 because snapping causes a full
+        # overlap with c0
+        self.assertFailEdit(
+            c1, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 14,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot move end of c2 to 21 because snapping causes a full
+        # overlap with c0
+        self.assertFailEdit(
+            c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # successes
+        self.timeline.set_snapping_distance(3)
+        # moving c0 also moves g1, along with g0
+        # with no snapping, this would result in a triple overlap between
+        # c1, c2 and c3, but c2's start edge will snap to the end of c3
+        # at a distance of 3 allowing the edit to succeed
+        #
+        # c1 and c3 have a new transition
+        # transition between c1 and c2 is not lost
+        #
+        # NOTE: there is no snapping between c0, c1 or c2 even though
+        # their edges are within distance 2 of each other because they are
+        # all moving
+        self.assertEdit(
+            c0, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 22, 17,
+            [c2], [c3],
+            {
+                c0 : {"start": 25, "layer": 1},
+                c1 : {"start": 12, "layer": 2},
+                c2 : {"start": 17, "layer": 2},
+                g0 : {"start": 12, "layer": 2},
+                g1 : {"start": 12, "layer": 1}
+            }, [(c3, c1, track)], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+                                .................g1..................
+                                :                                   :
+                                :                         *=========*
+                                :                         |   c0    |
+                                :                         *=========*
+        ________________________:___________________________________:
+        layer2__________________:___________________________________:
+                                :                                   :
+                                ..............g0...............     :
+                                :                             :     :
+                                *=======================*     :     :
+                                |           c1          |     :     :
+            *===================*=========*=============*=====*     :
+            |              c3             |        c2         |     :
+            *=============================*===================*     :
+                                :.............................:     :
+                                :                                   :
+                                :...................................:
+        """
+        # using EDGE_START we can move without moving parent
+        # snap at same position 17 but with c1's end edge to c3's end
+        # edge
+        # loose transition between c1 and c3
+        self.assertEdit(
+            g0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, 17,
+            [c1], [c3],
+            {
+                c1 : {"start": 5, "layer": 0},
+                c2 : {"start": 10, "layer": 0},
+                g0 : {"start": 5, "layer": 0},
+                g1 : {"start": 5, "duration": 25, "layer": 0},
+            }, [], [(c3, c1, track)])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                  .........................g1........................
+                  :                                                 :
+                  ...............g0..............                   :
+                  :                             :                   :
+                  *=======================*     :                   :
+                  |          c1           |     :                   :
+                  *=========*=============*=====*                   :
+                  :         |         c2        |                   :
+                  :         *===================*                   :
+                  :.............................:                   :
+        __________:_________________________________________________:
+        layer1____:_________________________________________________:
+                  :                                                 :
+                  :                                       *=========*
+                  :                                       |   c0    |
+                  :                                       *=========*
+                  :.................................................:
+        _____________________________________________________________
+        layer2_______________________________________________________
+
+            *=============================*
+            |              c3             |
+            *=============================*
+        """
+        # using EDGE_END we can move without moving parent
+        # no snap
+        # loose transition between c1 and c2
+        self.assertEdit(
+            c2, 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 21, None,
+            [], [],
+            {
+                c2 : {"duration": 11, "layer": 1},
+                g0 : {"duration": 16},
+            }, [], [(c1, c2, track)])
+
+        # no snapping when we use move layer
+        self.timeline.set_snapping_distance(10)
+        self.assertTrue(
+            c3.move_to_layer(self.timeline.get_layer(1)))
+        self.assertTimelineConfig(
+            new_props={c3 : {"layer": 1}}, new_transitions=[(c3, c2, track)])
+
+    def test_ripple(self):
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                      ......................g0.................
+                      :                                       :
+                      *=========*                             :
+                      |    c0   |                             :
+                      *=========*                             :
+        ______________:_______________________________________:______
+        layer1________:_______________________________________:______
+                      :                                       :
+                      :                         *=============*
+                      :                         |      c3     |
+                      :                         *=============*
+                      :.......................................:
+                            *===================*
+                            |         c2        |
+                  *=========*=========*=========*
+                  |        c1         |
+                  *===================*
+        """
+        track = self.add_video_track()
+        c0 = self.add_clip("c0", 0, [track], 7, 5)
+        c1 = self.add_clip("c1", 1, [track], 5, 10)
+        c2 = self.add_clip("c2", 1, [track], 10, 10)
+        c3 = self.add_clip("c3", 1, [track], 20, 7)
+        self.register_auto_transition(c1, c2, track)
+        g0 = self.add_group("g0", [c0, c3])
+
+        self.assertTimelineConfig()
+
+        # test failures
+
+        self.timeline.set_snapping_distance(2)
+
+        # would cause negative layer priority for c0
+        self.assertFailEdit(
+            c1, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5,
+            GES.Error.NEGATIVE_LAYER)
+
+        # would lead to c2 fully overlapping c3 since c2 does ripple
+        # but c3 does not(c3 shares a toplevel with c0, and
+        # GES_EDGE_START, same as NORMAL mode, does not move the
+        # toplevel
+        self.assertFailEdit(
+            c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 25,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # would lead to c2 fully overlapping c3 since c2 does not
+        # ripple but c3 does
+        self.assertFailEdit(
+            c0, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 13,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # add two more clips
+
+        c4 = self.add_clip("c4", 2, [track], 17, 8)
+        c5 = self.add_clip("c5", 2, [track], 21, 8)
+        self.register_auto_transition(c4, c5, track)
+
+        self.assertTimelineConfig()
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                      ......................g0.................
+                      :                                       :
+                      *=========*                             :
+                      |    c0   |                             :
+                      *=========*                             :
+        ______________:_______________________________________:______
+        layer1________:_______________________________________:______
+                      :                                       :
+                      :                         *=============*
+                      :                         |      c3     |
+                      :                         *=============*
+                      :.......................................:
+                            *===================*
+                            |         c2        |
+                  *=========*=========*=========*
+                  |        c1         |
+                  *===================*
+        _____________________________________________________________
+        layer2_______________________________________________________
+
+                                          *===============*
+                                          |       c4      |
+                                          *=======*=======*=======*
+                                                  |       c5      |
+                                                  *===============*
+        """
+
+        # rippling start of c2 only moves c4 and c5 because c3 is part
+        # of a toplevel with an earlier start
+        # NOTE: snapping only occurs for the edges of c2, in particular
+        # start of c4 does not snap to end of c1
+        self.assertEdit(
+            c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8, 7,
+            [c2], [c0],
+            {
+                c2 : {"start": 7},
+                c4 : {"start": 14},
+                c5 : {"start": 18},
+            }, [], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                      ......................g0.................
+                      :                                       :
+                      *=========*                             :
+                      |    c0   |                             :
+                      *=========*                             :
+        ______________:_______________________________________:______
+        layer1________:_______________________________________:______
+                      :                                       :
+                      :                         *=============*
+                      :                         |      c3     |
+                      :                         *=============*
+                      :.......................................:
+                      *===================*
+                      |         c2        |
+                  *===*===============*===*
+                  |        c1         |
+                  *===================*
+        _____________________________________________________________
+        layer2_______________________________________________________
+
+                                    *===============*
+                                    |       c4      |
+                                    *=======*=======*=======*
+                                            |       c5      |
+                                            *===============*
+        """
+
+        # rippling end of c2, only c5 moves
+        # NOTE: start edge of c2 does not snap!
+        self.assertEdit(
+            c2, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 19, 20,
+            [c2], [c3],
+            {
+                c2 : {"duration": 13},
+                c5 : {"start": 21},
+            }, [], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                      ......................g0.................
+                      :                                       :
+                      *=========*                             :
+                      |    c0   |                             :
+                      *=========*                             :
+        ______________:_______________________________________:______
+        layer1________:_______________________________________:______
+                      :                                       :
+                      :                         *=============*
+                      :                         |      c3     |
+                      :                         *=============*
+                      :.......................................:
+                      *=========================*
+                      |            c2           |
+                  *===*===============*=========*
+                  |        c1         |
+                  *===================*
+        _____________________________________________________________
+        layer2_______________________________________________________
+
+                                    *===============*
+                                    |       c4      |
+                                    *=============*=*=============*
+                                                  |       c5      |
+                                                  *===============*
+        """
+
+        # everything except c1 moves, and to the next layer
+        # end edge of c2 snaps to end of c1
+        # NOTE: does not snap to edges of rippled clips
+        # NOTE: c4 and c5 do not loose their transition when moving
+        # to the new layer
+        self.assertEdit(
+            c2, 2, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 0, 15,
+            [c2], [c1],
+            {
+                c0 : {"start": 2, "layer": 1},
+                c2 : {"start": 2, "layer": 2},
+                c3 : {"start": 15, "layer": 2},
+                c4 : {"start": 9, "layer": 3},
+                c5 : {"start": 16, "layer": 3},
+                g0 : {"start": 2, "layer": 1},
+            }, [(c0, c1, track)], [(c1, c2, track)])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+                  *===================*
+                  |        c1         |
+                  *===================*
+            ...................g0....................
+            *=========*                             :
+            |    c0   |                             :
+            *=========*                             :
+        ____:_______________________________________:________________
+        layer2______________________________________:________________
+            :                                       :
+            :                         *=============*
+            :                         |      c3     |
+            :                         *=============*
+            :.......................................:
+            *=========================*
+            |            c2           |
+            *=========================*
+        _____________________________________________________________
+        layer3_______________________________________________________
+
+                          *===============*
+                          |       c4      |
+                          *=============*=*=============*
+                                        |       c5      |
+                                        *===============*
+        """
+
+        # group c1 and c5, and g0 and c2
+        g1 = self.add_group("g1", [c1, c5])
+        g2 = self.add_group("g2", [g0, c2])
+        self.assertTimelineConfig()
+
+        # moving end edge of c0 does not move anything else in the same
+        # toplevel g2
+        # c5 does not move because it is grouped with c1, which starts
+        # earlier than the end edge of c0
+        # only c4 moves
+        # c0 does not snap to c4's start edge
+        self.assertEdit(
+            c0, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 10, None,
+            [], [],
+            {
+                c0 : {"duration": 8},
+                c4 : {"start": 12},
+            }, [], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+                  ..................g1.....................
+                  *===================*                   :
+                  |        c1         |                   :
+                  *===================*                   :
+                  :...................................... :
+            ...................g2....................   : :
+            :..................g0...................:   : :
+            *===============*                       :   : :
+            |        c0     |                       :   : :
+            *===============*                       :   : :
+        ____:_______________________________________:___:_:__________
+        layer2______________________________________:___:_:__________
+            :                                       :   : :
+            :                         *=============*   : :
+            :                         |      c3     |   : :
+            :                         *=============*   : :
+            :.......................................:   : :
+            *=========================*             :   : :
+            |            c2           |             :   : :
+            *=========================*             :   : :
+            :.......................................:   : :
+        ________________________________________________:_:__________
+        layer3__________________________________________:_:__________
+                                        ................: :
+                                        *===============* :
+                                        |       c5      | :
+                                        *===============* :
+                                        :.................:
+                                *===============*
+                                |       c4      |
+                                *===============*
+        """
+
+        # rippling start of c5 does not move anything else
+        # end edge snaps to start of c4
+        self.timeline.set_snapping_distance(1)
+        self.assertEdit(
+            c5, 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 18, None,
+            [], [],
+            {
+                c5 : {"start": 18, "layer": 0},
+                g1 : {"layer": 0, "duration": 21},
+            }, [], [(c4, c5, track)])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                  ....................g1.....................
+                  :                         *===============*
+                  :                         |       c5      |
+                  :                         *===============*
+        __________:_________________________________________:________
+        layer1____:_________________________________________:________
+                  :                                         :
+                  *===================*                     :
+                  |        c1         |                     :
+                  *===================*                     :
+                  :.........................................:
+            ...................g2....................
+            :..................g0...................:
+            *===============*                       :
+            |        c0     |                       :
+            *===============*                       :
+        ____:_______________________________________:________________
+        layer2______________________________________:________________
+            :                                       :
+            :                         *=============*
+            :                         |      c3     |
+            :                         *=============*
+            :.......................................:
+            *=========================*             :
+            |            c2           |             :
+            *=========================*             :
+            :.......................................:
+        _____________________________________________________________
+        layer3_______________________________________________________
+
+                                *===============*
+                                |       c4      |
+                                *===============*
+        """
+
+        # rippling g1 using c5
+        # initial position would make c1 go negative, but end edge of c1
+        # will snap to end of c0, allowing the edit to succeed
+        # c4 also moves because it is after the start of g1
+        self.timeline.set_snapping_distance(3)
+        self.assertEdit(
+            c5, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 12, 10,
+            [c1], [c0],
+            {
+                c5 : {"start": 13, "layer": 1},
+                c1 : {"start": 0, "layer": 2},
+                g1 : {"start": 0, "layer": 1},
+                c4 : {"start": 7, "layer": 4},
+            }, [(c1, c2, track)], [(c0, c1, track)])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+        ....................g1.....................
+        :                         *===============*
+        :                         |      c5       |
+        :                         *===============*
+        :   ...................g2.................:..
+        :   :..................g0.................:.:
+        :   *===============*                     : :
+        :   |        c0     |                     : :
+        :   *===============*                     : :
+        :___:_____________________________________:_:________________
+        layer2____________________________________:_:________________
+        :   :                                     : :
+        *===================*                     : :
+        |        c1         |                     : :
+        *===================*                     : :
+        :...:.....................................: :
+            :                         *=============*
+            :                         |      c3     |
+            :                         *=============*
+            :.......................................:
+            *=========================*             :
+            |            c2           |             :
+            *=========================*             :
+            :.......................................:
+        _____________________________________________________________
+        layer3_______________________________________________________
+        _____________________________________________________________
+        layer4_______________________________________________________
+
+                                *===============*
+                                |       c4      |
+                                *===============*
+        """
+        # moving start of c1 will move everything expect c5 because they
+        # can snap to c5 since it is not moving
+        # c1 and c2 keep transition
+        self.assertEdit(
+            c1, 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_START, 20, 21,
+            [c1], [c5],
+            {
+                c1 : {"start": 21, "layer": 1},
+                g1 : {"start": 13, "duration": 18},
+                c0 : {"start": 23, "layer": 0},
+                c2 : {"start": 23, "layer": 1},
+                c3 : {"start": 36, "layer": 1},
+                g0 : {"start": 23, "layer": 0},
+                g2 : {"start": 23, "layer": 0},
+                c4 : {"start": 28, "layer": 3},
+            }, [], [])
+
+    def test_trim(self):
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                  ..................g2...................
+                  :                                     :
+                  :...............g0.............       :
+                  :             *===============*       :
+                  :             |       a0      |       :
+                  :         *===*=====*=========*       :
+                  :         |    a1   |         :       :
+                  :         *=========*         :       :
+                  *===================*         :       :
+                  |        v0         |         :       :
+                  *===================*         :       :
+                  :.............................:       :
+        __________:_____________________________________:____________
+        layer1____:_____________________________________:____________
+                  :                                     :
+                  :         ............g1..............:
+                  :         :         *=================*
+                  :         :         |       a2        |
+                  :         :         *=================*
+                  :         *===========================*
+                  :         |           v1              |
+                  :         *===========================*
+        __________:_________:___________________________:____________
+        layer2____:_________:___________________________:____________
+                  *=============*     *=================*
+                  |      a3     |     |       a4        |
+                  *=============*     *=================*
+                  :         :...........................:
+                  *=========================*           :
+                  |           v2            |           :
+                  *=============*===========*===========*
+                  :             |           v3          |
+                  :             *=======================*
+                  :.....................................:
+        _____________________________________________________________
+        layer3_______________________________________________________
+
+        *===========================*       *===============*
+        |            v4             |       |       v5      |
+        *===========================*       *===============*
+        """
+        audio_track = self.add_audio_track()
+        video_track = self.add_video_track()
+        a0 = self.add_clip("a0", 0, [audio_track], 12, 8, 5, 15)
+        a1 = self.add_clip("a1", 0, [audio_track], 10, 5)
+        self.register_auto_transition(a1, a0, audio_track)
+        a2 = self.add_clip("a2", 1, [audio_track], 15, 9, 7, 19)
+        a3 = self.add_clip("a3", 2, [audio_track], 5, 7, 10)
+        a4 = self.add_clip("a4", 2, [audio_track], 15, 9)
+
+        v0 = self.add_clip("v0", 0, [video_track], 5, 10, 5)
+        v1 = self.add_clip("v1", 1, [video_track], 10, 14)
+        v2 = self.add_clip("v2", 2, [video_track], 5, 13, 4)
+        v3 = self.add_clip("v3", 2, [video_track], 12, 12)
+        self.register_auto_transition(v2, v3, video_track)
+        v4 = self.add_clip("v4", 3, [video_track], 0, 13)
+        v5 = self.add_clip("v5", 3, [video_track], 18, 8)
+
+        g0 = self.add_group("g0", [a0, a1, v0])
+        g1 = self.add_group("g1", [v1, a2, a4])
+        g2 = self.add_group("g2", [a3, v2, v3, g0, g1])
+
+        self.assertTimelineConfig()
+
+        # edit failures
+
+        # cannot trim end of g0 to 16 because a0 and a1 would fully
+        # overlap
+        self.assertFailEdit(
+            g0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot edit to new layer because there would be triple overlaps
+        # between v2, v3, v4 and v5
+        self.assertFailEdit(
+            g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 20,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot trim g1 end to 14 because it would result in a negative
+        # duration for a2 and a4
+        self.assertFailEdit(
+            g1, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 14,
+            GES.Error.NEGATIVE_TIME)
+
+        # cannot trim end of v2 below its start
+        self.assertFailEdit(
+            v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 2,
+            GES.Error.NEGATIVE_TIME)
+
+        # cannot trim end of g0 because a0's duration-limit would be
+        # exceeded
+        self.assertFailEdit(
+            g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23,
+            GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
+
+        # cannot trim g0 to 12 because a0 and a1 would fully overlap
+        self.assertFailEdit(
+            g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot trim start of v2 beyond its end point
+        self.assertFailEdit(
+            v2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 20,
+            GES.Error.NEGATIVE_TIME)
+
+        # with snapping
+        self.timeline.set_snapping_distance(4)
+
+        # cannot trim end of g2 to 19 because v1 and v2 would fully
+        # overlap after snapping to v5 start edge(18)
+        self.assertFailEdit(
+            g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 19,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot trim g2 to 3 because it would snap to start edge of
+        # v4(0), causing v2's in-point to be negative
+        self.assertFailEdit(
+            g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 3,
+            GES.Error.NEGATIVE_TIME)
+
+        # success
+
+        self.timeline.set_snapping_distance(2)
+
+        # first trim v4 start
+        self.assertEdit(
+            v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 1, None, [], [],
+            {
+                v4 : {"start": 1, "in-point": 1, "duration": 12},
+            }, [], [])
+
+        # and trim v5 end
+        self.assertEdit(
+            v5, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 25, 24,
+            [v5], [a2, a4, v1, v3],
+            {
+                v5 : {"duration": 6},
+            }, [], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                  ..................g2...................
+                  :                                     :
+                  :...............g0.............       :
+                  :             *===============*       :
+                  :             |       a0      |       :
+                  :         *===*=====*=========*       :
+                  :         |    a1   |         :       :
+                  :         *=========*         :       :
+                  *===================*         :       :
+                  |        v0         |         :       :
+                  *===================*         :       :
+                  :.............................:       :
+        __________:_____________________________________:____________
+        layer1____:_____________________________________:____________
+                  :                                     :
+                  :         ............g1..............:
+                  :         :         *=================*
+                  :         :         |       a2        |
+                  :         :         *=================*
+                  :         *===========================*
+                  :         |           v1              |
+                  :         *===========================*
+        __________:_________:___________________________:____________
+        layer2____:_________:___________________________:____________
+                  *=============*     *=================*
+                  |      a3     |     |       a4        |
+                  *=============*     *=================*
+                  :         :...........................:
+                  *=========================*           :
+                  |           v2            |           :
+                  *=============*===========*===========*
+                  :             |           v3          |
+                  :             *=======================*
+                  :.....................................:
+        _____________________________________________________________
+        layer3_______________________________________________________
+
+          *=========================*       *===========*
+          |          v4             |       |     v5    |
+          *=========================*       *===========*
+        """
+
+        # can trim g2 to 0 even though in-point of v2 is 4 because it will
+        # snap to 1. Note, there is only snapping on the start edge
+        # everything at the start edge is stretched back
+        self.assertEdit(
+            g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 0, 1,
+            [v0, v2, a3], [v4],
+            {
+                v0 : {"start": 1, "in-point": 1, "duration": 14},
+                a3 : {"start": 1, "in-point": 6, "duration": 11},
+                v2 : {"start": 1, "in-point": 0, "duration": 17},
+                g0 : {"start": 1, "duration": 19},
+                g2 : {"start": 1, "duration": 23},
+            }, [], [])
+
+        self.timeline.set_snapping_distance(0)
+
+        # trim end to use as a snapping point
+        self.assertEdit(
+            v4, 3, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 11, None, [], [],
+            {
+                v4 : {"duration": 10},
+            }, [], [])
+
+        self.timeline.set_snapping_distance(2)
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+          ......................g2.......................
+          :                                             :
+          :................g0....................       :
+          :                     *===============*       :
+          :                     |       a0      |       :
+          :                 *===*=====*=========*       :
+          :                 |    a1   |         :       :
+          :                 *=========*         :       :
+          *===========================*         :       :
+          |           v0              |         :       :
+          *===========================*         :       :
+          :.....................................:       :
+        __:_____________________________________________:____________
+        layer1__________________________________________:____________
+          :                                             :
+          :                 ............g1..............:
+          :                 :         *=================*
+          :                 :         |       a2        |
+          :                 :         *=================*
+          :                 *===========================*
+          :                 |           v1              |
+          :                 *===========================*
+        __:_________________:___________________________:____________
+        layer2______________:___________________________:____________
+          *=====================*     *=================*
+          |         a3          |     |       a4        |
+          *=====================*     *=================*
+          :                 :...........................:
+          *=================================*           :
+          |                v2               |           :
+          *=====================*===========*===========*
+          :                     |           v3          |
+          :                     *=======================*
+          :.............................................:
+        _____________________________________________________________
+        layer3_______________________________________________________
+
+          *===================*             *===========*
+          |        v4         |             |     v5    |
+          *===================*             *===========*
+        """
+
+        # can trim g2 to 12 even though it would cause a0 and a1 to fully
+        # overlap because the snapping allows it to succeed
+        self.assertEdit(
+            g2, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 12, 11,
+            [a3, v0, v2], [v4],
+            {
+                v0 : {"start": 11, "in-point": 11, "duration": 4},
+                a1 : {"start": 11, "in-point": 1, "duration": 4},
+                v1 : {"start": 11, "in-point": 1, "duration": 13},
+                a3 : {"start": 11, "in-point": 16, "duration": 1},
+                v2 : {"start": 11, "in-point": 10, "duration": 7},
+                g0 : {"start": 11, "duration": 9},
+                g1 : {"start": 11, "duration": 13},
+                g2 : {"start": 11, "duration": 13},
+            }, [], [])
+
+        # trim end to use as a snapping point
+        self.assertEdit(
+            v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 27, None, [], [],
+            {
+                v5 : {"duration": 9, "layer": 4},
+            }, [], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                              .............g2............
+                              :                         :
+                              :.......g0.........       :
+                              : *===============*       :
+                              : |       a0      |       :
+                              *=*=====*=========*       :
+                              |  a1   |         :       :
+                              *=======*         :       :
+                              *=======*         :       :
+                              |  v0   |         :       :
+                              *=======*         :       :
+                              :.................:       :
+        ______________________:_________________________:____________
+        layer1________________:_________________________:____________
+                              :                         :
+                              :.........g1..............:
+                              :       *=================*
+                              :       |       a2        |
+                              :       *=================*
+                              *=========================*
+                              |         v1              |
+                              *=========================*
+        ______________________:_________________________:____________
+        layer2________________:_________________________:____________
+                              *=*     *=================*
+                              a3|     |       a4        |
+                              *=*     *=================*
+                              :.........................:
+                              *=============*           :
+                              |      v2     |           :
+                              *=*===========*===========*
+                              : |           v3          |
+                              : *=======================*
+                              :.........................:
+        _____________________________________________________________
+        layer3_______________________________________________________
+
+          *===================*
+          |        v4         |
+          *===================*
+        _____________________________________________________________
+        layer4_______________________________________________________
+
+                                            *=================*
+                                            |         v5      |
+                                            *=================*
+        """
+
+        # trim end of g2 and move layer. Without the snap, would fail since
+        # a2's duration-limit is 12.
+        # Even elements not being trimmed will still move layer
+        # a0 and a1 keep transition
+        # v2 and v3 keep transition
+        self.assertEdit(
+            g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 29, 27,
+            [a2, a4, v1, v3], [v5],
+            {
+                a2 : {"duration": 12, "layer": 2},
+                v1 : {"duration": 16, "layer": 2},
+                a4 : {"duration": 12, "layer": 3},
+                v3 : {"duration": 15, "layer": 3},
+                g1 : {"duration": 16, "layer": 2},
+                g2 : {"duration": 16, "layer": 1},
+                a0 : {"layer": 1},
+                a1 : {"layer": 1},
+                v0 : {"layer": 1},
+                a3 : {"layer": 3},
+                v2 : {"layer": 3},
+                g0 : {"layer": 1},
+            }, [], [])
+
+        # trim start to use as a snapping point
+        self.timeline.set_snapping_distance(0)
+        self.assertEdit(
+            v5, 4, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 19, None,
+            [], [],
+            {
+                v5 : {"start": 19, "in-point": 1, "duration": 8},
+            }, [], [])
+
+        self.timeline.set_snapping_distance(2)
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+                              .............g2..................
+                              :                               :
+                              :.......g0.........             :
+                              : *===============*             :
+                              : |       a0      |             :
+                              *=*=====*=========*             :
+                              |  a1   |         :             :
+                              *=======*         :             :
+                              *=======*         :             :
+                              |  v0   |         :             :
+                              *=======*         :             :
+                              :.................:             :
+        ______________________:_______________________________:______
+        layer2________________:_______________________________:______
+                              :                               :
+                              :.........g1....................:
+                              :       *=======================*
+                              :       |           a2          |
+                              :       *=======================*
+                              *===============================*
+                              |               v1              |
+                              *===============================*
+        ______________________:_______________________________:______
+        layer3________________:_______________________________:______
+                              *=*     *=======================*
+                              a3|     |           a4          |
+                              *=*     *=======================*
+                              :...............................:
+          *===================*=============*                 :
+          |        v4         |      v2     |                 :
+          *===================*=*===========*=================*
+                              : |                v3           |
+                              : *=============================*
+                              :...............................:
+        _____________________________________________________________
+        layer4_______________________________________________________
+
+                                              *===============*
+                                              |       v5      |
+                                              *===============*
+        """
+
+        # trim end of g2 and move layer. Trim at 17 would lead to
+        # v3 being fully overlapped by v2, but snap to 19 makes it work
+        self.assertEdit(
+            g2, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 19,
+            [a2, a4, v1, v3], [v5],
+            {
+                a0 : {"duration": 7},
+                a2 : {"duration": 4},
+                v1 : {"duration": 8},
+                a4 : {"duration": 4},
+                v3 : {"duration": 7},
+                g0 : {"duration": 8},
+                g1 : {"duration": 8},
+                g2 : {"duration": 8},
+            }, [], [])
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+                              ........g2.......
+                              :               :
+                              :.......g0......:
+                              : *=============*
+                              : |      a0     |
+                              *=*=====*=======*
+                              |  a1   |       :
+                              *=======*       :
+                              *=======*       :
+                              |  v0   |       :
+                              *=======*       :
+                              :...............:
+        ______________________:_______________:______________________
+        layer2________________:_______________:______________________
+                              :               :
+                              :.......g1......:
+                              :       *=======*
+                              :       |   a2  |
+                              :       *=======*
+                              *===============*
+                              |       v1      |
+                              *===============*
+        ______________________:_______________:______________________
+        layer3________________:_______________:______________________
+                              *=*     *=======*
+                              a3|     |   a4  |
+                              *=*     *=======*
+                              :...............:
+          *===================*=============* :
+          |        v4         |      v2     | :
+          *===================*=*===========*=*
+                              : |     v3      |
+                              : *=============*
+                              :...............:
+        _____________________________________________________________
+        layer4_______________________________________________________
+
+                                              *===============*
+                                              |       v5      |
+                                              *===============*
+        """
+
+        # can trim without trimming parent
+        self.assertEdit(
+            v0, 1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5, None, [], [],
+            {
+                v0 : {"start": 5, "in-point": 5, "duration": 10},
+                g0 : {"start": 5, "duration": 14},
+                g2 : {"start": 5, "duration": 14},
+            }, [], [])
+
+        self.assertEdit(
+            a2, 2, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 23, None, [], [],
+            {
+                a2 : {"duration": 8},
+                g1 : {"duration": 12},
+                g2 : {"duration": 18},
+            }, [], [])
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+        _____________________________________________________________
+        layer1_______________________________________________________
+
+                  ...................g2................
+                  :                                   :
+                  :...........g0...............       :
+                  :             *=============*       :
+                  :             |      a0     |       :
+                  :           *=*=====*=======*       :
+                  :           |  a1   |       :       :
+                  :           *=======*       :       :
+                  *===================*       :       :
+                  |         v0        |       :       :
+                  *===================*       :       :
+                  :...........................:       :
+        __________:___________________________________:______________
+        layer2____:___________________________________:______________
+                  :                                   :
+                  :           ............g1..........:
+                  :           :       *===============*
+                  :           :       |       a2      |
+                  :           :       *===============*
+                  :           *===============*       :
+                  :           |       v1      |       :
+                  :           *===============*       :
+        __________:___________:_______________________:______________
+        layer3____:___________:_______________________:______________
+                  :           *=*     *=======*       :
+                  :           a3|     |   a4  |       :
+                  :           *=*     *=======*       :
+                  :           :.......................:
+          *===================*=============*         :
+          |        v4         |      v2     |         :
+          *===================*=*===========*=*       :
+                  :             |     v3      |       :
+                  :             *=============*       :
+                  :...................................:
+        _____________________________________________________________
+        layer4_______________________________________________________
+
+                                              *===============*
+                                              |       v5      |
+                                              *===============*
+        """
+        # same with group within a group
+        self.assertEdit(
+            g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 9, 11,
+            [v0], [v1, v2, v4, a3],
+            {
+                v0 : {"start": 11, "in-point": 11, "duration": 4, "layer": 0},
+                a0 : {"layer": 0},
+                a1 : {"layer": 0},
+                g0 : {"start": 11, "duration": 8, "layer": 0},
+                g2 : {"start": 11, "duration": 12, "layer": 0},
+            }, [], [])
+
+        self.assertEdit(
+            g0, 0, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 17, 18,
+            [a0], [v2],
+            {
+                a0 : {"duration": 6},
+                g0 : {"duration": 7},
+            }, [], [])
+
+
+    def test_roll(self):
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                      *===============================*                ]
+                      |               c0              |                ]
+        *=============*=====*===================*=====*=============*  ]>video
+        |        c1         |                   |         c2        |  ]
+        *===================*                   *===================*  ]
+                            ..........g0.........
+                            *===========*       :                      ]
+                            |     c3    |       :                      ]>audio0
+                            *===*=======*===*   :                      ]
+                            :   |    c4     |   :                      ] ]
+                            :   *===*=======*===*                        ]
+                            :       |    c5     |                        ]>audio1
+                            :       *===========*                        ]
+        ____________________:___________________:____________________
+        layer1______________:___________________:____________________
+                            :                   :
+        .............g3.....:....               :
+        :     *=================*               :                      ]
+        :     |      c12        |           ....:...g4...............  ]
+        :     *=================*           :   :                   :  ]
+        :........g1.........:   :           :   :.........g2........:  ]>audio0
+        *=============*     :   :           :   *=============*     :  ]
+        |     c8      |     :   :           :   |      c10    |     :  ]
+        *=============*     :   :     *=========*=============*     :  ]
+        :                   :   :     |   c7    |                   :  ] ]
+        :                   *=========*=========*                   :    ]>video
+        :                   |    c6   |     :   :                   :  ] ]
+        :     *=============*=========*     :   :     *=============*  ]
+        :     |     c9      |...:...........:...:     |     c11     |  ]
+        :     *=============*   :           :   :     *=============*  ]
+        :...................:   :           :   :...................:  ]>audio1
+        :.......................:           *=================*     :  ]
+                                            |        c13      |     :  ]
+                                            *=================*     :  ]
+                                            :.......................:
+        """
+        video = self.add_video_track()
+        audio0 = self.add_audio_track()
+        audio1 = self.add_audio_track()
+
+        c0 = self.add_clip("c0", 0, [video], 7, 16)
+        c1 = self.add_clip("c1", 0, [video], 0, 10)
+        c2 = self.add_clip("c2", 0, [video], 20, 10, 20)
+        self.register_auto_transition(c1, c0, video)
+        self.register_auto_transition(c0, c2, video)
+
+        c3 = self.add_clip("c3", 0, [audio0], 10, 6, 2, 38)
+        c4 = self.add_clip("c4", 0, [audio0, audio1], 12, 6, 15)
+        self.register_auto_transition(c3, c4, audio0)
+        c5 = self.add_clip("c5", 0, [audio1], 14, 6, 30, 38)
+        self.register_auto_transition(c4, c5, audio1)
+        c6 = self.add_clip("c6", 1, [audio1, video], 10, 5, 7)
+        c7 = self.add_clip("c7", 1, [audio0, video], 15, 5, 1, 15)
+        g0 = self.add_group("g0", [c3, c4, c5, c6, c7])
+
+        c8 = self.add_clip("c8", 1, [audio0], 0, 7, 3, 13)
+        c9 = self.add_clip("c9", 1, [audio1], 3, 7)
+        g1 = self.add_group("g1", [c8, c9])
+        c10 = self.add_clip("c10", 1, [audio0], 20, 7, 1)
+        c11 = self.add_clip("c11", 1, [audio1], 23, 7, 3, 10)
+        g2 = self.add_group("g2", [c10, c11])
+
+        c12 = self.add_clip("c12", 1, [audio0], 3, 9)
+        self.register_auto_transition(c8, c12, audio0)
+        g3 = self.add_group("g3", [g1, c12])
+        c13 = self.add_clip("c13", 1, [audio1], 18, 9)
+        self.register_auto_transition(c13, c11, audio1)
+        g4 = self.add_group("g4", [g2, c13])
+
+        self.assertTimelineConfig()
+
+        # edit failures
+        self.timeline.set_snapping_distance(2)
+
+        # cannot roll c10 to 22, which snaps to 23, because it will
+        # extend c5 beyond its duration limit of 8
+        self.assertFailEdit(
+            c10, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22,
+            GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
+
+        # same with g2
+        self.assertFailEdit(
+            g2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22,
+            GES.Error.NOT_ENOUGH_INTERNAL_CONTENT)
+
+        # cannot roll end c9 to 8, which snaps to 7, because it would
+        # cause c3's in-point to become negative
+        self.assertFailEdit(
+            c9, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8,
+            GES.Error.NEGATIVE_TIME)
+
+        # same with g1
+        self.assertFailEdit(
+            g1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8,
+            GES.Error.NEGATIVE_TIME)
+
+        # cannot roll c13 to 19, snap to 20, because it would cause
+        # c4 to fully overlap c5
+        self.assertFailEdit(
+            c13, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 19,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # cannot roll c12 to 11, snap to 10, because it would cause
+        # c3 to fully overlap c4
+        self.assertFailEdit(
+            c12, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 11,
+            GES.Error.INVALID_OVERLAP_IN_TRACK)
+
+        # give c6 a bit more allowed duration so we can focus on c9
+        self.assertTrue(c6.set_inpoint(10))
+        self.assertTimelineConfig({ c6 : {"in-point": 10}})
+        # cannot roll c6 to 0 because it would cause c9 to be trimmed
+        # below its start
+        self.assertFailEdit(
+            c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 0,
+            GES.Error.NEGATIVE_TIME)
+        # set back
+        self.assertTrue(c6.set_inpoint(7))
+        self.assertTimelineConfig({ c6 : {"in-point": 7}})
+
+        # give c7 a bit more allowed duration so we can focus on c10
+        self.assertTrue(c7.set_inpoint(0))
+        self.assertTimelineConfig({ c7 : {"in-point": 0}})
+        # cannot roll end c7 to 30 because it would cause c10 to be
+        # trimmed beyond its end
+        self.assertFailEdit(
+            c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 30,
+            GES.Error.NEGATIVE_TIME)
+        # set back
+        self.assertTrue(c7.set_inpoint(1))
+        self.assertTimelineConfig({ c7 : {"in-point": 1}})
+
+        # moving layer is not supported
+        self.assertFailEdit(
+            c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, None)
+        self.assertFailEdit(
+            c0, 2, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, None)
+
+        # successes
+        self.timeline.set_snapping_distance(0)
+
+        # c1 and g1 are trimmed at their end
+        # NOTE: c12 is not trimmed even though it shares a group g3
+        # with g1 because g3 does not share the same edge
+        # trim forward
+        self.assertEdit(
+            c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None,
+            [], [],
+            {
+                c6 : {"start": 11, "in-point": 8, "duration": 4},
+                c1 : {"duration": 11},
+                c9 : {"duration": 8},
+                g1 : {"duration": 11},
+            }, [], [])
+        # and reset
+        self.assertEdit(
+            c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None,
+            [], [],
+            {
+                c6 : {"start": 10, "in-point": 7, "duration": 5},
+                c1 : {"duration": 10},
+                c9 : {"duration": 7},
+                g1 : {"duration": 10},
+            }, [], [])
+
+        # same with g0
+        self.assertEdit(
+            g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 11, None,
+            [], [],
+            {
+                c6 : {"start": 11, "in-point": 8, "duration": 4},
+                c3 : {"start": 11, "in-point": 3, "duration": 5},
+                g0 : {"start": 11, "duration": 9},
+                c1 : {"duration": 11},
+                c9 : {"duration": 8},
+                g1 : {"duration": 11},
+            }, [], [])
+        self.assertEdit(
+            g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None,
+            [], [],
+            {
+                c6 : {"start": 10, "in-point": 7, "duration": 5},
+                c3 : {"start": 10, "in-point": 2, "duration": 6},
+                g0 : {"start": 10, "duration": 10},
+                c1 : {"duration": 10},
+                c9 : {"duration": 7},
+                g1 : {"duration": 10},
+            }, [], [])
+
+        self.timeline.set_snapping_distance(1)
+        # trim backward
+        # NOTE: c9 has zero width, not considered overlapping with c6
+        # snapping allows the edit to succeed (in-point of c6 no longer
+        # negative)
+        # NOTE: c12 does not move, but c8 does because it is in the same
+        # group as g1
+        # loose transitions
+        self.assertEdit(
+            c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 2, 3,
+            [c6], [c12],
+            {
+                c6 : {"start": 3, "in-point": 0, "duration": 12},
+                g0 : {"start": 3, "duration": 17},
+                c1 : {"duration": 3},
+                c8 : {"duration": 3},
+                c9 : {"duration": 0},
+                g1 : {"duration": 3},
+            }, [], [(c1, c0, video), (c8, c12, audio0)])
+
+        # bring back
+        # NOTE: no snapping to c3 start edge because it is part of the
+        # element being edited, g0, even though it doesn't end up changing
+        # gain back new transitions
+        self.assertEdit(
+            g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 10, None,
+            [], [],
+            {
+                c6 : {"start": 10, "in-point": 7, "duration": 5},
+                g0 : {"start": 10, "duration": 10},
+                c1 : {"duration": 10},
+                c8 : {"duration": 10},
+                c9 : {"duration": 7},
+                g1 : {"duration": 10},
+            }, [(c1, c0, video), (c8, c12, audio0)], [])
+
+
+        # same but with the end edge of g0
+        self.timeline.set_snapping_distance(0)
+        self.assertEdit(
+            c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [],
+            {
+                c7 : {"duration": 4},
+                c2 : {"start": 19, "in-point": 19, "duration": 11},
+                c10 : {"start": 19, "in-point": 0, "duration": 8},
+                g2 : {"start": 19, "duration": 11},
+            }, [], [])
+        self.assertEdit(
+            c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [],
+            {
+                c7 : {"duration": 5},
+                c2 : {"start": 20, "in-point": 20, "duration": 10},
+                c10 : {"start": 20, "in-point": 1, "duration": 7},
+                g2 : {"start": 20, "duration": 10},
+            }, [], [])
+        # do same with g0
+        self.assertEdit(
+            g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 19, None, [], [],
+            {
+                c7 : {"duration": 4},
+                c5 : {"duration": 5},
+                g0 : {"duration": 9},
+                c2 : {"start": 19, "in-point": 19, "duration": 11},
+                c10 : {"start": 19, "in-point": 0, "duration": 8},
+                g2 : {"start": 19, "duration": 11},
+            }, [], [])
+        self.assertEdit(
+            g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [],
+            {
+                c7 : {"duration": 5},
+                c5 : {"duration": 6},
+                g0 : {"duration": 10},
+                c2 : {"start": 20, "in-point": 20, "duration": 10},
+                c10 : {"start": 20, "in-point": 1, "duration": 7},
+                g2 : {"start": 20, "duration": 10},
+            }, [], [])
+
+        self.timeline.set_snapping_distance(1)
+        # trim forwards
+        # NOTE: c10 has zero width, not considered overlapping with c7
+        # snapping allows the edit to succeed (duration of c7 no longer
+        # above its limit)
+        # NOTE: c12 does not move, but c11 does because it is in the same
+        # group as g2
+        self.assertEdit(
+            c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 28, 27,
+            [c7], [c13],
+            {
+                c7 : {"duration": 12},
+                g0 : {"duration": 17},
+                c2 : {"start": 27, "in-point": 27, "duration": 3},
+                c10 : {"start": 27, "in-point": 8, "duration": 0},
+                c11 : {"start": 27, "in-point": 7, "duration": 3},
+                g2 : {"start": 27, "duration": 3},
+            }, [], [(c0, c2, video), (c13, c11, audio1)])
+        # bring back using g0
+        # NOTE: no snapping to c5 end edge because it is part of the
+        # element being edited, g0, even though it doesn't end up changing
+        self.assertEdit(
+            g0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 20, None, [], [],
+            {
+                c7 : {"duration": 5},
+                g0 : {"duration": 10},
+                c2 : {"start": 20, "in-point": 20, "duration": 10},
+                c10 : {"start": 20, "in-point": 1, "duration": 7},
+                c11 : {"start": 20, "in-point": 0, "duration": 10},
+                g2 : {"start": 20, "duration": 10},
+            }, [(c0, c2, video), (c13, c11, audio1)], [])
+
+        # adjust c0 for snapping
+        # doesn't move anything else
+        self.assertEdit(
+            c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 8, None,
+            [], [],
+            {
+                c0 : {"start": 8, "in-point": 1, "duration": 15},
+            }, [], [])
+        self.assertEdit(
+            c0, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 22, None, [], [],
+            {
+                c0 : {"duration": 14},
+            }, [], [])
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                        *===========================*                  ]
+                        |             c0            |                  ]
+        *===============*===*===================*===*===============*  ]>video
+        |        c1         |                   |         c2        |  ]
+        *===================*                   *===================*  ]
+                            ..........g0.........
+                            *===========*       :                      ]
+                            |     c3    |       :                      ]>audio0
+                            *===*=======*===*   :                      ]
+                            :   |    c4     |   :                      ] ]
+                            :   *===*=======*===*                        ]
+                            :       |    c5     |                        ]>audio1
+                            :       *===========*                        ]
+        ____________________:___________________:____________________
+        layer1______________:___________________:____________________
+                            :                   :
+        .............g3.....:....               :
+        :     *=================*               :                      ]
+        :     |      c12        |           ....:...g4...............  ]
+        :     *=================*           :   :                   :  ]
+        :........g1.........:   :           :   :.........g2........:  ]>audio0
+        *===================*   :           :   *=============*     :  ]
+        |        c8         |   :           :   |      c10    |     :  ]
+        *===================*   :     *=========*=============*     :  ]
+        :                   :   :     |   c7    |                   :  ] ]
+        :                   *=========*=========*                   :    ]>video
+        :                   |    c6   |     :   :                   :  ] ]
+        :     *=============*=========*     :   *===================*  ]
+        :     |     c9      |...:...........:...|        c11        |  ]
+        :     *=============*   :           :   *===================*  ]
+        :...................:   :           :   :...................:  ]>audio1
+        :.......................:           *=================*     :  ]
+                                            |        c13      |     :  ]
+                                            *=================*     :  ]
+                                            :.......................:
+        """
+        # rolling only moves an element if it contains a source that
+        # touches the rolling edge. For a group, any source below it
+        # at the corresponding edge counts, we also prefer trimming the
+        # whole group over just one of its childrens.
+        # As such, when rolling the end of c5, c11 shares the audio1
+        # track and starts when c5 ends, so is set to be trimmed. But it
+        # is also at the start edge of its parent g2, so g2 is set to be
+        # trimmed. However, it is not at the start of g4, so g4 is not
+        # set to be trimmed. As such, c10 will also move, even though it
+        # does not share a track. c2, on the other hand, will not move
+        # NOTE: snapping helps keep c5's duration below its limit (8)
+        self.assertEdit(
+            c5, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 23, 22,
+            [c5], [c0],
+            {
+                c5 : {"duration": 8},
+                g0 : {"duration": 12},
+                c11 : {"start": 22, "in-point": 2, "duration": 8},
+                c10 : {"start": 22, "in-point": 3, "duration": 5},
+                g2 : {"start": 22, "duration": 8},
+            }, [], [])
+
+        # same with c3 at its start edge
+        self.assertEdit(
+            c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 7, 8,
+            [c3], [c0],
+            {
+                c3 : {"start": 8, "in-point": 0, "duration": 8},
+                g0 : {"start": 8, "duration": 14},
+                c8 : {"duration": 8},
+                c9 : {"duration": 5},
+                g1 : {"duration": 8},
+            }, [], [])
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                        *===========================*                  ]
+                        |             c0            |                  ]
+        *===============*===*===================*===*===============*  ]>video
+        |        c1         |                   |         c2        |  ]
+        *===================*                   *===================*  ]
+                        ..............g0.............
+                        *===============*           :                  ]
+                        |      c3       |           :                  ]>audio0
+                        *=======*=======*===*       :                  ]
+                        :       |    c4     |       :                  ] ]
+                        :       *===*=======*=======*                    ]
+                        :           |      c5       |                    ]>audio1
+                        :           *===============*                    ]
+        ________________:___________________________:________________
+        layer1__________:___________________________:________________
+                        :                           :
+        .............g3.:........                   :
+        :     *=================*                   :                  ]
+        :     |      c12        |           ........:.g4.............  ]
+        :     *=================*           :       :               :  ]
+        :........g1.....:       :           :       :.....g2........:  ]>audio0
+        *===============*       :           :       *=========*     :  ]
+        |      c8       |       :           :       |   c10   |     :  ]
+        *===============*       :     *=========*   *=========*     :  ]
+        :               :       :     |   c7    |   :               :  ] ]
+        :               :   *=========*=========*   :               :    ]>video
+        :               :   |    c6   |     :       :               :  ] ]
+        :     *=========*   *=========*     :       *===============*  ]
+        :     |   c9    |.......:...........:.......|     c11       |  ]
+        :     *=========*       :           :       *===============*  ]
+        :...............:       :           :       :...............:  ]>audio1
+        :.......................:           *=================*     :  ]
+                                            |        c13      |     :  ]
+                                            *=================*     :  ]
+                                            :.......................:
+        """
+        # rolling end of c1 only moves c6, similarly with c2 and c7
+        self.assertEdit(
+            c1, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 8, 8,
+            [c1], [c9, c8, c3, c0],
+            {
+                c1 : {"duration": 8},
+                c6 : {"start": 8, "in-point": 5, "duration": 7},
+            }, [], [(c1, c0, video)])
+        self.assertEdit(
+            c2, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 22, 22,
+            [c2], [c0, c5, c10, c11],
+            {
+                c2 : {"start": 22, "in-point": 22, "duration": 8},
+                c7 : {"duration": 7},
+            }, [], [(c0, c2, video)])
+
+        # move c3 end edge out the way
+        self.timeline.set_snapping_distance(0)
+        self.assertEdit(
+            c3, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 17, None, [], [],
+            {
+                c3: {"duration": 9},
+            }, [], [])
+
+        self.timeline.set_snapping_distance(2)
+
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                        *===========================*                  ]
+                        |             c0            |                  ]
+        *===============*===========================*===============*  ]>video
+        |       c1      |                           |      c2       |  ]
+        *===============*                           *===============*  ]
+                        ..............g0.............
+                        *=================*         :                  ]
+                        |      c3         |         :                  ]>audio0
+                        *=======*=========*=*       :                  ]
+                        :       |    c4     |       :                  ] ]
+                        :       *===*=======*=======*                    ]
+                        :           |      c5       |                    ]>audio1
+                        :           *===============*                    ]
+        ________________:___________________________:________________
+        layer1__________:___________________________:________________
+                        :                           :
+        .............g3.:........                   :
+        :     *=================*                   :                  ]
+        :     |      c12        |           ........:.g4.............  ]
+        :     *=================*           :       :               :  ]
+        :........g1.....:       :           :       :.....g2........:  ]>audio0
+        *===============*       :           :       *=========*     :  ]
+        |      c8       |       :           :       |   c10   |     :  ]
+        *===============*       :     *=============*=========*     :  ]
+        :               :       :     |     c7      |               :  ] ]
+        :               *=============*=============*               :    ]>video
+        :               |      c6     |     :       :               :  ] ]
+        :     *=========*=============*     :       *===============*  ]
+        :     |   c9    |.......:...........:.......|     c11       |  ]
+        :     *=========*       :           :       *===============*  ]
+        :...............:       :           :       :...............:  ]>audio1
+        :.......................:           *=================*     :  ]
+                                            |        c13      |     :  ]
+                                            *=================*     :  ]
+                                            :.......................:
+        """
+
+        # can safely roll within a group
+        # NOTE: we do not snap to an edge used in the edit
+        self.assertEdit(
+            c6, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, 15, 14,
+            [c6], [c5],
+            {
+                c6: {"duration": 6},
+                c7: {"start": 14, "in-point": 0, "duration": 8},
+            }, [], [])
+        self.assertEdit(
+            c7, -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 16, 17,
+            [c7], [c3],
+            {
+                c6: {"duration": 9},
+                c7: {"start": 17, "in-point": 3, "duration": 5},
+            }, [], [])
+
+    def test_snap_from_negative(self):
+        track = self.add_video_track()
+        c0 = self.add_clip("c0", 0, [track], 0, 20)
+        c1 = self.add_clip("c1", 0, [track], 100, 10)
+        g1 = self.add_group("g0", [c0, c1])
+        snap_to = self.add_clip("snap-to", 2, [track], 4, 50)
+
+        self.assertTimelineConfig()
+
+        self.timeline.set_snapping_distance(9)
+        # move without snap would make start edge of c0 go to -5, but this
+        # edge snaps to the start edge of snap_to, allowing the edit to
+        # succeed
+        self.assertEdit(
+            c1, 1, GES.EditMode.NORMAL, GES.Edge.NONE, 95, 4, [c0], [snap_to],
+            {
+                c0 : {"start": 4, "layer": 1},
+                c1 : {"start": 104, "layer": 1},
+                g1 : {"start": 4, "layer": 1},
+            }, [], [])
+
+    def test_move_layer(self):
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                                      *===================*
+                                      |         c1        |
+                                      *===================*
+                  ..............g2...............
+                  :                             :
+                  :.............g0..............:
+                  *=============================*
+                  |             c0              |
+                  *=============================*
+                  :                             :
+                  :                             :
+        __________:_____________________________:____________________
+        layer1____:_____________________________:____________________
+                  :                             :
+                  *===================*         :
+                  |        c2         |         :
+                  *===================*         :
+                  :.............................:
+                  :         ...............g1...:..........
+                  :         *=============================*
+                  :         |              c3             |
+                  :         *=============================*
+        __________:_________:_____________________________:__________
+        layer2____:_________:_____________________________:__________
+                  :         :                             :
+                  :         :         *===================*
+                  :         :         |        c5         |
+                  :         :.........*===================*
+                  *===================*         :
+                  |         c4        |         :
+                  *===================*         :
+                  :.............................:
+                            *===================*
+                            |         c6        |
+                            *===================*
+        """
+        track = self.add_video_track()
+        c0 = self.add_clip("c0", 0, [track], 5, 15)
+        c1 = self.add_clip("c1", 0, [track], 15, 10)
+        self.register_auto_transition(c0, c1, track)
+        c2 = self.add_clip("c2", 1, [track], 5, 10)
+        c3 = self.add_clip("c3", 1, [track], 10, 15)
+        self.register_auto_transition(c2, c3, track)
+        c4 = self.add_clip("c4", 2, [track], 5, 10)
+        c5 = self.add_clip("c5", 2, [track], 15, 10)
+        c6 = self.add_clip("c6", 2, [track], 10, 10)
+        self.register_auto_transition(c4, c6, track)
+        self.register_auto_transition(c6, c5, track)
+
+        g0 = self.add_group("g0", [c0, c2])
+        g1 = self.add_group("g1", [c3, c5])
+        g2 = self.add_group("g2", [g0, c4])
+
+        self.assertTimelineConfig()
+
+        layer = self.timeline.get_layer(0)
+        self.assertIsNotNone(layer)
+
+        # don't loose auto-transitions
+        # clips stay in their layer (groups do not move them)
+        self.timeline.move_layer(layer, 2)
+        self.assertTimelineConfig(
+            {
+                c0 : {"layer": 2},
+                c1 : {"layer": 2},
+                c2 : {"layer": 0},
+                c3 : {"layer": 0},
+                c4 : {"layer": 1},
+                c5 : {"layer": 1},
+                c6 : {"layer": 1},
+                g1 : {"layer": 0},
+            })
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                  ..............g2...............
+                  :                             :
+                  :.............g0..............:
+                  *===================*         :
+                  |        c2         |         :
+                  *===================*         :
+                  :   ..........................:
+                  :   :     ...............g1..............
+                  :   :     *=============================*
+                  :   :     |              c3             |
+                  :   :     *=============================*
+        __________:___:_____:_____________________________:__________
+        layer1____:___:_____:_____________________________:__________
+                  :   :     :                             :
+                  :   :     :         *===================*
+                  :   :     :         |        c5         |
+                  :   :     :         *===================*
+                  :   :     :.............................:
+                  :   :..........g2.....
+                  :   g0               :
+                  :   :                :
+                  *===================*:
+                  |         c4        |:
+                  *===================*:
+                  :   :................:
+                  :   :     *===================*
+                  :   :     |         c6        |
+                  :   :     *===================*
+        __________:___:______________________________________________
+        layer2____:___:______________________________________________
+                  :   :..........................
+                  *=============================*
+                  |             c0              |
+                  *=============================*
+                  :.............................:
+                  :.............................:
+                                      *===================*
+                                      |         c1        |
+                                      *===================*
+        """
+        layer = self.timeline.get_layer(1)
+        self.assertIsNotNone(layer)
+        self.timeline.move_layer(layer, 0)
+        self.assertTimelineConfig(
+            {
+                c2 : {"layer": 1},
+                c3 : {"layer": 1},
+                c4 : {"layer": 0},
+                c5 : {"layer": 0},
+                c6 : {"layer": 0},
+                g0 : {"layer": 1},
+            })
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                            *===================*
+                            |         c6        |
+                            *===================*
+
+                  ..............g2...............
+                  *===================*         :
+                  |         c4        |         :
+                  *===================*         :
+                  :         ...............g1...:..........
+                  :         :         *===================*
+                  :         :         |        c5         |
+                  :         :         *===================*
+        __________:_________:___________________:_________:__________
+        layer1____:_________:___________________:_________:__________
+                  :         :                   :         :
+                  :         *=============================*
+                  :         |              c3             |
+                  :         *=============================*
+                  :         :...................:.........:
+                  :.............g0..............:
+                  *===================*         :
+                  |        c2         |         :
+                  *===================*         :
+                  :.............................:
+        __________:_____________________________:____________________
+        layer2____:_____________________________:____________________
+                  :                             :
+                  *=============================*
+                  |             c0              |
+                  *=============================*
+                  :.............................:
+                  :.............................:
+                                      *===================*
+                                      |         c1        |
+                                      *===================*
+        """
+        self.timeline.append_layer()
+        layer = self.timeline.get_layer(3)
+        self.assertIsNotNone(layer)
+        self.timeline.move_layer(layer, 1)
+        self.assertTimelineConfig(
+            {
+                c0 : {"layer": 3},
+                c1 : {"layer": 3},
+                c2 : {"layer": 2},
+                c3 : {"layer": 2},
+                g0 : {"layer": 2},
+            })
+        layer = self.timeline.get_layer(3)
+        self.assertIsNotNone(layer)
+        self.timeline.move_layer(layer, 0)
+        self.assertTimelineConfig(
+            {
+                c0 : {"layer": 0},
+                c1 : {"layer": 0},
+                c2 : {"layer": 3},
+                c3 : {"layer": 3},
+                c4 : {"layer": 1},
+                c5 : {"layer": 1},
+                c6 : {"layer": 1},
+                g0 : {"layer": 0},
+                g1 : {"layer": 1},
+            })
+        """
+        , . . . . , . . . . , . . . . , . . . . , . . . . , . . . . ,
+        0         5         10        15        20        25        30
+        _____________________________________________________________
+        layer0_______________________________________________________
+
+                                      *===================*
+                                      |         c1        |
+                                      *===================*
+                  ..............g2...............
+                  :.............g0..............:
+                  *=============================*
+                  |             c0              |
+                  *=============================*
+                  :   ..........................:
+        __________:___:______________________________________________
+        layer1____:___:______________________________________________
+                  :   :
+                  :   :.......g2.......
+                  :   g0              :
+                  :   :               :
+                  *===================*
+                  |         c4        |
+                  *===================*
+                  :   :...............:
+                  :   :     *===================*
+                  :   :     |         c6        |
+                  :   :     *===================*
+                  :   :     ...............g1..............
+                  :   :     :         *===================*
+                  :   :     :         |        c5         |
+                  :   :     :         *===================*
+        __________:___:_____:_____________________________:__________
+        layer2____:___:_____:_____________________________:__________
+        __________:___:_____:_____________________________:__________
+        layer3____:___:_____:_____________________________:__________
+                  :   :     :                             :
+                  :   :     *=============================*
+                  :   :     |              c3             |
+                  :   :     *=============================*
+                  :   :     :.............................:
+                  :   :..........................
+                  *===================*         :
+                  |        c2         |         :
+                  *===================*         :
+                  :.............................:
+                  :.............................:
+        """
+        layer = self.timeline.get_layer(1)
+        self.assertTrue(self.timeline.remove_layer(layer))
+
+        # TODO: add tests when removing layers:
+        # FIXME: groups should probably loose their children when they
+        # are removed from the timeline, which would change g1's
+        # priority, but currently c5 remains in the group with priority
+        # of the removed layer
+
+    def test_not_snappable(self):
+        track = self.add_video_track()
+        c0 = self.add_clip("c0", 0, [track], 0, 10)
+        no_source = self.add_clip(
+            "no-source", 0, [], 5, 10, effects=[GES.Effect.new("agingtv")])
+        effect_clip = self.add_clip(
+            "effect-clip", 0, [track], 5, 10, clip_type=GES.EffectClip,
+            asset_id="agingtv || audioecho")
+        text = self.add_clip(
+            "text-clip", 0, [track], 5, 10, clip_type=GES.TextOverlayClip)
+
+        self.assertTimelineConfig()
+
+        self.timeline.set_snapping_distance(20)
+
+        self.assertEdit(
+            c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 8, None,
+            [], [], {c0 : {"start": 8}}, [], [])
+        self.assertEdit(
+            c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_START, 5, None,
+            [], [], {c0 : {"start": 5}}, [], [])
+        self.assertEdit(
+            c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_END, 8, None,
+            [], [], {c0 : {"duration": 3}}, [], [])
+
+        c1 = self.add_clip("c1", 0, [track], 30, 3)
+        self.assertTimelineConfig()
+
+        # end edge snaps to start of c1
+        self.assertEdit(
+            c0, 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10, 30,
+            [c0], [c1], {c0 : {"start": 27}}, [], [])
+
 
 class TestTransitions(common.GESSimpleTimelineTest):
 
@@ -930,11 +3699,6 @@ class TestTransitions(common.GESSimpleTimelineTest):
         <clip id='1' asset-id='bar-wipe-lr' type-name='GESTransitionClip' layer-priority='0' track-types='4' start='3225722559' duration='1333196743' inpoint='0' rate='0' properties='properties, name=(string)transitionclip84;'  children-properties='properties, GESVideoTransition::border=(uint)0, GESVideoTransition::invert=(boolean)false;'/>
         <clip id='2' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='0' track-types='6' start='3225722559' duration='3479110239' inpoint='4558919302' rate='0' properties='properties, name=(string)uriclip25265, mute=(boolean)false, is-image=(boolean)false;' />
       </layer>
-      <layer priority='1' properties='properties, auto-transition=(boolean)true;' metadatas='metadatas, volume=(float)1;'>
-        <clip id='3' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='1' track-types='4' start='8566459322' duration='1684610449' inpoint='0' rate='0' properties='properties, name=(string)uriclip25266, mute=(boolean)false, is-image=(boolean)true;' />
-        <clip id='4' asset-id='GESTitleClip' type-name='GESTitleClip' layer-priority='1' track-types='6' start='8566459322' duration='4500940746' inpoint='0' rate='0' properties='properties, name=(string)titleclip69;' />
-        <clip id='5' asset-id='%(uri)s' type-name='GESUriClip' layer-priority='1' track-types='4' start='9566459322' duration='4363207710' inpoint='0' rate='0' properties='properties, name=(string)uriclip25275, mute=(boolean)false, is-image=(boolean)true;' />
-      </layer>
       <groups>
       </groups>
     </timeline>
@@ -948,13 +3712,16 @@ class TestTransitions(common.GESSimpleTimelineTest):
             timeline = project.extract()
 
             mainloop = common.create_main_loop()
-            mainloop.run(until_empty=True)
+            def loaded_cb(unused_project, unused_timeline):
+                mainloop.quit()
+            project.connect("loaded", loaded_cb)
+
+            mainloop.run()
 
             layers = timeline.get_layers()
-            self.assertEqual(len(layers), 2)
+            self.assertEqual(len(layers), 1)
 
             self.assertTrue(layers[0].props.auto_transition)
-            self.assertTrue(layers[1].props.auto_transition)
 
     def test_transition_type(self):
         xges = self.create_xges()
@@ -963,10 +3730,13 @@ class TestTransitions(common.GESSimpleTimelineTest):
             timeline = project.extract()
 
             mainloop = common.create_main_loop()
-            mainloop.run(until_empty=True)
+            def loaded_cb(unused_project, unused_timeline):
+                mainloop.quit()
+            project.connect("loaded", loaded_cb)
+            mainloop.run()
 
             layers = timeline.get_layers()
-            self.assertEqual(len(layers), 2)
+            self.assertEqual(len(layers), 1)
 
             clips = layers[0].get_clips()
             clip1 = clips[0]
@@ -977,10 +3747,6 @@ class TestTransitions(common.GESSimpleTimelineTest):
             self.assertLess(clip1.props.start + clip1.props.duration, clip2.props.start + clip2.props.duration)
             self.assertEqual(len(clips), 3)
 
-            # 3 clips would be overlapping, 1 of them wasn't added!
-            clips = layers[1].get_clips()
-            self.assertEqual(len(clips), 3)
-
 
 class TestPriorities(common.GESSimpleTimelineTest):
 
diff --git a/tests/check/scenarios/check-clip-positioning.validatetest b/tests/check/scenarios/check-clip-positioning.validatetest
new file mode 100644 (file)
index 0000000..2d19769
--- /dev/null
@@ -0,0 +1,12 @@
+meta,
+    tool = "ges-launch-$(gst_api_version)",
+    handles-states=true,
+    args = {
+        +test-clip, blue, "d=0.5", "asset-id=time-overlay, width=640, height=360, max-duration=5.0", "name=clip",
+        --track-types, video,
+        --videosink, "$(videosink) name=videosink sync=true",
+        --video-caps, "video/x-raw,format=I420,width=640,height=360,framerate=10/1,chroma-site=mpeg2,colorimetry=bt709;",
+    }
+
+check-child-properties, element-name=clip, width=0, height=0
+stop
\ No newline at end of file
diff --git a/tests/check/scenarios/check_edit_in_frames.scenario b/tests/check/scenarios/check_edit_in_frames.scenario
new file mode 100644 (file)
index 0000000..39a1eb5
--- /dev/null
@@ -0,0 +1,31 @@
+description, handles-states=true,
+    ges-options={\
+        --track-types, video\
+     }
+
+add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=f0, inpoint=f30, duration=f60
+
+check-ges-properties, element-name=clip, start=0, in-point=1.0, duration=2.0
+edit, element-name=clip, position=f30
+
+check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0
+
+# Getting the 60th frame in the input media file, means inpoint=f30 + f30 = f60
+edit, element-name=clip, position=f60, edit-mode=edit_trim, edge=edge_end
+check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=1.0
+
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1280,height=720,framerate=60/1"
+
+# 60 frames in media time, meaning 90 - inpoint (30) / 30 = 2 seconds
+edit, element-name=clip, source-frame=90, edit-mode=edit_trim, edge=edge_end
+check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0
+
+# 60 frames in timeline time, meaning 60/60 = 1 second
+edit, element-name=clip, position=f60
+check-ges-properties, element-name=clip, start=1.0, in-point=1.0, duration=2.0
+
+# 60 frames in timeline time, meaning 60/60 = 1 second
+edit, element-name=clip, source-frame=75, edit-mode=trim, edge=start
+check-ges-properties, element-name=clip, start=2.5, in-point=2.5, duration=0.5
+
+stop
\ No newline at end of file
diff --git a/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario b/tests/check/scenarios/check_edit_in_frames_with_framerate_mismatch.scenario
new file mode 100644 (file)
index 0000000..e5e37ed
--- /dev/null
@@ -0,0 +1,23 @@
+description, handles-states=true,
+    ges-options={\
+        --track-types, video,
+        --disable-mixing,
+        "--videosink=fakevideosink"\
+    }
+
+add-clip, name=clip, asset-id="time-overlay,framerate=120/1", layer-priority=0, type=GESSourceClip, pattern=blue, duration=f240, inpoint=f100
+set-child-properties, element-name=clip, time-mode=time-code
+pause
+
+check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=100
+
+edit, element-name=clip, edit-mode=normal, position=1.0
+
+edit, element-name=clip, edit-mode=edit_trim, edge=start, source-frame=60
+edit, element-name=clip, position=0
+commit;
+check-last-sample, sinkpad-caps="video/x-raw", timecode-frame-number=60
+
+edit, element-name=clip, edit-mode=edit_trim, edge=start, source-frame=120
+check-ges-properties, element-name=clip, start=0.5
+stop
diff --git a/tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest b/tests/check/scenarios/check_keyframes_in_compositor_two_sources.validatetest
new file mode 100644 (file)
index 0000000..cbe1062
--- /dev/null
@@ -0,0 +1,111 @@
+meta,
+    tool = "ges-launch-$(gst_api_version)",
+    args = {
+        -t, video,
+        --videosink, "$(videosink) name=videosink sync=true",
+        --video-caps, "video/x-raw,width=500,height=500,framerate=10/1,format=I420,colorimetry=(string)bt601,chroma-site=(string)jpeg",
+    },
+    handles-states=true,
+    ignore-eos=true,
+    configs = {
+        "$(validateflow), pad=videosink:sink, buffers-checksum=as-id, ignored-fields=\"stream-start={stream-id,group-id, stream}\"",
+    },
+    expected-issues = {
+        # Blending is not 100% reproducible when seeking/changing values for
+        # some reason. It has been manually checked and the contents are very
+        # slightly off and ssim would detect no difference at all between the
+        # images
+        "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=10.*\\\\n.*\\\\n.*content-id=12.*\", sometimes=true",
+        "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=12.*\\\\n.*\\\\n.*content-id=10.*\", sometimes=true",
+        "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=0.*\\\\n.*\\\\n.*content-id=12.*\", sometimes=true",
+        "expected-issue, issue-id=validateflow::mismatch, details=\".*content-id=0.*\\\\n.*\\\\n.*content-id=13.*\", sometimes=true",
+    }
+
+remove-feature, name=queue
+
+add-clip, name=c0, asset-id="pattern=blue", layer-priority=0, type=GESTestClip, start=0, duration=2.0, inpoint=2.0
+add-clip, name=c1, asset-id="pattern=green", layer-priority=1, type=GESTestClip, start=0, duration=2.0
+set-child-properties, element-name=c0, width=250, height=250, pattern=blue
+set-child-properties, element-name=c1, width=250, height=250, pattern=green
+
+set-control-source, element-name=c0, property-name=posx, binding-type=direct-absolute
+set-control-source, element-name=c0, property-name=posy, binding-type=direct-absolute
+set-control-source, element-name=c1, property-name=posx, binding-type=direct-absolute
+set-control-source, element-name=c1, property-name=posy, binding-type=direct-absolute
+
+# c0 starts in the top left corner going to the bottom right at 1sec and going back to the top right at 2s
+# Keyframes are in 'source stream time' so we take the inpoint into account for the timestamp
+add-keyframe, element-name=c0, timestamp=2.0, property-name=posx, value=0
+add-keyframe, element-name=c0, timestamp=2.0, property-name=posy, value=0
+add-keyframe, element-name=c1, timestamp=0.0, property-name=posx, value=250
+add-keyframe, element-name=c1, timestamp=0.0, property-name=posy, value=250
+
+add-keyframe, element-name=c0, timestamp=3.0, property-name=posx, value=250
+add-keyframe, element-name=c0, timestamp=3.0, property-name=posy, value=250
+add-keyframe, element-name=c1, timestamp=1.0, property-name=posx, value=0
+add-keyframe, element-name=c1, timestamp=1.0, property-name=posy, value=0
+
+add-keyframe, element-name=c0, timestamp=4.0, property-name=posx, value=0
+add-keyframe, element-name=c0, timestamp=4.0, property-name=posy, value=0
+add-keyframe, element-name=c1, timestamp=2.0, property-name=posx, value=250
+add-keyframe, element-name=c1, timestamp=2.0, property-name=posy, value=250
+
+play
+
+check-properties,
+    gessmartmixer0-compositor.sink_0::xpos=0,  gessmartmixer0-compositor.sink_0::ypos=0,
+    gessmartmixer0-compositor.sink_1::xpos=250, gessmartmixer0-compositor.sink_1::ypos=250
+
+crank-clock
+wait, on-clock=true
+
+check-properties,
+    gessmartmixer0-compositor.sink_0::xpos=25,  gessmartmixer0-compositor.sink_0::ypos=25,
+    gessmartmixer0-compositor.sink_1::xpos=225, gessmartmixer0-compositor.sink_1::ypos=225
+
+# Check the 5th buffer
+crank-clock, repeat=4
+wait, on-clock=true
+check-properties,
+    gessmartmixer0-compositor.sink_0::xpos=125, gessmartmixer0-compositor.sink_0::ypos=125,
+    gessmartmixer0-compositor.sink_1::xpos=125,   gessmartmixer0-compositor.sink_1::ypos=125
+
+crank-clock
+check-position, expected-position=0.5
+
+# Check the 10th buffer
+crank-clock, repeat=4
+wait, on-clock=true
+check-properties,
+    gessmartmixer0-compositor.sink_0::xpos=250, gessmartmixer0-compositor.sink_0::ypos=250,
+    gessmartmixer0-compositor.sink_1::xpos=0,   gessmartmixer0-compositor.sink_1::ypos=0
+
+crank-clock
+check-position, expected-position=1.0
+
+crank-clock, repeat=11
+check-position, on-message=eos, expected-position=2000000001
+
+seek, start=1.0, flags=accurate+flush
+wait, on-clock=true
+
+# 10th buffer
+check-properties,
+    gessmartmixer0-compositor.sink_3::xpos=250, gessmartmixer0-compositor.sink_3::ypos=250,
+    gessmartmixer0-compositor.sink_4::xpos=0,   gessmartmixer0-compositor.sink_4::ypos=0
+
+set-ges-properties, element-name=c0, start=1000000000
+set-ges-properties, element-name=c1, start=1000000000
+commit;
+wait, on-clock=true
+
+check-position, expected-position=1.0
+
+# First buffer
+check-properties,
+    gessmartmixer0-compositor.sink_3::xpos=0,
+    gessmartmixer0-compositor.sink_3::ypos=0,
+    gessmartmixer0-compositor.sink_4::xpos=250,
+    gessmartmixer0-compositor.sink_4::ypos=250
+
+stop
diff --git a/tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/check_keyframes_in_compositor_two_sources/flow-expectations/log-videosink-sink-expected
new file mode 100644 (file)
index 0000000..707a669
--- /dev/null
@@ -0,0 +1,34 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE;
+event caps: video/x-raw, chroma-site=(string)jpeg, colorimetry=(string)bt601, format=(string)I420, framerate=(fraction)10/1, height=(int)500, width=(int)500;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:02.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none
+buffer: content-id=0, pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=1, pts=0:00:00.100000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=2, pts=0:00:00.200000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=3, pts=0:00:00.300000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=4, pts=0:00:00.400000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=5, pts=0:00:00.500000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=6, pts=0:00:00.600000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=7, pts=0:00:00.700000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=8, pts=0:00:00.800000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=9, pts=0:00:00.900000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=10, pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=9, pts=0:00:01.100000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=8, pts=0:00:01.200000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=7, pts=0:00:01.300000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=6, pts=0:00:01.400000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=5, pts=0:00:01.500000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=4, pts=0:00:01.600000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=3, pts=0:00:01.700000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=2, pts=0:00:01.800000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: content-id=1, pts=0:00:01.900000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event segment: format=TIME, start=0:00:02.000000000, offset=0:00:00.000000000, stop=0:00:02.000000001, flags=0x01, time=0:00:02.000000000, base=0:00:02.000000000, position=none
+buffer: content-id=11, pts=0:00:02.000000000, dur=0:00:00.000000001, meta=GstVideoMeta
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:02.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none
+buffer: content-id=10, pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:03.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none
+buffer: content-id=0, pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
diff --git a/tests/check/scenarios/check_layer_activness_gaps.scenario b/tests/check/scenarios/check_layer_activness_gaps.scenario
new file mode 100644 (file)
index 0000000..382f516
--- /dev/null
@@ -0,0 +1,24 @@
+description, handles-states=true,
+    ges-options={\
+        "--disable-mixing",
+        "--videosink=fakevideosink",
+        "--audiosink=fakeaudiosink"\
+    }
+
+add-clip, name=clip, asset-id="framerate=30/1", layer-priority=0, type=GESTestClip, pattern=blue, duration=5000.0
+set-layer-active, tracks={gesvideotrack0}, active=false, layer-priority=0
+
+pause;
+
+# Make sure the video test src is a gap test src.
+check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="100% Black"
+check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Sine"
+
+set-layer-active, tracks={gesvideotrack0}, active=true, layer-priority=0
+set-layer-active, tracks={gesaudiotrack0}, active=false, layer-priority=0
+commit;
+# Make sure the video test src is the GESVideoTestSource and the audio test source is a gap
+check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="Blue"
+check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Silence"
+
+stop;
\ No newline at end of file
diff --git a/tests/check/scenarios/check_video_track_restriction_scale.scenario b/tests/check/scenarios/check_video_track_restriction_scale.scenario
new file mode 100644 (file)
index 0000000..9ec1722
--- /dev/null
@@ -0,0 +1,58 @@
+description, handles-states=true,
+    ges-options={\
+        --track-types, video,
+        --video-caps, "video/x-raw,width=1200,height=1000" \
+    }
+
+add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, duration=1.0
+
+# VideoTestSource natural size is 1280x720, so keeping aspect ratio, mean
+# that it will be scaled to 1200x675 (placed at x=0, y=163)
+check-child-properties, element-name=clip, width=1200, height=675, posx=0, posy=163
+
+set-child-properties, element-name=clip, width=1024, height=768
+check-child-properties, element-name=clip, width=1024, height=768
+
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1400,height=1200"
+check-child-properties, element-name=clip, width=1024, height=768
+
+set-child-properties, element-name=clip, width=0
+check-child-properties, element-name=clip, width=0, height=768
+
+set-child-properties, element-name=clip, height=0
+check-child-properties, element-name=clip, width=0, height=0
+
+set-child-properties, element-name=clip, width=1400, height=1200
+check-child-properties, element-name=clip, width=1400, height=1200
+
+# Changing track size, keeping aspect ratio should scale the video source
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=700,height=600"
+check-child-properties, element-name=clip, width=700, height=600
+
+# The video source has the same size as the track restriction caps but we
+# are changing the aspect ratio, the video should thus not be rescaled. */
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080"
+check-child-properties, element-name=clip, width=700, height=600
+
+set-child-properties, element-name=clip, width=1280, height=720, posx=320, posy=240
+check-child-properties, element-name=clip, width=1280, height=720, posx=320, posy=240
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=960,height=540"
+check-child-properties, element-name=clip, width=640, height=360, posx=160, posy=120
+
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1280,height=720"
+set-child-properties, element-name=clip, width=128, height=72, posx=-100, posy=-100
+check-child-properties, element-name=clip, width=128, height=72, posx=-100, posy=-100
+
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080"
+check-child-properties, element-name=clip, width=192, height=108, posx=-150, posy=-150
+
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=192,height=108"
+check-child-properties, element-name=clip, width=19, height=11, posx=-15, posy=-15
+
+set-child-properties, element-name=clip, posx=10, posy=-10
+
+# Make sure we do not lose precision when going back to previous size
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080"
+check-child-properties, element-name=clip, width=192, height=108, posx=100, posy=-100
+
+stop
\ No newline at end of file
diff --git a/tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario b/tests/check/scenarios/check_video_track_restriction_scale_with_keyframes.scenario
new file mode 100644 (file)
index 0000000..0c0b95e
--- /dev/null
@@ -0,0 +1,32 @@
+description, handles-states=true, seek=true,
+    ges-options={\
+        --track-types, video,
+        --video-caps, "video/x-raw,width=1280,height=720,framerate=30/1" \
+    }
+
+add-clip, name=clip, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=0.0, duration=1.0, pattern=blue
+
+set-control-source, element-name=videotestsource0, property-name=width, binding-type=direct-absolute, source-type=interpolation
+set-control-source, element-name=videotestsource0, property-name=height, binding-type=direct, source-type=interpolation
+
+
+# Goes from 1280x720 at 0, to 640x360 at 0.5 then back to 1280x720 ar 1.0
+add-keyframe, element-name=videotestsource0, property-name="width", timestamp=0.0, value=(gint)1280
+add-keyframe, element-name=videotestsource0, property-name="height", timestamp=0.0, value=0.0072
+
+add-keyframe, element-name=videotestsource0, property-name="width", timestamp=0.5, value=(gint)640
+add-keyframe, element-name=videotestsource0, property-name="height", timestamp=0.5, value=0.0036
+
+add-keyframe, element-name=videotestsource0, property-name="width", timestamp=1.0, value=(gint)1280
+add-keyframe, element-name=videotestsource0, property-name="height", timestamp=1.0, value=0.0072
+
+check-child-properties, element-name=videotestsource0, width=1280, height=720, posx=0, posy=0, at-time=0.0
+check-child-properties, element-name=videotestsource0, width=640, height=360, posx=0, posy=0, at-time=0.5
+check-child-properties, element-name=videotestsource0, width=1280, height=720, posx=0, posy=0, at-time=1.0
+
+set-track-restriction-caps, track-type=video, caps="video/x-raw,width=1920,height=1080"
+check-child-properties, element-name=videotestsource0, width=1920, height=1080, posx=0, posy=0, at-time=0.0
+check-child-properties, element-name=videotestsource0, width=960, height=540, posx=0, posy=0, at-time=0.5
+check-child-properties, element-name=videotestsource0, width=1920, height=1080, posx=0, posy=0, at-time=1.0
+
+stop;
\ No newline at end of file
diff --git a/tests/check/scenarios/complex_effect_bin_desc.validatetest b/tests/check/scenarios/complex_effect_bin_desc.validatetest
new file mode 100644 (file)
index 0000000..d958544
--- /dev/null
@@ -0,0 +1,24 @@
+# Check that we can have effect with sources integrated where GES will request a pad on some elements
+# In that example, we are blending a green rectangle on top of a blue GESVideoTestSource using an effect
+meta,
+    tool = "ges-launch-$(gst_api_version)",
+    handles-states=true,
+    args = {
+        "--track-types", "video",
+        "--videosink", "$(videosink) name=videosink",
+        "--video-caps", "video/x-raw, format=I420, width=1280, height=720, framerate=30/1, chroma-site=jpeg, colorimetry=bt601",
+    },
+    configs = {
+        "$(validateflow), pad=videosink:sink, buffers-checksum=true, ignored-fields=\"stream-start={stream-id,group-id,stream}, segment={position,}\", ignored-event-types={gap}",
+    }
+
+
+add-clip, name=c0, asset-id=GESTestClip, layer-priority=0, type=GESTestClip, start=0, duration=0.1
+set-child-properties, element-name=c0, pattern=blue
+
+container-add-child,
+    container-name=c0,
+    asset-id="videotestsrc pattern=green ! video/x-raw,width=640,height=360 ! compositor sink_0::xpos=320 sink_0::ypos=180 sink_0::zorder=500",
+    child-type=GESEffect,
+    child-name=effect
+play
\ No newline at end of file
diff --git a/tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/complex_effect_bin_desc/flow-expectations/log-videosink-sink-expected
new file mode 100644 (file)
index 0000000..b4b70bc
--- /dev/null
@@ -0,0 +1,9 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE;
+event caps: video/x-raw, chroma-site=(string)jpeg, colorimetry=(string)bt601, format=(string)I420, framerate=(fraction)30/1, height=(int)720, width=(int)1280;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.100000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: checksum=369888c2612267760fcfaa74e52fc53bd73e4d15, pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: checksum=369888c2612267760fcfaa74e52fc53bd73e4d15, pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: checksum=369888c2612267760fcfaa74e52fc53bd73e4d15, pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta
+event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=0:00:00.100000001, flags=0x01, time=0:00:00.100000000, base=0:00:00.100000000
+buffer: checksum=b4a126ab26f314a74ef860a9af457327a28d680b, pts=0:00:00.100000000, dur=0:00:00.000000001, meta=GstVideoMeta
+event eos: (no structure)
diff --git a/tests/check/scenarios/edit_while_seeked_with_stop.validatetest b/tests/check/scenarios/edit_while_seeked_with_stop.validatetest
new file mode 100644 (file)
index 0000000..0a96bee
--- /dev/null
@@ -0,0 +1,58 @@
+meta,
+    tool = "ges-launch-$(gst_api_version)",
+    args = {
+        --track-types, video,
+        --videosink, "$(videosink) name=videosink",
+        --video-caps, "video/x-raw,format=I420,width=1280,height=720,framerate=10/1,chroma-site=mpeg2,colorimetry=bt709",
+    },
+    handles-states=true,
+    ignore-eos=true,
+    configs = {
+        # Ideally we should be able to record checksums... but they are not reproducible
+        "$(validateflow), pad=videosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}\"",
+    }
+
+add-clip, name=c0, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=0, duration=1.0
+set-child-properties, element-name=c0, pattern=blue, valignment=center, halignment=center, time-mode=time-code
+
+add-clip, name=c1, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=1.0, duration=1.0
+set-child-properties, element-name=c1, pattern=red, valignment=center, halignment=center, time-mode=time-code
+commit;
+play
+
+seek, start=0.0, stop=0.5, flags=accurate+flush
+
+edit, element-name=c0, position=0.5, edge=end, edit-mode=trim
+commit;
+
+crank-clock, expected-elapsed-time=0.0
+crank-clock, repeat=5, expected-elapsed-time=0.1
+check-position, on-message=eos, expected-position=0.5
+
+seek, start=0.5, stop=1.0, flags=accurate+flush
+
+edit, element-name=c1, position=5.0, edge=end, edit-mode=trim
+commit;
+
+crank-clock, expected-elapsed-time=0.0
+crank-clock, repeat=5, expected-elapsed-time=0.1
+check-position, on-message=eos, expected-position=1.0
+
+edit, element-name=c1, position=3.0, edge=end, edit-mode=trim
+commit;
+check-position, on-message=eos, expected-position=1.0
+
+seek, start=1.0, stop=2.0, flags=accurate+flush
+check-position, expected-position=1.0
+
+edit, element-name=c1, position=1.5, edge=end, edit-mode=trim
+commit;
+
+crank-clock, expected-elapsed-time=0.0
+crank-clock, repeat=5, expected-elapsed-time=0.1
+
+# Last 1ns clip added by GES
+crank-clock, repeat=1, expected-elapsed-time=(guint64)1
+check-position, on-message=eos, expected-position=1500000001
+
+stop;
\ No newline at end of file
diff --git a/tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/edit_while_seeked_with_stop/flow-expectations/log-videosink-sink-expected
new file mode 100644 (file)
index 0000000..57d0771
--- /dev/null
@@ -0,0 +1,49 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE;
+event caps: video/x-raw, chroma-site=(string)mpeg2, colorimetry=(string)bt709, format=(string)I420, framerate=(fraction)10/1, height=(int)720, width=(int)1280;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.100000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.200000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.300000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.400000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.500000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.500000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.500000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.500000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.500000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.500000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.600000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.700000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.800000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.900000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:02.000000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.500000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:01.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:01.100000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:01.200000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:01.300000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:01.400000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event segment: format=TIME, start=0:00:01.500000000, offset=0:00:00.000000000, stop=0:00:01.500000001, flags=0x01, time=0:00:01.500000000, base=0:00:00.500000000, position=none
+buffer: pts=0:00:01.500000000, dur=0:00:00.000000001, meta=GstVideoMeta
+event eos: (no structure)
diff --git a/tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest b/tests/check/scenarios/seek_with_stop.check_clock_sync.validatetest
new file mode 100644 (file)
index 0000000..0c67936
--- /dev/null
@@ -0,0 +1,30 @@
+meta,
+    tool = "ges-launch-$(gst_api_version)",
+    args = {
+        --track-types, video,
+        --videosink, "$(videosink) name=videosink",
+        --video-caps, "video/x-raw,format=I420,width=1280,height=720,framerate=10/1,chroma-site=mpeg2,colorimetry=bt709;",
+    },
+    handles-states=true,
+    ignore-eos=true,
+    configs = {
+        # Ideally we should be able to record checksums... but they are not reproducible
+        "$(validateflow), pad=videosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}\"",
+    }
+
+add-clip, name=c0, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=0, duration=1.0
+set-child-properties, element-name=c0, pattern=blue, valignment=center, halignment=center, time-mode=time-code
+
+add-clip, name=c1, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=1.0, duration=1.0
+set-child-properties, element-name=c1, pattern=red, valignment=center, halignment=center, time-mode=time-code
+commit;
+play
+
+seek, start=0.0, stop=0.5, flags=accurate+flush
+crank-clock, expected-elapsed-time=0.0
+
+# 4 remaining buffers + eos.
+crank-clock, repeat=5, expected-elapsed-time=0.1
+
+check-position, on-message=eos, expected-position=0.5
+stop;
\ No newline at end of file
diff --git a/tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/seek_with_stop.check_clock_sync/flow-expectations/log-videosink-sink-expected
new file mode 100644 (file)
index 0000000..75364c1
--- /dev/null
@@ -0,0 +1,13 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE;
+event caps: video/x-raw, chroma-site=(string)mpeg2, colorimetry=(string)bt709, format=(string)I420, framerate=(fraction)10/1, height=(int)720, width=(int)1280;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000, position=none
+buffer: pts=0:00:00.000000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.100000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.200000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.300000000, dur=0:00:00.100000000, meta=GstVideoMeta
+buffer: pts=0:00:00.400000000, dur=0:00:00.100000000, meta=GstVideoMeta
+event eos: (no structure)
diff --git a/tests/check/scenarios/seek_with_stop.validatetest b/tests/check/scenarios/seek_with_stop.validatetest
new file mode 100644 (file)
index 0000000..582d0a4
--- /dev/null
@@ -0,0 +1,28 @@
+meta,
+    tool = "ges-launch-$(gst_api_version)",
+    args = {
+        --videosink, "$(videosink) sync=false name=videosink",
+        --audiosink, "$(audiosink) sync=false name=audiosink",
+        --video-caps, "video/x-raw,format=I420,width=1280,height=720,framerate=30/1,chroma-site=mpeg2,colorimetry=bt709",
+    },
+    handles-states=true,
+    ignore-eos=true,
+    configs = {
+        "$(validateflow), pad=videosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}, segment={position,}\", ignored-event-types={gap}",
+        "$(validateflow), pad=audiosink:sink, record-buffers=true, ignored-fields=\"stream-start={stream-id,group-id,stream}, segment={position,}\", ignored-event-types={gap}",
+    }
+
+add-clip, name=c0, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=0, duration=1.0
+set-child-properties, element-name=c0, pattern=blue, valignment=center, halignment=center, time-mode=time-code
+
+add-clip, name=c1, asset-id=time-overlay, layer-priority=0, type=GESSourceClip, start=1.0, duration=1.0
+set-child-properties, element-name=c1, pattern=red, valignment=center, halignment=center, time-mode=time-code
+pause
+
+seek, start=0.0, stop=0.5, flags=accurate+flush
+play
+
+seek, on-message=eos, start=0.0, stop=0.5, flags=accurate+flush
+seek, on-message=eos, start=0.0, stop=1.0, flags=accurate+flush
+seek, on-message=eos, start=1.0, stop=1.5, flags=accurate+flush
+stop, on-message=eos
\ No newline at end of file
diff --git a/tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected b/tests/check/scenarios/seek_with_stop/flow-expectations/log-audiosink-sink-expected
new file mode 100644 (file)
index 0000000..17df415
--- /dev/null
@@ -0,0 +1,270 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE;
+event caps: audio/x-raw, channel-mask=(bitmask)0x0000000000000003, channels=(int)2, format=(string)S32LE, layout=(string)interleaved, rate=(int)44100;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.010000000
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.010000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.020000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.030000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.040000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.050000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.060000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.070000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.080000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.090000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.100000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.110000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.120000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.130000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.140000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.150000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.160000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.170000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.180000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.190000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.200000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.210000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.220000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.230000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.240000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.250000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.260000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.270000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.280000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.290000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.300000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.310000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.320000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.330000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.340000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.350000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.360000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.370000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.380000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.390000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.400000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.410000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.420000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.430000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.440000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.450000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.460000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.470000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.480000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.490000000, dur=0:00:00.010000000
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.010000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.020000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.030000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.040000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.050000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.060000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.070000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.080000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.090000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.100000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.110000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.120000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.130000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.140000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.150000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.160000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.170000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.180000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.190000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.200000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.210000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.220000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.230000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.240000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.250000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.260000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.270000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.280000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.290000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.300000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.310000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.320000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.330000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.340000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.350000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.360000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.370000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.380000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.390000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.400000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.410000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.420000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.430000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.440000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.450000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.460000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.470000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.480000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.490000000, dur=0:00:00.010000000
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.010000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.020000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.030000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.040000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.050000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.060000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.070000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.080000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.090000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.100000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.110000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.120000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.130000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.140000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.150000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.160000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.170000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.180000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.190000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.200000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.210000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.220000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.230000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.240000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.250000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.260000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.270000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.280000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.290000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.300000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.310000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.320000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.330000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.340000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.350000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.360000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.370000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.380000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.390000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.400000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.410000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.420000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.430000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.440000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.450000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.460000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.470000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.480000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.490000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.500000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.510000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.520000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.530000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.540000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.550000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.560000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.570000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.580000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.590000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.600000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.610000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.620000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.630000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.640000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.650000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.660000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.670000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.680000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.690000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.700000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.710000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.720000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.730000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.740000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.750000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.760000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.770000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.780000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.790000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.800000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.810000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.820000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.830000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.840000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.850000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.860000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.870000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.880000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.890000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.900000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.910000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.920000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.930000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.940000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.950000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.960000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.970000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.980000000, dur=0:00:00.010000000
+buffer: pts=0:00:00.990000000, dur=0:00:00.010000000
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.500000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000
+buffer: pts=0:00:01.000000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.010000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.020000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.030000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.040000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.050000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.060000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.070000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.080000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.090000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.100000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.110000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.120000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.130000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.140000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.150000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.160000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.170000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.180000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.190000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.200000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.210000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.220000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.230000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.240000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.250000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.260000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.270000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.280000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.290000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.300000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.310000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.320000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.330000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.340000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.350000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.360000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.370000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.380000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.390000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.400000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.410000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.420000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.430000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.440000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.450000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.460000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.470000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.480000000, dur=0:00:00.010000000
+buffer: pts=0:00:01.490000000, dur=0:00:00.010000000
+event eos: (no structure)
diff --git a/tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected b/tests/check/scenarios/seek_with_stop/flow-expectations/log-videosink-sink-expected
new file mode 100644 (file)
index 0000000..626460f
--- /dev/null
@@ -0,0 +1,95 @@
+event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE;
+event caps: video/x-raw, chroma-site=(string)mpeg2, colorimetry=(string)bt709, format=(string)I420, framerate=(fraction)30/1, height=(int)720, width=(int)1280;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.100000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.133333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.166666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.200000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.233333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.266666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.300000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.333333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.366666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.400000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.433333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.466666667, dur=0:00:00.033333333, meta=GstVideoMeta
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:00.500000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.100000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.133333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.166666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.200000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.233333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.266666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.300000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.333333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.366666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.400000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.433333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.466666667, dur=0:00:00.033333333, meta=GstVideoMeta
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=0:00:01.000000000, flags=0x01, time=0:00:00.000000000, base=0:00:00.000000000
+buffer: pts=0:00:00.000000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.033333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.066666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.100000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.133333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.166666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.200000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.233333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.266666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.300000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.333333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.366666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.400000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.433333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.466666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.500000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.533333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.566666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.600000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.633333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.666666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.700000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.733333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.766666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.800000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.833333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.866666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.900000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:00.933333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:00.966666667, dur=0:00:00.033333333, meta=GstVideoMeta
+event eos: (no structure)
+event flush-start: (no structure)
+event flush-stop: GstEventFlushStop, reset-time=(boolean)true;
+event segment: format=TIME, start=0:00:01.000000000, offset=0:00:00.000000000, stop=0:00:01.500000000, flags=0x01, time=0:00:01.000000000, base=0:00:00.000000000
+buffer: pts=0:00:01.000000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.033333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:01.066666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.100000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.133333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:01.166666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.200000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.233333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:01.266666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.300000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.333333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:01.366666667, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.400000000, dur=0:00:00.033333333, meta=GstVideoMeta
+buffer: pts=0:00:01.433333333, dur=0:00:00.033333334, meta=GstVideoMeta
+buffer: pts=0:00:01.466666667, dur=0:00:00.033333333, meta=GstVideoMeta
+event eos: (no structure)
diff --git a/tests/check/scenarios/set-layer-on-command-line.validatetest b/tests/check/scenarios/set-layer-on-command-line.validatetest
new file mode 100644 (file)
index 0000000..d590bb8
--- /dev/null
@@ -0,0 +1,11 @@
+meta, handles-states=true,
+    tool = "ges-launch-$(gst_api_version)",
+    handles-states=true,
+    args = {
+        +test-clip, blue, "d=0.5", "layer=0", "name=blue",
+        +test-clip, green, "d=0.5", "layer=1", "name=green",
+    }
+
+check-ges-properties, element-name=blue, layer::priority=0
+check-ges-properties, element-name=green, layer::priority=1
+stop
\ No newline at end of file
diff --git a/tests/validate/Makefile.am b/tests/validate/Makefile.am
deleted file mode 100644 (file)
index 2a59db7..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-SUBDIRS = scenarios
-
-validatedir=${libdir}/gst-validate-launcher/python/launcher/apps/
-
-validate_DATA = geslaunch.py
-
-EXTRA_DIST = geslaunch.py
index ad1e111..289e0d0 100644 (file)
@@ -1,6 +1,8 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
-# Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
+# Copyright (c) 2013, Thibault Saunier <thibault.saunier@collabora.com>
+# Copyright (c) 2020, Igalia S.L
+#    Author: Thibault Saunier <tsaunier@igalia.com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -19,6 +21,7 @@
 
 import os
 import sys
+import tempfile
 import urllib.parse
 import subprocess
 from launcher import utils
@@ -116,14 +119,20 @@ class XgesProjectDescriptor(MediaDescriptor):
 
 class GESTest(GstValidateTest):
     def __init__(self, classname, options, reporter, project, scenario=None,
-                 combination=None, expected_failures=None):
+                 combination=None, expected_failures=None, nest=False, testfile=None):
 
         super(GESTest, self).__init__(GES_LAUNCH_COMMAND, classname, options, reporter,
                                       scenario=scenario)
 
         self.project = project
+        self.nested = nest
+        self.testfile = testfile
 
     def set_sample_paths(self):
+        if self.testfile:
+            # testfile should be self contained
+            return
+
         if not self.options.paths:
             if self.options.disable_recurse:
                 return
@@ -142,25 +151,47 @@ class GESTest(GstValidateTest):
             path = path.replace("\\", "/")
             if not self.options.disable_recurse:
                 self.add_arguments("--ges-sample-path-recurse", quote_uri(path))
+                self.add_arguments("--ges-sample-path-recurse", quote_uri(self.options.projects_paths))
             else:
                 self.add_arguments("--ges-sample-paths", quote_uri(path))
 
-    def build_arguments(self):
-        GstValidateTest.build_arguments(self)
+    def set_sink_args(self):
+        if self.testfile:
+            # testfile should be self contained and --mute should give required infos.
+            if self.options.mute:
+                self.add_arguments("--mute")
+            return
 
         if self.options.mute:
-            self.add_arguments("--mute")
+            needs_clock = self.scenario.needs_clock_sync() \
+                if self.scenario else False
+            audiosink = utils.get_fakesink_for_media_type("audio", needs_clock)
+            videosink = utils.get_fakesink_for_media_type("video", needs_clock)
+        else:
+            audiosink = 'autoaudiosink'
+            videosink = 'autovideosink'
+        self.add_arguments("--videosink", videosink + " name=videosink")
+        self.add_arguments("--audiosink", audiosink + " name=audiosink")
 
+    def build_arguments(self):
+        GstValidateTest.build_arguments(self)
+
+        self.set_sink_args()
         self.set_sample_paths()
 
         if self.project:
-            self.add_arguments("-l", self.project.get_uri())
-
+            assert self.testfile is None
+            if self.nested:
+                self.add_arguments("+clip", self.project.get_uri())
+            else:
+                self.add_arguments("-l", self.project.get_uri())
+        elif self.testfile:
+            self.add_arguments("--set-test-file", self.testfile)
 
 class GESPlaybackTest(GESTest):
-    def __init__(self, classname, options, reporter, project, scenario):
+    def __init__(self, classname, options, reporter, project, scenario,nest):
         super(GESPlaybackTest, self).__init__(classname, options, reporter,
-                                      project, scenario=scenario)
+                                      project, scenario=scenario, nest=nest)
 
     def get_current_value(self):
         return self.get_current_position()
@@ -212,26 +243,8 @@ class GESRenderTest(GESTest, GstValidateEncodingTestInterface):
         self.add_arguments("-f", profile, "-o", self.dest_file)
 
     def check_results(self):
-        if self.result in [Result.PASSED, Result.NOT_RUN] and self.scenario is None:
-            if self.process.returncode != 0:
-                return super().check_results()
-
-            res, msg = self.check_encoded_file()
-            self.set_result(res, msg)
-        else:
-            if self.result == utils.Result.TIMEOUT:
-                missing_eos = False
-                try:
-                    if utils.get_duration(self.dest_file) == self.project.get_duration():
-                        missing_eos = True
-                except Exception as e:
-                    pass
-
-                if missing_eos is True:
-                    self.set_result(utils.Result.TIMEOUT, "The rendered file had right duration, MISSING EOS?\n",
-                                    "failure")
-            else:
-                GstValidateTest.check_results(self)
+        self.check_encoded_file()
+        return GstValidateTest.check_results(self)
 
     def get_current_value(self):
         size = self.get_current_size()
@@ -251,12 +264,12 @@ class GESTestsManager(TestsManager):
 
     def init(self):
         try:
-            if "--set-scenario=" in subprocess.check_output([GES_LAUNCH_COMMAND, "--help"]).decode():
-
-                return True
-            else:
-                self.warning("Can not use ges-launch, it seems not to be compiled against"
-                             " gst-validate")
+            with tempfile.NamedTemporaryFile() as f:
+                if "--set-scenario=" in subprocess.check_output([GES_LAUNCH_COMMAND, "--help"], stderr=f).decode():
+                    return True
+                else:
+                    self.warning("Can not use ges-launch, it seems not to be compiled against"
+                                " gst-validate")
         except subprocess.CalledProcessError as e:
             self.warning("Can not use ges-launch: %s" % e)
         except OSError as e:
@@ -300,7 +313,7 @@ Available options:""")
 
     def register_defaults(self, project_paths=None, scenarios_path=None):
         projects = list()
-        all_scenarios = list()
+        all_scenarios = {}
         if not self.args:
             if project_paths == None:
                 path = self.options.projects_paths
@@ -319,9 +332,24 @@ Available options:""")
             if scenarios_path:
                 for root, dirs, files in os.walk(scenarios_path):
                     for f in files:
-                        if not f.endswith(".scenario"):
+                        name, ext = os.path.splitext(f)
+                        f = os.path.join(root, f)
+                        if ext == ".validatetest":
+                            fpath = os.path.abspath(os.path.join(root, f))
+                            pathname = os.path.abspath(os.path.join(root, name))
+                            name = pathname.replace(os.path.commonpath([scenarios_path, root]), '').replace('/', '.')
+                            self.add_test(GESTest('test' + name,
+                                                  self.options,
+                                                  self.reporter,
+                                                  None,
+                                                  testfile=fpath))
                             continue
-                        all_scenarios.append(os.path.join(root, f))
+                        elif ext != ".scenario":
+                            continue
+                        config = f + ".config"
+                        if not os.path.exists(config):
+                            config = None
+                        all_scenarios[f] = config
         else:
             for proj_uri in self.args:
                 if not utils.isuri(proj_uri):
@@ -355,8 +383,17 @@ Available options:""")
                                               self.options,
                                               self.reporter,
                                               project,
-                                              scenario=scenario)
-                                  )
+                                              scenario=scenario,
+                                              nest=False))
+                #For nested timelines
+                classname = "playback.nested.%s.%s" % (scenario.name,
+                                                    os.path.basename(proj_uri).replace(".xges", ""))
+                self.add_test(GESPlaybackTest(classname,
+                                              self.options,
+                                              self.reporter,
+                                              project,
+                                              scenario=scenario,
+                                              nest=True))
 
             # And now rendering casses
             for comb in GES_ENCODING_TARGET_COMBINATIONS:
@@ -367,9 +404,10 @@ Available options:""")
                                             combination=comb)
                                   )
         if all_scenarios:
-            for scenario in self._scenarios.discover_scenarios(all_scenarios):
+            for scenario in self._scenarios.discover_scenarios(list(all_scenarios.keys())):
+                config = all_scenarios[scenario.path]
                 classname = "scenario.%s" % scenario.name
-                self.add_test(GESScenarioTest(classname,
-                                            self.options,
-                                            self.reporter,
-                                            scenario=scenario))
\ No newline at end of file
+                test = GESScenarioTest(classname, self.options, self.reporter, scenario=scenario)
+                if config:
+                    test.add_validate_config(config)
+                self.add_test(test)
\ No newline at end of file
diff --git a/tests/validate/scenarios/Makefile.am b/tests/validate/scenarios/Makefile.am
deleted file mode 100644 (file)
index 54e20ba..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-scenariosdir=${datadir}/gstreamer-$(GST_API_VERSION)/validate/scenarios
-
-scenarios_DATA = ges-edit-clip-while-paused.scenario
-
-EXTRA_DIST = ges-edit-clip-while-paused.scenario
diff --git a/tools/Makefile.am b/tools/Makefile.am
deleted file mode 100644 (file)
index 4a5dc07..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-bin_PROGRAMS = ges-launch-@GST_API_VERSION@
-
-AM_CFLAGS =  -I$(top_srcdir) $(GST_PBUTILS_CFLAGS) $(GST_CFLAGS) $(GIO_CFLAGS) $(GST_VALIDATE_CFLAGS)
-LDADD = $(top_builddir)/ges/libges-@GST_API_VERSION@.la $(GST_PBUTILS_LIBS) $(GST_LIBS) $(GIO_LIBS) $(GST_VALIDATE_LIBS)
-
-noinst_HEADERS = ges-validate.h ges-launcher.h utils.h
-
-ges_launch_@GST_API_VERSION@_SOURCES = ges-validate.c ges-launch.c ges-launcher.c utils.c
-
-#man_MANS = ges-launch-1.0.1
-
-EXTRA_DIST = ges-launch-1.0.1
index 2971f0f..66eeb23 100644 (file)
 #include <locale.h>             /* for LC_ALL */
 #include "ges-launcher.h"
 
-static void
-_print_all_commands (gint nargs, gchar ** commands)
-{
-  gchar *help;
-
-  if (nargs == 0)
-    g_print ("Available ges-launch-1.0 commands:\n\n");
-
-  help = ges_command_line_formatter_get_help (nargs, commands);
-
-  g_print ("%s", help);
-
-  g_free (help);
-}
-
-static void
-_check_command_help (int argc, gchar ** argv)
-{
-/**
- *   gchar *page = NULL;
- *
- *     if (argc == 2)
- *       page = g_strdup ("ges-launch-1.0");
- *     else if (!g_strcmp0 (argv[2], "all"))
- */
-
-  if (!g_strcmp0 (argv[1], "help")) {
-    gint nargs = 0;
-    gchar **commands = NULL;
-
-    if (argc > 2) {
-      nargs = argc - 2;
-      commands = &argv[2];
-    }
-
-    _print_all_commands (nargs, commands);
-    exit (0);
-  }
-
-/*     else
- *       page = g_strconcat ("ges-launch-1.0", "-", argv[2], NULL);
- *
- *     if (page) {
- *       execlp ("man", "man", page, NULL);
- *       g_free (page);
- *     }
- *
- *     an error is raised by execlp it will be displayed in the terminal
- *     exit (0);
- *   }
- */
-}
 
 int
 main (int argc, gchar ** argv)
@@ -80,7 +28,6 @@ main (int argc, gchar ** argv)
   GESLauncher *launcher;
   gint ret;
 
-  _check_command_help (argc, argv);
   setlocale (LC_ALL, "");
 
   launcher = ges_launcher_new ();
@@ -91,6 +38,7 @@ main (int argc, gchar ** argv)
     ret = ges_launcher_get_exit_status (launcher);
 
   g_object_unref (launcher);
+  ges_deinit ();
   gst_deinit ();
 
   return ret;
diff --git a/tools/ges-launcher-kb.c b/tools/ges-launcher-kb.c
new file mode 100644 (file)
index 0000000..ffaafde
--- /dev/null
@@ -0,0 +1,293 @@
+/* GStreamer command line playback testing utility - keyboard handling helpers
+ *
+ * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
+ * Copyright (C) 2013 Centricular Ltd
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ges-launcher-kb.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef G_OS_UNIX
+#include <unistd.h>
+#include <termios.h>
+#endif
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <io.h>
+#endif
+
+#include <gst/gst.h>
+
+/* This is all not thread-safe, but doesn't have to be really */
+static GstPlayKbFunc kb_callback;
+static gpointer kb_callback_data;
+
+#ifdef G_OS_UNIX
+static struct termios term_settings;
+static gboolean term_settings_saved = FALSE;
+static gulong io_watch_id;
+
+static gboolean
+gst_play_kb_io_cb (GIOChannel * ioc, GIOCondition cond, gpointer user_data)
+{
+  GIOStatus status;
+
+  if (cond & G_IO_IN) {
+    gchar buf[16] = { 0, };
+    gsize read;
+
+    status = g_io_channel_read_chars (ioc, buf, sizeof (buf) - 1, &read, NULL);
+    if (status == G_IO_STATUS_ERROR)
+      return FALSE;
+    if (status == G_IO_STATUS_NORMAL) {
+      if (kb_callback)
+        kb_callback (buf, kb_callback_data);
+    }
+  }
+
+  return TRUE;                  /* call us again */
+}
+
+gboolean
+gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data)
+{
+  GIOChannel *ioc;
+
+  if (!isatty (STDIN_FILENO)) {
+    GST_INFO ("stdin is not connected to a terminal");
+    return FALSE;
+  }
+
+  if (io_watch_id > 0) {
+    g_source_remove (io_watch_id);
+    io_watch_id = 0;
+  }
+
+  if (kb_func == NULL && term_settings_saved) {
+    /* restore terminal settings */
+    if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &term_settings) == 0)
+      term_settings_saved = FALSE;
+    else
+      g_warning ("could not restore terminal attributes");
+
+    setvbuf (stdin, NULL, _IOLBF, 0);
+  }
+
+  if (kb_func != NULL) {
+    struct termios new_settings;
+
+    if (!term_settings_saved) {
+      if (tcgetattr (STDIN_FILENO, &term_settings) != 0) {
+        g_warning ("could not save terminal attributes");
+        return FALSE;
+      }
+      term_settings_saved = TRUE;
+
+      /* Echo off, canonical mode off, extended input processing off  */
+      new_settings = term_settings;
+      new_settings.c_lflag &= ~(ECHO | ICANON | IEXTEN);
+      new_settings.c_cc[VMIN] = 0;
+      new_settings.c_cc[VTIME] = 0;
+
+      if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &new_settings) != 0) {
+        g_warning ("Could not set terminal state");
+        return FALSE;
+      }
+      setvbuf (stdin, NULL, _IONBF, 0);
+    }
+  }
+
+  ioc = g_io_channel_unix_new (STDIN_FILENO);
+
+  io_watch_id = g_io_add_watch_full (ioc, G_PRIORITY_DEFAULT, G_IO_IN,
+      (GIOFunc) gst_play_kb_io_cb, user_data, NULL);
+  g_io_channel_unref (ioc);
+
+  kb_callback = kb_func;
+  kb_callback_data = user_data;
+
+  return TRUE;
+}
+
+#elif defined(G_OS_WIN32)
+
+typedef struct
+{
+  GThread *thread;
+  HANDLE event_handle;
+  HANDLE console_handle;
+  gboolean closing;
+  GMutex lock;
+} Win32KeyHandler;
+
+static Win32KeyHandler *win32_handler = NULL;
+
+static gboolean
+gst_play_kb_source_cb (Win32KeyHandler * handler)
+{
+  HANDLE h_input = handler->console_handle;
+  INPUT_RECORD buffer;
+  DWORD n;
+
+  if (PeekConsoleInput (h_input, &buffer, 1, &n) && n == 1) {
+    ReadConsoleInput (h_input, &buffer, 1, &n);
+
+    if (buffer.EventType == KEY_EVENT && buffer.Event.KeyEvent.bKeyDown) {
+      gchar key_val[2] = { 0 };
+
+      switch (buffer.Event.KeyEvent.wVirtualKeyCode) {
+        case VK_RIGHT:
+          kb_callback (GST_PLAY_KB_ARROW_RIGHT, kb_callback_data);
+          break;
+        case VK_LEFT:
+          kb_callback (GST_PLAY_KB_ARROW_LEFT, kb_callback_data);
+          break;
+        case VK_UP:
+          kb_callback (GST_PLAY_KB_ARROW_UP, kb_callback_data);
+          break;
+        case VK_DOWN:
+          kb_callback (GST_PLAY_KB_ARROW_DOWN, kb_callback_data);
+          break;
+        default:
+          key_val[0] = buffer.Event.KeyEvent.uChar.AsciiChar;
+          kb_callback (key_val, kb_callback_data);
+          break;
+      }
+    }
+  }
+
+  return G_SOURCE_REMOVE;
+}
+
+static gpointer
+gst_play_kb_win32_thread (gpointer user_data)
+{
+  Win32KeyHandler *handler = (Win32KeyHandler *) user_data;
+  HANDLE handles[2];
+
+  handles[0] = handler->event_handle;
+  handles[1] = handler->console_handle;
+
+  if (!kb_callback)
+    return NULL;
+
+  while (TRUE) {
+    DWORD ret = WaitForMultipleObjects (2, handles, FALSE, INFINITE);
+
+    if (ret == WAIT_FAILED) {
+      GST_WARNING ("WaitForMultipleObject Failed");
+      return NULL;
+    }
+
+    g_mutex_lock (&handler->lock);
+    if (handler->closing) {
+      g_mutex_unlock (&handler->lock);
+
+      return NULL;
+    }
+    g_mutex_unlock (&handler->lock);
+
+    g_idle_add ((GSourceFunc) gst_play_kb_source_cb, handler);
+  }
+
+  return NULL;
+}
+
+gboolean
+gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data)
+{
+  gint fd = _fileno (stdin);
+
+  if (!_isatty (fd)) {
+    GST_INFO ("stdin is not connected to a terminal");
+    return FALSE;
+  }
+
+  if (win32_handler) {
+    g_mutex_lock (&win32_handler->lock);
+    win32_handler->closing = TRUE;
+    g_mutex_unlock (&win32_handler->lock);
+
+    SetEvent (win32_handler->event_handle);
+    g_thread_join (win32_handler->thread);
+    CloseHandle (win32_handler->event_handle);
+
+    g_mutex_clear (&win32_handler->lock);
+    g_free (win32_handler);
+    win32_handler = NULL;
+  }
+
+  if (kb_func) {
+    SECURITY_ATTRIBUTES sec_attrs;
+
+    sec_attrs.nLength = sizeof (SECURITY_ATTRIBUTES);
+    sec_attrs.lpSecurityDescriptor = NULL;
+    sec_attrs.bInheritHandle = FALSE;
+
+    win32_handler = g_new0 (Win32KeyHandler, 1);
+
+    /* create cancellable event handle */
+    win32_handler->event_handle = CreateEvent (&sec_attrs, TRUE, FALSE, NULL);
+
+    if (!win32_handler->event_handle) {
+      GST_WARNING ("Couldn't create event handle");
+      g_free (win32_handler);
+      win32_handler = NULL;
+
+      return FALSE;
+    }
+
+    win32_handler->console_handle = GetStdHandle (STD_INPUT_HANDLE);
+    if (!win32_handler->console_handle) {
+      GST_WARNING ("Couldn't get console handle");
+      CloseHandle (win32_handler->event_handle);
+      g_free (win32_handler);
+      win32_handler = NULL;
+
+      return FALSE;
+    }
+
+    g_mutex_init (&win32_handler->lock);
+    win32_handler->thread =
+        g_thread_new ("gst-play-kb", gst_play_kb_win32_thread, win32_handler);
+  }
+
+  kb_callback = kb_func;
+  kb_callback_data = user_data;
+
+  return TRUE;
+}
+
+#else
+
+gboolean
+gst_play_kb_set_key_handler (GstPlayKbFunc key_func, gpointer user_data)
+{
+  GST_FIXME ("Keyboard handling for this OS needs to be implemented");
+  return FALSE;
+}
+
+#endif /* !G_OS_UNIX */
diff --git a/tools/ges-launcher-kb.h b/tools/ges-launcher-kb.h
new file mode 100644 (file)
index 0000000..7dab0ed
--- /dev/null
@@ -0,0 +1,35 @@
+/* GStreamer command line playback testing utility - keyboard handling helpers
+ *
+ * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
+ * Copyright (C) 2013 Centricular Ltd
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifndef __GST_PLAY_KB_INCLUDED__
+#define __GST_PLAY_KB_INCLUDED__
+
+#include <glib.h>
+
+#define GST_PLAY_KB_ARROW_UP    "\033[A"
+#define GST_PLAY_KB_ARROW_DOWN  "\033[B"
+#define GST_PLAY_KB_ARROW_RIGHT "\033[C"
+#define GST_PLAY_KB_ARROW_LEFT  "\033[D"
+
+typedef void (*GstPlayKbFunc) (const gchar * kb_input, gpointer user_data);
+
+gboolean gst_play_kb_set_key_handler (GstPlayKbFunc kb_func, gpointer user_data);
+
+#endif /* __GST_PLAY_KB_INCLUDED__ */
index 972221a..a13238e 100644 (file)
 #include "ges-launcher.h"
 #include "ges-validate.h"
 #include "utils.h"
+#include "ges-launcher-kb.h"
 
-typedef struct
-{
-  gboolean mute;
-  gboolean disable_mixing;
-  gchar *save_path;
-  gchar *save_only_path;
-  gchar *load_path;
-  GESTrackType track_types;
-  gboolean needs_set_state;
-  gboolean smartrender;
-  gchar *scenario;
-  gchar *format;
-  gchar *outputuri;
-  gchar *encoding_profile;
-  gchar *videosink;
-  gchar *audiosink;
-  gboolean list_transitions;
-  gboolean inspect_action_type;
-  gchar *sanitized_timeline;
-  const gchar *video_track_caps;
-  const gchar *audio_track_caps;
-} ParsedOptions;
+typedef enum
+{
+  GST_PLAY_TRICK_MODE_NONE = 0,
+  GST_PLAY_TRICK_MODE_DEFAULT,
+  GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO,
+  GST_PLAY_TRICK_MODE_KEY_UNITS,
+  GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO,
+  GST_PLAY_TRICK_MODE_INSTANT_RATE,
+  GST_PLAY_TRICK_MODE_LAST
+} GstPlayTrickMode;
 
 struct _GESLauncherPrivate
 {
@@ -63,32 +52,266 @@ struct _GESLauncherPrivate
 #ifdef G_OS_UNIX
   guint signal_watch_id;
 #endif
-  ParsedOptions parsed_options;
+  GESLauncherParsedOptions parsed_options;
+
+  GstPlayTrickMode trick_mode;
+  gdouble rate;
+
+  GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (GESLauncher, ges_launcher, G_TYPE_APPLICATION);
 
 static const gchar *HELP_SUMMARY =
-    "ges-launch-1.0 creates a multimedia timeline and plays it back,\n"
+    "  `ges-launch-1.0` creates a multimedia timeline and plays it back,\n"
     "  or renders it to the specified format.\n\n"
-    " It can load a timeline from an existing project, or create one\n"
-    " from the specified commands.\n\n"
-    " Updating an existing project can be done through --set-scenario\n"
-    " if ges-launch-1.0 has been compiled with gst-validate, see\n"
-    " ges-launch-1.0 --inspect-action-type for the available commands.\n\n"
-    " You can learn more about individual ges-launch-1.0 commands with\n"
-    " \"ges-launch-1.0 help command\".\n\n"
-    " By default, ges-launch-1.0 is in \"playback-mode\".";
+    "  It can load a timeline from an existing project, or create one\n"
+    "  using the 'Timeline description format', specified in the section\n"
+    "  of the same name.\n\n"
+    "  Updating an existing project can be done through `--set-scenario`\n"
+    "  if ges-launch-1.0 has been compiled with gst-validate, see\n"
+    "  `ges-launch-1.0 --inspect-action-type` for the available commands.\n\n"
+    "  By default, ges-launch-1.0 is in \"playback-mode\".";
 
 static gboolean
-_parse_track_type (const gchar * option_name, const gchar * value,
-    GESLauncher * self, GError ** error)
+play_do_seek (GESLauncher * self, gint64 pos, gdouble rate,
+    GstPlayTrickMode mode)
 {
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GstSeekFlags seek_flags;
+  GstEvent *seek;
 
-  opts->track_types = get_flags_from_string (GES_TYPE_TRACK_TYPE, value);
+  seek_flags = 0;
+
+  switch (mode) {
+    case GST_PLAY_TRICK_MODE_DEFAULT:
+      seek_flags |= GST_SEEK_FLAG_TRICKMODE;
+      break;
+    case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
+      seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
+      break;
+    case GST_PLAY_TRICK_MODE_KEY_UNITS:
+      seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
+      break;
+    case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
+      seek_flags |=
+          GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
+      break;
+    case GST_PLAY_TRICK_MODE_NONE:
+    default:
+      break;
+  }
 
-  if (opts->track_types == 0)
+  /* See if we can do an instant rate change (not changing dir) */
+  if (mode & GST_PLAY_TRICK_MODE_INSTANT_RATE && rate * self->priv->rate > 0) {
+    seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
+        seek_flags | GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
+        GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE,
+        GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+    if (gst_element_send_event (GST_ELEMENT (self->priv->pipeline), seek)) {
+      goto done;
+    }
+  }
+
+  /* No instant rate change, need to do a flushing seek */
+  seek_flags |= GST_SEEK_FLAG_FLUSH;
+  if (rate >= 0)
+    seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
+        seek_flags | GST_SEEK_FLAG_ACCURATE,
+        /* start */ GST_SEEK_TYPE_SET, pos,
+        /* stop */ GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
+  else
+    seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
+        seek_flags | GST_SEEK_FLAG_ACCURATE,
+        /* start */ GST_SEEK_TYPE_SET, 0,
+        /* stop */ GST_SEEK_TYPE_SET, pos);
+
+  if (!gst_element_send_event (GST_ELEMENT (self->priv->pipeline), seek))
+    return FALSE;
+
+done:
+  self->priv->rate = rate;
+  self->priv->trick_mode = mode & ~GST_PLAY_TRICK_MODE_INSTANT_RATE;
+  return TRUE;
+}
+
+static void
+restore_terminal (void)
+{
+  gst_play_kb_set_key_handler (NULL, NULL);
+}
+
+static void
+toggle_paused (GESLauncher * self)
+{
+  if (self->priv->desired_state == GST_STATE_PLAYING)
+    self->priv->desired_state = GST_STATE_PAUSED;
+  else
+    self->priv->desired_state = GST_STATE_PLAYING;
+
+  gst_element_set_state (GST_ELEMENT (self->priv->pipeline),
+      self->priv->desired_state);
+}
+
+static void
+relative_seek (GESLauncher * self, gdouble percent)
+{
+  gint64 pos = -1, step, dur;
+
+  g_return_if_fail (percent >= -1.0 && percent <= 1.0);
+
+  if (!gst_element_query_position (GST_ELEMENT (self->priv->pipeline),
+          GST_FORMAT_TIME, &pos))
+    goto seek_failed;
+
+  if (!gst_element_query_duration (GST_ELEMENT (self->priv->pipeline),
+          GST_FORMAT_TIME, &dur)) {
+    goto seek_failed;
+  }
+
+  step = dur * percent;
+  if (ABS (step) < GST_SECOND)
+    step = (percent < 0) ? -GST_SECOND : GST_SECOND;
+
+  pos = pos + step;
+  if (pos > dur) {
+    gst_print ("\n%s\n", "Reached end of self list.");
+    g_application_quit (G_APPLICATION (self));
+  } else {
+    if (pos < 0)
+      pos = 0;
+
+    play_do_seek (self, pos, self->priv->rate, self->priv->trick_mode);
+  }
+
+  return;
+
+seek_failed:
+  {
+    gst_print ("\nCould not seek.\n");
+  }
+}
+
+static gboolean
+play_set_rate_and_trick_mode (GESLauncher * self, gdouble rate,
+    GstPlayTrickMode mode)
+{
+  gint64 pos = -1;
+
+  g_return_val_if_fail (rate != 0, FALSE);
+
+  if (!gst_element_query_position (GST_ELEMENT (self->priv->pipeline),
+          GST_FORMAT_TIME, &pos))
+    return FALSE;
+
+  return play_do_seek (self, pos, rate, mode);
+}
+
+static void
+play_set_playback_rate (GESLauncher * self, gdouble rate)
+{
+  GstPlayTrickMode mode = self->priv->trick_mode;
+
+  if (play_set_rate_and_trick_mode (self, rate, mode)) {
+    gst_print ("Playback rate: %.2f", rate);
+    gst_print ("                               \n");
+  } else {
+    gst_print ("\n");
+    gst_print ("Could not change playback rate to %.2f", rate);
+    gst_print (".\n");
+  }
+}
+
+static void
+play_set_relative_playback_rate (GESLauncher * self, gdouble rate_step,
+    gboolean reverse_direction)
+{
+  gdouble new_rate = self->priv->rate + rate_step;
+
+  play_set_playback_rate (self, new_rate);
+}
+
+static const gchar *
+trick_mode_get_description (GstPlayTrickMode mode)
+{
+  switch (mode) {
+    case GST_PLAY_TRICK_MODE_NONE:
+      return "normal playback, trick modes disabled";
+    case GST_PLAY_TRICK_MODE_DEFAULT:
+      return "trick mode: default";
+    case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
+      return "trick mode: default, no audio";
+    case GST_PLAY_TRICK_MODE_KEY_UNITS:
+      return "trick mode: key frames only";
+    case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
+      return "trick mode: key frames only, no audio";
+    default:
+      break;
+  }
+  return "unknown trick mode";
+}
+
+static void
+play_switch_trick_mode (GESLauncher * self)
+{
+  GstPlayTrickMode new_mode = ++self->priv->trick_mode;
+  const gchar *mode_desc;
+
+  if (new_mode == GST_PLAY_TRICK_MODE_LAST)
+    new_mode = GST_PLAY_TRICK_MODE_NONE;
+
+  mode_desc = trick_mode_get_description (new_mode);
+
+  if (play_set_rate_and_trick_mode (self, self->priv->rate, new_mode)) {
+    gst_print ("Rate: %.2f (%s)                      \n", self->priv->rate,
+        mode_desc);
+  } else {
+    gst_print ("\nCould not change trick mode to %s.\n", mode_desc);
+  }
+}
+
+static void
+print_keyboard_help (void)
+{
+  static struct
+  {
+    const gchar *key_desc;
+    const gchar *key_help;
+  } key_controls[] = {
+    {
+    "space", "pause/unpause"}, {
+    "q or ESC", "quit"}, {
+    "\342\206\222", "seek forward"}, {
+    "\342\206\220", "seek backward"}, {
+    "+", "increase playback rate"}, {
+    "-", "decrease playback rate"}, {
+    "t", "enable/disable trick modes"}, {
+    "s", "change subtitle track"}, {
+    "0", "seek to beginning"}, {
+  "k", "show keyboard shortcuts"},};
+  guint i, chars_to_pad, desc_len, max_desc_len = 0;
+
+  gst_print ("\n\n%s\n\n", "Interactive mode - keyboard controls:");
+
+  for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
+    desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
+    max_desc_len = MAX (max_desc_len, desc_len);
+  }
+  ++max_desc_len;
+
+  for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
+    chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
+    gst_print ("\t%s", key_controls[i].key_desc);
+    gst_print ("%-*s: ", chars_to_pad, "");
+    gst_print ("%s\n", key_controls[i].key_help);
+  }
+  gst_print ("\n");
+}
+
+static gboolean
+_parse_track_type (const gchar * option_name, const gchar * value,
+    GESLauncherParsedOptions * opts, GError ** error)
+{
+  if (!get_flags_from_string (GES_TYPE_TRACK_TYPE, value, &opts->track_types))
     return FALSE;
 
   return TRUE;
@@ -105,7 +328,7 @@ _set_track_restriction_caps (GESTrack * track, const gchar * caps_str)
   caps = gst_caps_from_string (caps_str);
 
   if (!caps) {
-    g_printerr ("Could not create caps for %s from: %s",
+    g_error ("Could not create caps for %s from: %s",
         G_OBJECT_TYPE_NAME (track), caps_str);
 
     return FALSE;
@@ -117,6 +340,351 @@ _set_track_restriction_caps (GESTrack * track, const gchar * caps_str)
   return TRUE;
 }
 
+static void
+_set_restriction_caps (GESTimeline * timeline, GESLauncherParsedOptions * opts)
+{
+  GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
+
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
+      _set_track_restriction_caps (tmp->data, opts->video_track_caps);
+    else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
+      _set_track_restriction_caps (tmp->data, opts->audio_track_caps);
+  }
+
+  g_list_free_full (tracks, gst_object_unref);
+
+}
+
+static void
+_set_track_forward_tags (const GValue * item, gpointer unused)
+{
+  GstElement *comp = g_value_get_object (item);
+
+  g_object_set (comp, "drop-tags", FALSE, NULL);
+}
+
+static void
+_set_tracks_forward_tags (GESTimeline * timeline,
+    GESLauncherParsedOptions * opts)
+{
+  GList *tmp, *tracks;
+
+  if (!opts->forward_tags)
+    return;
+
+  tracks = ges_timeline_get_tracks (timeline);
+
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    GstIterator *it =
+        gst_bin_iterate_all_by_element_factory_name (GST_BIN (tmp->data),
+        "nlecomposition");
+
+    gst_iterator_foreach (it,
+        (GstIteratorForeachFunction) _set_track_forward_tags, NULL);
+    gst_iterator_free (it);
+  }
+
+  g_list_free_full (tracks, gst_object_unref);
+
+}
+
+static void
+_check_has_audio_video (GESLauncher * self, gint * n_audio, gint * n_video)
+{
+  GList *tmp, *tracks = ges_timeline_get_tracks (self->priv->timeline);
+
+  *n_video = *n_audio = 0;
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_VIDEO)
+      *n_video = *n_video + 1;
+    else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
+      *n_audio = *n_audio + 1;
+  }
+}
+
+#define N_INSTANCES "__n_instances"
+static gint
+sort_encoding_profiles (gconstpointer a, gconstpointer b)
+{
+  const gint acount =
+      GPOINTER_TO_INT (g_object_get_data ((GObject *) a, N_INSTANCES));
+  const gint bcount =
+      GPOINTER_TO_INT (g_object_get_data ((GObject *) b, N_INSTANCES));
+
+  if (acount < bcount)
+    return -1;
+
+  if (acount == bcount)
+    return 0;
+
+  return 1;
+}
+
+static GList *
+_timeline_assets (GESLauncher * self)
+{
+  GList *tmp, *assets = NULL;
+
+  for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) {
+    GList *tclip, *clips = ges_layer_get_clips (tmp->data);
+
+    for (tclip = clips; tclip; tclip = tclip->next) {
+      if (GES_IS_URI_CLIP (tclip->data)) {
+        assets =
+            g_list_append (assets, ges_extractable_get_asset (tclip->data));
+      }
+    }
+    g_list_free_full (clips, gst_object_unref);
+  }
+
+  return assets;
+}
+
+static GESAsset *
+_asset_for_named_clip (GESLauncher * self, const gchar * name)
+{
+  GList *tmp;
+  GESAsset *ret = NULL;
+
+  for (tmp = self->priv->timeline->layers; tmp; tmp = tmp->next) {
+    GList *tclip, *clips = ges_layer_get_clips (tmp->data);
+
+    for (tclip = clips; tclip; tclip = tclip->next) {
+      if (GES_IS_URI_CLIP (tclip->data) &&
+          !g_strcmp0 (name, ges_timeline_element_get_name (tclip->data))) {
+        ret = ges_extractable_get_asset (tclip->data);
+        break;
+      }
+    }
+
+    g_list_free_full (clips, gst_object_unref);
+
+    if (ret)
+      break;
+  }
+
+  return ret;
+}
+
+static GstEncodingProfile *
+_get_profile_from (GESLauncher * self)
+{
+  GESAsset *asset =
+      _asset_for_named_clip (self, self->priv->parsed_options.profile_from);
+  GstDiscovererInfo *info;
+  GstEncodingProfile *prof;
+
+  g_assert (asset);
+
+  info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
+  prof = gst_encoding_profile_from_discoverer (info);
+
+  return prof;
+}
+
+static GstEncodingProfile *
+get_smart_profile (GESLauncher * self)
+{
+  gint n_audio, n_video;
+  GList *tmp, *assets, *possible_profiles = NULL;
+  GstEncodingProfile *res = NULL;
+
+  if (self->priv->parsed_options.profile_from) {
+    GESAsset *asset =
+        _asset_for_named_clip (self, self->priv->parsed_options.profile_from);
+    GstDiscovererInfo *info;
+    GstEncodingProfile *prof;
+
+    g_assert (asset);
+
+    info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
+    prof = gst_encoding_profile_from_discoverer (info);
+
+    return prof;
+  }
+
+  _check_has_audio_video (self, &n_audio, &n_video);
+
+  assets = _timeline_assets (self);
+
+  for (tmp = assets; tmp; tmp = tmp->next) {
+    GESAsset *asset = tmp->data;
+    GList *audio_streams, *video_streams;
+    GstDiscovererInfo *info;
+
+    if (!GES_IS_URI_CLIP_ASSET (asset))
+      continue;
+
+    info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
+    audio_streams = gst_discoverer_info_get_audio_streams (info);
+    video_streams = gst_discoverer_info_get_video_streams (info);
+    if (g_list_length (audio_streams) >= n_audio
+        && g_list_length (video_streams) >= n_video) {
+      GstEncodingProfile *prof = gst_encoding_profile_from_discoverer (info);
+      GList *prevprof;
+
+      prevprof =
+          g_list_find_custom (possible_profiles, prof,
+          (GCompareFunc) gst_encoding_profile_is_equal);
+      if (prevprof) {
+        g_object_unref (prof);
+        prof = prevprof->data;
+      } else {
+        possible_profiles = g_list_prepend (possible_profiles, prof);
+      }
+
+      g_object_set_data ((GObject *) prof, N_INSTANCES,
+          GINT_TO_POINTER (GPOINTER_TO_INT (g_object_get_data ((GObject *) prof,
+                      N_INSTANCES)) + 1));
+    }
+    gst_discoverer_stream_info_list_free (audio_streams);
+    gst_discoverer_stream_info_list_free (video_streams);
+  }
+
+  g_list_free (assets);
+
+  if (possible_profiles) {
+    possible_profiles = g_list_sort (possible_profiles, sort_encoding_profiles);
+    res = gst_object_ref (possible_profiles->data);
+    g_list_free_full (possible_profiles, gst_object_unref);
+  }
+
+  return res;
+}
+
+static void
+disable_bframe_for_smart_rendering_cb (GstBin * bin, GstBin * sub_bin,
+    GstElement * child)
+{
+  GstElementFactory *factory = gst_element_get_factory (child);
+
+  if (factory && !g_strcmp0 (GST_OBJECT_NAME (factory), "x264enc")) {
+    g_object_set (child, "b-adapt", FALSE, "b-pyramid", FALSE, "bframes", 0,
+        NULL);
+  }
+}
+
+static gboolean
+_set_rendering_details (GESLauncher * self)
+{
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
+  gboolean smart_profile = FALSE;
+  GESPipelineFlags cmode = ges_pipeline_get_mode (self->priv->pipeline);
+  GESProject *proj;
+
+  if (cmode & GES_PIPELINE_MODE_RENDER
+      || cmode & GES_PIPELINE_MODE_SMART_RENDER) {
+    GST_INFO_OBJECT (self, "Rendering settings already set");
+    return TRUE;
+  }
+
+  proj =
+      GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->priv->
+              timeline)));
+
+  /* Setup profile/encoding if needed */
+  if (opts->outputuri) {
+    GstEncodingProfile *prof = NULL;
+    if (!opts->format) {
+      const GList *profiles = ges_project_list_encoding_profiles (proj);
+
+      if (profiles) {
+        prof = profiles->data;
+        if (opts->encoding_profile)
+          for (; profiles; profiles = profiles->next)
+            if (g_strcmp0 (opts->encoding_profile,
+                    gst_encoding_profile_get_name (profiles->data)) == 0)
+              prof = profiles->data;
+      }
+
+      if (prof)
+        prof = gst_object_ref (prof);
+    }
+
+    if (!prof) {
+      if (opts->format == NULL) {
+        if (opts->profile_from)
+          prof = _get_profile_from (self);
+        else if (opts->smartrender)
+          prof = get_smart_profile (self);
+        if (prof)
+          smart_profile = TRUE;
+        else {
+          opts->format = get_file_extension (opts->outputuri);
+          prof = parse_encoding_profile (opts->format);
+        }
+      } else {
+        prof = parse_encoding_profile (opts->format);
+        if (!prof)
+          g_error ("Invalid format specified: %s", opts->format);
+      }
+
+      if (!prof) {
+        ges_warn
+            ("No format specified and couldn't find one from output file extension, "
+            "falling back to theora+vorbis in ogg.");
+        g_free (opts->format);
+
+        opts->format =
+            g_strdup ("application/ogg:video/x-theora:audio/x-vorbis");
+        prof = parse_encoding_profile (opts->format);
+      }
+
+      if (!prof) {
+        ges_printerr ("Could not find any encoding format for %s\n",
+            opts->format);
+        return FALSE;
+      }
+
+      gst_print ("\nEncoding details:\n");
+      gst_print ("================\n");
+
+      gst_print ("  -> Output file: %s\n", opts->outputuri);
+      gst_print ("  -> Profile:%s\n",
+          smart_profile ?
+          " (selected from input files format for efficient smart rendering" :
+          "");
+      describe_encoding_profile (prof);
+      gst_print ("\n");
+
+      ges_project_add_encoding_profile (proj, prof);
+    }
+
+    opts->outputuri = ensure_uri (opts->outputuri);
+    if (opts->smartrender) {
+      g_signal_connect (self->priv->pipeline, "deep-element-added",
+          G_CALLBACK (disable_bframe_for_smart_rendering_cb), NULL);
+    }
+    if (!prof
+        || !ges_pipeline_set_render_settings (self->priv->pipeline,
+            opts->outputuri, prof)
+        || !ges_pipeline_set_mode (self->priv->pipeline,
+            opts->smartrender ? GES_PIPELINE_MODE_SMART_RENDER :
+            GES_PIPELINE_MODE_RENDER)) {
+      return FALSE;
+    }
+
+    gst_encoding_profile_unref (prof);
+  } else {
+    ges_pipeline_set_mode (self->priv->pipeline, GES_PIPELINE_MODE_PREVIEW);
+  }
+  return TRUE;
+}
+
+static void
+_track_set_mixing (GESTrack * track, GESLauncherParsedOptions * opts)
+{
+  static gboolean printed_mixing_disabled = FALSE;
+
+  if (opts->disable_mixing || opts->smartrender)
+    ges_track_set_mixing (track, FALSE);
+  if (!opts->disable_mixing && opts->smartrender && !printed_mixing_disabled) {
+    gst_print ("**Mixing is disabled for smart rendering to work**\n");
+    printed_mixing_disabled = TRUE;
+  }
+}
+
 static gboolean
 _timeline_set_user_options (GESLauncher * self, GESTimeline * timeline,
     const gchar * load_path)
@@ -124,7 +692,47 @@ _timeline_set_user_options (GESLauncher * self, GESTimeline * timeline,
   GList *tmp;
   GESTrack *tracka, *trackv;
   gboolean has_audio = FALSE, has_video = FALSE;
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
+
+  if (self->priv->parsed_options.profile_from) {
+    GList *tmp, *tracks;
+    GList *audio_streams, *video_streams;
+    GESAsset *asset =
+        _asset_for_named_clip (self, self->priv->parsed_options.profile_from);
+    GstDiscovererInfo *info;
+    guint i;
+
+    if (!asset) {
+      ges_printerr
+          ("\nERROR: can't create profile from named clip, no such clip %s\n\n",
+          self->priv->parsed_options.profile_from);
+      return FALSE;
+    }
+
+    tracks = ges_timeline_get_tracks (self->priv->timeline);
+
+    for (tmp = tracks; tmp; tmp = tmp->next) {
+      ges_timeline_remove_track (timeline, tmp->data);
+    }
+
+    g_list_free_full (tracks, gst_object_unref);
+
+    info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
+
+    audio_streams = gst_discoverer_info_get_audio_streams (info);
+    video_streams = gst_discoverer_info_get_video_streams (info);
+
+    for (i = 0; i < g_list_length (audio_streams); i++) {
+      ges_timeline_add_track (timeline, GES_TRACK (ges_audio_track_new ()));
+    }
+
+    for (i = 0; i < g_list_length (video_streams); i++) {
+      ges_timeline_add_track (timeline, GES_TRACK (ges_video_track_new ()));
+    }
+
+    gst_discoverer_stream_info_list_free (audio_streams);
+    gst_discoverer_stream_info_list_free (video_streams);
+  }
 
 retry:
   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
@@ -134,24 +742,25 @@ retry:
     else if (GES_TRACK (tmp->data)->type == GES_TRACK_TYPE_AUDIO)
       has_audio = TRUE;
 
-    if (opts->disable_mixing)
-      ges_track_set_mixing (tmp->data, FALSE);
+    _track_set_mixing (tmp->data, opts);
 
-    if (!(GES_TRACK (tmp->data)->type & opts->track_types)) {
-      ges_timeline_remove_track (timeline, tmp->data);
-      goto retry;
+    if (!self->priv->parsed_options.profile_from) {
+      if (!(GES_TRACK (tmp->data)->type & opts->track_types)) {
+        ges_timeline_remove_track (timeline, tmp->data);
+        goto retry;
+      }
     }
   }
 
-  if (opts->scenario && !load_path) {
+  if ((opts->scenario || opts->testfile) && !load_path
+      && !self->priv->parsed_options.profile_from) {
     if (!has_video && opts->track_types & GES_TRACK_TYPE_VIDEO) {
       trackv = GES_TRACK (ges_video_track_new ());
 
       if (!_set_track_restriction_caps (trackv, opts->video_track_caps))
         return FALSE;
 
-      if (opts->disable_mixing)
-        ges_track_set_mixing (trackv, FALSE);
+      _track_set_mixing (trackv, opts);
 
       if (!(ges_timeline_add_track (timeline, trackv)))
         return FALSE;
@@ -163,23 +772,36 @@ retry:
       if (!_set_track_restriction_caps (tracka, opts->audio_track_caps))
         return FALSE;
 
-      if (opts->disable_mixing)
-        ges_track_set_mixing (tracka, FALSE);
+      _track_set_mixing (tracka, opts);
 
       if (!(ges_timeline_add_track (timeline, tracka)))
         return FALSE;
     }
+  } else {
+    _set_restriction_caps (timeline, opts);
   }
 
+  _set_tracks_forward_tags (timeline, opts);
+
   return TRUE;
 }
 
 static void
+_project_loading_error_cb (GESProject * project, GESTimeline * timeline,
+    GError * error, GESLauncher * self)
+{
+  ges_printerr ("Error loading timeline: '%s'\n", error->message);
+  self->priv->seenerrors = TRUE;
+
+  g_application_quit (G_APPLICATION (self));
+}
+
+static void
 _project_loaded_cb (GESProject * project, GESTimeline * timeline,
     GESLauncher * self)
 {
   gchar *project_uri = NULL;
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
   GST_INFO ("Project loaded, playing it");
 
   if (opts->save_path) {
@@ -195,7 +817,7 @@ _project_loaded_cb (GESProject * project, GESTimeline * timeline,
       g_application_quit (G_APPLICATION (self));
     }
 
-    g_print ("\nSaving project to %s\n", uri);
+    gst_print ("\nSaving project to %s\n", uri);
     ges_project_save (project, timeline, uri, NULL, TRUE, &error);
     g_free (uri);
 
@@ -208,16 +830,27 @@ _project_loaded_cb (GESProject * project, GESTimeline * timeline,
   }
 
   project_uri = ges_project_get_uri (project);
-  _timeline_set_user_options (self, timeline, project_uri);
 
   if (self->priv->parsed_options.load_path && project_uri
       && ges_validate_activate (GST_PIPELINE (self->priv->pipeline),
-          opts->scenario, &opts->needs_set_state) == FALSE) {
-    g_error ("Could not activate scenario %s", opts->scenario);
+          self, opts) == FALSE) {
+    if (opts->scenario)
+      g_error ("Could not activate scenario %s", opts->scenario);
+    else
+      g_error ("Could not activate testfile %s", opts->testfile);
     self->priv->seenerrors = TRUE;
     g_application_quit (G_APPLICATION (self));
   }
 
+  if (!_timeline_set_user_options (self, timeline, project_uri)) {
+    g_error ("Failed to set user options on timeline\n");
+  } else if (project_uri) {
+    if (!_set_rendering_details (self))
+      g_error ("Failed to setup rendering details\n");
+  }
+
+  print_timeline (self->priv->timeline);
+
   g_free (project_uri);
 
   if (!self->priv->seenerrors && opts->needs_set_state &&
@@ -231,7 +864,7 @@ static void
 _error_loading_asset_cb (GESProject * project, GError * error,
     const gchar * failed_id, GType extractable_type, GESLauncher * self)
 {
-  g_printerr ("Error loading asset %s: %s\n", failed_id, error->message);
+  ges_printerr ("Error loading asset %s: %s\n", failed_id, error->message);
   self->priv->seenerrors = TRUE;
 
   g_application_quit (G_APPLICATION (self));
@@ -239,7 +872,7 @@ _error_loading_asset_cb (GESProject * project, GError * error,
 
 static gboolean
 _create_timeline (GESLauncher * self, const gchar * serialized_timeline,
-    const gchar * proj_uri, const gchar * scenario)
+    const gchar * proj_uri, gboolean validate)
 {
   GESProject *project;
 
@@ -247,8 +880,7 @@ _create_timeline (GESLauncher * self, const gchar * serialized_timeline,
 
   if (proj_uri != NULL) {
     project = ges_project_new (proj_uri);
-  } else if (scenario == NULL) {
-    GST_INFO ("serialized timeline is %s", serialized_timeline);
+  } else if (!validate) {
     project = ges_project_new (serialized_timeline);
   } else {
     project = ges_project_new (NULL);
@@ -257,12 +889,15 @@ _create_timeline (GESLauncher * self, const gchar * serialized_timeline,
   g_signal_connect (project, "error-loading-asset",
       G_CALLBACK (_error_loading_asset_cb), self);
   g_signal_connect (project, "loaded", G_CALLBACK (_project_loaded_cb), self);
+  g_signal_connect (project, "error-loading",
+      G_CALLBACK (_project_loading_error_cb), self);
 
   self->priv->timeline =
       GES_TIMELINE (ges_asset_extract (GES_ASSET (project), &error));
+  gst_object_unref (project);
 
   if (error) {
-    g_printerr ("\nERROR: Could not create timeline because: %s\n\n",
+    ges_printerr ("\nERROR: Could not create timeline because: %s\n\n",
         error->message);
     g_error_free (error);
     return FALSE;
@@ -271,16 +906,15 @@ _create_timeline (GESLauncher * self, const gchar * serialized_timeline,
   return TRUE;
 }
 
-typedef void (*sinkSettingFunction) (GESPipeline * pipeline,
-    GstElement * element);
+typedef void (*SetSinkFunc) (GESPipeline * pipeline, GstElement * element);
 
 static gboolean
-_set_sink (GESLauncher * self, const gchar * sink_desc,
-    sinkSettingFunction set_func)
+_set_sink (GESLauncher * self, const gchar * sink_desc, SetSinkFunc set_func)
 {
   if (sink_desc != NULL) {
     GError *err = NULL;
-    GstElement *sink = gst_parse_bin_from_description (sink_desc, TRUE, &err);
+    GstElement *sink = gst_parse_bin_from_description_full (sink_desc, TRUE,
+        NULL, GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS, &err);
     if (sink == NULL) {
       GST_ERROR ("could not create the requested videosink %s (err: %s), "
           "exiting", err ? err->message : "", sink_desc);
@@ -296,7 +930,7 @@ _set_sink (GESLauncher * self, const gchar * sink_desc,
 static gboolean
 _set_playback_details (GESLauncher * self)
 {
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
 
   if (!_set_sink (self, opts->videosink, ges_pipeline_preview_set_video_sink) ||
       !_set_sink (self, opts->audiosink, ges_pipeline_preview_set_audio_sink))
@@ -321,9 +955,9 @@ bus_message_cb (GstBus * bus, GstMessage * message, GESLauncher * self)
       gst_message_parse_error (message, &err, &dbg_info);
       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
           GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch-error");
-      g_printerr ("ERROR from element %s: %s\n", GST_OBJECT_NAME (message->src),
-          err->message);
-      g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
+      ges_printerr ("ERROR from element %s: %s\n",
+          GST_OBJECT_NAME (message->src), err->message);
+      ges_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
       g_clear_error (&err);
       g_free (dbg_info);
       self->priv->seenerrors = TRUE;
@@ -331,8 +965,10 @@ bus_message_cb (GstBus * bus, GstMessage * message, GESLauncher * self)
       break;
     }
     case GST_MESSAGE_EOS:
-      g_printerr ("\nDone\n");
-      g_application_quit (G_APPLICATION (self));
+      if (!self->priv->parsed_options.ignore_eos) {
+        ges_ok ("\nDone\n");
+        g_application_quit (G_APPLICATION (self));
+      }
       break;
     case GST_MESSAGE_STATE_CHANGED:
       if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (self->priv->pipeline)) {
@@ -365,10 +1001,10 @@ bus_message_cb (GstBus * bus, GstMessage * message, GESLauncher * self)
 static gboolean
 intr_handler (GESLauncher * self)
 {
-  g_print ("interrupt received.\n");
+  gst_print ("interrupt received.\n");
 
   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->priv->pipeline),
-      GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.interupted");
+      GST_DEBUG_GRAPH_SHOW_ALL, "ges-launch.interrupted");
 
   g_application_quit (G_APPLICATION (self));
 
@@ -380,7 +1016,30 @@ intr_handler (GESLauncher * self)
 static gboolean
 _save_timeline (GESLauncher * self)
 {
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
+
+
+  if (opts->embed_nesteds) {
+    GList *tmp, *assets;
+    GESProject *proj =
+        GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->
+                priv->timeline)));
+
+    assets = ges_project_list_assets (proj, GES_TYPE_URI_CLIP);
+    for (tmp = assets; tmp; tmp = tmp->next) {
+      gboolean is_nested;
+
+      g_object_get (tmp->data, "is-nested-timeline", &is_nested, NULL);
+      if (is_nested) {
+        GESAsset *subproj =
+            ges_asset_request (GES_TYPE_TIMELINE, ges_asset_get_id (tmp->data),
+            NULL);
+
+        ges_project_add_asset (proj, subproj);
+      }
+    }
+    g_list_free_full (assets, gst_object_unref);
+  }
 
   if (opts->save_only_path) {
     gchar *uri;
@@ -412,17 +1071,33 @@ static gboolean
 _run_pipeline (GESLauncher * self)
 {
   GstBus *bus;
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
 
   if (!opts->load_path) {
+    g_clear_pointer (&opts->sanitized_timeline, &g_free);
     if (ges_validate_activate (GST_PIPELINE (self->priv->pipeline),
-            opts->scenario, &opts->needs_set_state) == FALSE) {
+            self, opts) == FALSE) {
       g_error ("Could not activate scenario %s", opts->scenario);
       return FALSE;
     }
 
+    if (opts->sanitized_timeline) {
+      GESProject *project = ges_project_new (opts->sanitized_timeline);
+
+      if (!ges_project_load (project, self->priv->timeline, NULL)) {
+        ges_printerr ("Could not load timeline: %s\n",
+            opts->sanitized_timeline);
+        return FALSE;
+      }
+    }
+
     if (!_timeline_set_user_options (self, self->priv->timeline, NULL)) {
-      g_printerr ("Could not properly set tracks\n");
+      ges_printerr ("Could not properly set tracks\n");
+      return FALSE;
+    }
+
+    if (!_set_rendering_details (self)) {
+      g_error ("Failed to setup rendering details\n");
       return FALSE;
     }
   }
@@ -431,81 +1106,21 @@ _run_pipeline (GESLauncher * self)
   gst_bus_add_signal_watch (bus);
   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), self);
 
-  if (!opts->load_path) {
-    if (opts->needs_set_state
-        && gst_element_set_state (GST_ELEMENT (self->priv->pipeline),
-            GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
-      g_error ("Failed to start the pipeline\n");
-      return FALSE;
-    }
-  }
   g_application_hold (G_APPLICATION (self));
 
   return TRUE;
 }
 
 static gboolean
-_set_rendering_details (GESLauncher * self)
-{
-  ParsedOptions *opts = &self->priv->parsed_options;
-
-  /* Setup profile/encoding if needed */
-  if (opts->smartrender || opts->outputuri) {
-    GstEncodingProfile *prof = NULL;
-
-    if (!opts->format) {
-      GESProject *proj =
-          GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (self->
-                  priv->timeline)));
-      const GList *profiles = ges_project_list_encoding_profiles (proj);
-
-      if (profiles) {
-        prof = profiles->data;
-        if (opts->encoding_profile)
-          for (; profiles; profiles = profiles->next)
-            if (g_strcmp0 (opts->encoding_profile,
-                    gst_encoding_profile_get_name (profiles->data)) == 0)
-              prof = profiles->data;
-      }
-    }
-
-    if (!prof) {
-      if (opts->format == NULL)
-        opts->format =
-            g_strdup ("application/ogg:video/x-theora:audio/x-vorbis");
-
-      prof = parse_encoding_profile (opts->format);
-    }
-
-    if (opts->outputuri)
-      opts->outputuri = ensure_uri (opts->outputuri);
-
-    if (!prof
-        || !ges_pipeline_set_render_settings (self->priv->pipeline,
-            opts->outputuri, prof)
-        || !ges_pipeline_set_mode (self->priv->pipeline,
-            opts->smartrender ? GES_PIPELINE_MODE_SMART_RENDER :
-            GES_PIPELINE_MODE_RENDER)) {
-      return FALSE;
-    }
-
-    gst_encoding_profile_unref (prof);
-  } else {
-    ges_pipeline_set_mode (self->priv->pipeline, GES_PIPELINE_MODE_PREVIEW);
-  }
-  return TRUE;
-}
-
-static gboolean
 _create_pipeline (GESLauncher * self, const gchar * serialized_timeline)
 {
   gchar *uri = NULL;
   gboolean res = TRUE;
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
 
   /* Timeline creation */
   if (opts->load_path) {
-    g_printf ("Loading project from : %s\n", opts->load_path);
+    gst_print ("Loading project from : %s\n", opts->load_path);
 
     if (!(uri = ensure_uri (opts->load_path))) {
       g_error ("couldn't create uri for '%s'", opts->load_path);
@@ -515,7 +1130,11 @@ _create_pipeline (GESLauncher * self, const gchar * serialized_timeline)
 
   self->priv->pipeline = ges_pipeline_new ();
 
-  if (!_create_timeline (self, serialized_timeline, uri, opts->scenario)) {
+  if (opts->outputuri)
+    ges_pipeline_set_mode (self->priv->pipeline, 0);
+
+  if (!_create_timeline (self, serialized_timeline, uri, opts->scenario
+          || opts->testfile)) {
     GST_ERROR ("Could not create the timeline");
     goto failure;
   }
@@ -535,13 +1154,10 @@ _create_pipeline (GESLauncher * self, const gchar * serialized_timeline)
    * our timeline in. */
 
   if (opts->mute) {
-    GstElement *sink = gst_element_factory_make ("fakesink", NULL);
-
-    g_object_set (sink, "sync", TRUE, NULL);
+    GstElement *sink = gst_element_factory_make ("fakeaudiosink", NULL);
     ges_pipeline_preview_set_audio_sink (self->priv->pipeline, sink);
 
-    sink = gst_element_factory_make ("fakesink", NULL);
-    g_object_set (sink, "sync", TRUE, NULL);
+    sink = gst_element_factory_make ("fakevideosink", NULL);
     ges_pipeline_preview_set_video_sink (self->priv->pipeline, sink);
   }
 
@@ -576,10 +1192,9 @@ _print_transition_list (void)
 }
 
 static GOptionGroup *
-ges_launcher_get_project_option_group (GESLauncher * self)
+ges_launcher_get_project_option_group (GESLauncherParsedOptions * opts)
 {
   GOptionGroup *group;
-  ParsedOptions *opts = &self->priv->parsed_options;
 
   GOptionEntry options[] = {
     {"load", 'l', 0, G_OPTION_ARG_STRING, &opts->load_path,
@@ -605,10 +1220,9 @@ ges_launcher_get_project_option_group (GESLauncher * self)
 }
 
 static GOptionGroup *
-ges_launcher_get_info_option_group (GESLauncher * self)
+ges_launcher_get_info_option_group (GESLauncherParsedOptions * opts)
 {
   GOptionGroup *group;
-  ParsedOptions *opts = &self->priv->parsed_options;
 
   GOptionEntry options[] = {
 #ifdef HAVE_GST_VALIDATE
@@ -634,15 +1248,16 @@ ges_launcher_get_info_option_group (GESLauncher * self)
 }
 
 static GOptionGroup *
-ges_launcher_get_rendering_option_group (GESLauncher * self)
+ges_launcher_get_rendering_option_group (GESLauncherParsedOptions * opts)
 {
   GOptionGroup *group;
-  ParsedOptions *opts = &self->priv->parsed_options;
 
   GOptionEntry options[] = {
     {"outputuri", 'o', 0, G_OPTION_ARG_STRING, &opts->outputuri,
           "If set, ges-launch-1.0 will render the timeline instead of playing "
-          "it back. The default rendering format is ogv, containing theora and vorbis.",
+          "it back. If no format `--format` is specified, the outputuri extension"
+          " will be used to determine an encoding format, or default to theora+vorbis"
+          " in ogg if that doesn't work out.",
         "<URI>"},
     {"format", 'f', 0, G_OPTION_ARG_STRING, &opts->format,
           "Set an encoding profile on the command line. "
@@ -654,6 +1269,17 @@ ges_launcher_get_rendering_option_group (GESLauncher * self)
           "See ges-launch-1.0 help profile for more information. "
           "This will have no effect if no outputuri has been specified.",
         "<profile-name>"},
+    {"profile-from", 0, 0, G_OPTION_ARG_STRING, &opts->profile_from,
+          "Use clip with name <clip-name> to determine the topology and profile "
+          "of the rendered output. This will have no effect if no outputuri "
+          "has been specified.",
+        "<clip-name>"},
+    {"forward-tags", 0, 0, G_OPTION_ARG_NONE, &opts->forward_tags,
+          "Forward tags from input files to the output",
+        NULL},
+    {"smart-rendering", 0, 0, G_OPTION_ARG_NONE, &opts->smartrender,
+          "Avoid reencoding when rendering. This option implies --disable-mixing.",
+        NULL},
     {NULL}
   };
 
@@ -666,10 +1292,9 @@ ges_launcher_get_rendering_option_group (GESLauncher * self)
 }
 
 static GOptionGroup *
-ges_launcher_get_playback_option_group (GESLauncher * self)
+ges_launcher_get_playback_option_group (GESLauncherParsedOptions * opts)
 {
   GOptionGroup *group;
-  ParsedOptions *opts = &self->priv->parsed_options;
 
   GOptionEntry options[] = {
     {"videosink", 'v', 0, G_OPTION_ARG_STRING, &opts->videosink,
@@ -689,107 +1314,273 @@ ges_launcher_get_playback_option_group (GESLauncher * self)
   return group;
 }
 
-static gboolean
-_local_command_line (GApplication * application, gchar ** arguments[],
-    gint * exit_status)
+gboolean
+ges_launcher_parse_options (GESLauncher * self,
+    gchar ** arguments[], gint * argc, GOptionContext * ctx, GError ** error)
 {
-  GESLauncher *self = GES_LAUNCHER (application);
-  GError *error = NULL;
-  gchar **argv;
-  gint argc;
-  GOptionContext *ctx;
-  ParsedOptions *opts = &self->priv->parsed_options;
+  gboolean res;
   GOptionGroup *main_group;
+  gint nargs = 0, tmpargc;
+  gchar **commands = NULL, *help, *tmp;
+  GError *err = NULL;
+  gboolean owns_ctx = ctx == NULL;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
+  gchar *prev_videosink = opts->videosink, *prev_audiosink = opts->audiosink;
+/*  *INDENT-OFF* */
   GOptionEntry options[] = {
     {"disable-mixing", 0, 0, G_OPTION_ARG_NONE, &opts->disable_mixing,
-        "Do not use mixing elements to mix layers together.", NULL},
-    {"track-types", 't', 0, G_OPTION_ARG_CALLBACK, &_parse_track_type,
-          "Specify the track types to be created. "
-          "When loading a project, only relevant tracks will be added to the timeline.",
-        "<track-types>"},
-    {"video-caps", 0, 0, G_OPTION_ARG_STRING, &opts->video_track_caps,
-        "Specify the track restriction caps of the video track.",},
-    {"audio-caps", 0, 0, G_OPTION_ARG_STRING, &opts->audio_track_caps,
-        "Specify the track restriction caps of the audio track.",},
+        "Do not use mixing elements to mix layers together.", NULL
+    },
     {"track-types", 't', 0, G_OPTION_ARG_CALLBACK, &_parse_track_type,
           "Specify the track types to be created. "
-          "When loading a project, only relevant tracks will be added to the timeline.",
-        "<track-types>"},
+          "When loading a project, only relevant tracks will be added to the "
+          "timeline.",
+        "<track-types>"
+    },
+    {
+          "video-caps",
+          0,
+          0,
+          G_OPTION_ARG_STRING,
+          &opts->video_track_caps,
+          "Specify the track restriction caps of the video track.",
+    },
+    {
+          "audio-caps",
+          0,
+          0,
+          G_OPTION_ARG_STRING,
+          &opts->audio_track_caps,
+          "Specify the track restriction caps of the audio track.",
+    },
 #ifdef HAVE_GST_VALIDATE
+    {"set-test-file", 0, 0, G_OPTION_ARG_STRING, &opts->testfile,
+          "ges-launch-1.0 exposes gst-validate functionalities, such as test files and scenarios."
+          " Scenarios describe actions to execute, such as seeks or setting of "
+          "properties. "
+          "GES implements editing-specific actions such as adding or removing "
+          "clips. "
+          "See gst-validate-1.0 --help for more info about validate and "
+          "scenarios, " "and --inspect-action-type.",
+        "</test/file/path>"
+    },
     {"set-scenario", 0, 0, G_OPTION_ARG_STRING, &opts->scenario,
           "ges-launch-1.0 exposes gst-validate functionalities, such as scenarios."
-          " Scenarios describe actions to execute, such as seeks or setting of properties. "
-          "GES implements editing-specific actions such as adding or removing clips. "
-          "See gst-validate-1.0 --help for more info about validate and scenarios, "
-          "and --inspect-action-type.",
-        "<scenario_name>"},
+          " Scenarios describe actions to execute, such as seeks or setting of "
+          "properties. "
+          "GES implements editing-specific actions such as adding or removing "
+          "clips. "
+          "See gst-validate-1.0 --help for more info about validate and "
+          "scenarios, " "and --inspect-action-type.",
+        "<scenario_name>"
+    },
+    {"enable-validate", 0, 0, G_OPTION_ARG_NONE, &opts->enable_validate,
+          "Run inside GstValidate.", NULL,
+    },
 #endif
+    {
+          "embed-nesteds",
+          0,
+          0,
+          G_OPTION_ARG_NONE,
+          &opts->embed_nesteds,
+          "Embed nested timelines when saving.",
+          NULL,
+    },
+    {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
+          &opts->interactive,
+        "Disable interactive control via the keyboard", NULL
+    },
     {NULL}
   };
+/*  *INDENT-ON* */
+
+  if (owns_ctx) {
+    opts->videosink = opts->audiosink = NULL;
+    ctx = g_option_context_new ("- plays or renders a timeline.");
+  }
+  tmpargc = argc ? *argc : g_strv_length (*arguments);
+
+  if (tmpargc > 2) {
+    nargs = tmpargc - 2;
+    commands = &(*arguments)[2];
+  }
 
-  ctx = g_option_context_new ("- plays or renders a timeline.");
-  g_option_context_set_summary (ctx, HELP_SUMMARY);
+  tmp = ges_command_line_formatter_get_help (nargs, commands);
+  help =
+      g_strdup_printf ("%s\n\nTimeline description format:\n\n%s", HELP_SUMMARY,
+      tmp);
+  g_free (tmp);
+  g_option_context_set_summary (ctx, help);
+  g_free (help);
 
   main_group =
       g_option_group_new ("launcher", "launcher options",
-      "Main launcher options", self, NULL);
+      "Main launcher options", opts, NULL);
   g_option_group_add_entries (main_group, options);
   g_option_context_set_main_group (ctx, main_group);
   g_option_context_add_group (ctx, gst_init_get_option_group ());
   g_option_context_add_group (ctx, ges_init_get_option_group ());
   g_option_context_add_group (ctx,
-      ges_launcher_get_project_option_group (self));
+      ges_launcher_get_project_option_group (opts));
   g_option_context_add_group (ctx,
-      ges_launcher_get_rendering_option_group (self));
+      ges_launcher_get_rendering_option_group (opts));
   g_option_context_add_group (ctx,
-      ges_launcher_get_playback_option_group (self));
-  g_option_context_add_group (ctx, ges_launcher_get_info_option_group (self));
+      ges_launcher_get_playback_option_group (opts));
+  g_option_context_add_group (ctx, ges_launcher_get_info_option_group (opts));
   g_option_context_set_ignore_unknown_options (ctx, TRUE);
 
-  argv = *arguments;
-  argc = g_strv_length (argv);
+  res = g_option_context_parse_strv (ctx, arguments, &err);
+  if (argc)
+    *argc = tmpargc;
+
+  if (err)
+    g_propagate_error (error, err);
+
+  opts->enable_validate |= opts->testfile || opts->scenario
+      || g_getenv ("GST_VALIDATE_SCENARIO");
+
+  if (owns_ctx) {
+    g_option_context_free (ctx);
+    /* sinks passed in the command line are preferred. */
+    if (prev_videosink) {
+      g_free (opts->videosink);
+      opts->videosink = prev_videosink;
+    }
+
+    if (prev_audiosink) {
+      g_free (opts->audiosink);
+      opts->audiosink = prev_audiosink;
+    }
+    _set_playback_details (self);
+  }
+
+  return res;
+}
+
+static gboolean
+_local_command_line (GApplication * application, gchar ** arguments[],
+    gint * exit_status)
+{
+  gboolean res = TRUE;
+  gint argc;
+  GError *error = NULL;
+  GESLauncher *self = GES_LAUNCHER (application);
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
+  GOptionContext *ctx = g_option_context_new ("- plays or renders a timeline.");
+
   *exit_status = 0;
+  argc = g_strv_length (*arguments);
 
-  if (!g_option_context_parse (ctx, &argc, &argv, &error)) {
+  gst_init (&argc, arguments);
+  if (!ges_launcher_parse_options (self, arguments, &argc, ctx, &error)) {
     gst_init (NULL, NULL);
-    g_printerr ("Error initializing: %s\n", error->message);
     g_option_context_free (ctx);
-    g_error_free (error);
+    if (error) {
+      ges_printerr ("Error initializing: %s\n", error->message);
+      g_error_free (error);
+    } else {
+      ges_printerr ("Error parsing command line arguments\n");
+    }
     *exit_status = 1;
-    return TRUE;
+    goto done;
   }
 
   if (opts->inspect_action_type) {
-    ges_validate_print_action_types ((const gchar **) argv + 1, argc - 1);
-    return TRUE;
+    ges_validate_print_action_types ((const gchar **) &((*arguments)[1]),
+        argc - 1);
+    goto done;
   }
 
-  if (!opts->load_path && !opts->scenario && !opts->list_transitions
-      && (argc <= 1)) {
-    g_printf ("%s", g_option_context_get_help (ctx, TRUE, NULL));
+  if (!opts->load_path && !opts->scenario && !opts->testfile
+      && !opts->list_transitions && (argc <= 1)) {
+    gst_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
     g_option_context_free (ctx);
     *exit_status = 1;
-    return TRUE;
+    goto done;
   }
 
   g_option_context_free (ctx);
 
-  opts->sanitized_timeline = sanitize_timeline_description (argc, argv);
+  opts->sanitized_timeline = sanitize_timeline_description (*arguments, opts);
 
   if (!g_application_register (application, NULL, &error)) {
     *exit_status = 1;
     g_clear_error (&error);
-    return FALSE;
+    res = FALSE;
   }
 
-  return TRUE;
+done:
+  return res;
+}
+
+static void
+keyboard_cb (const gchar * key_input, gpointer user_data)
+{
+  GESLauncher *self = (GESLauncher *) user_data;
+  gchar key = '\0';
+
+  /* only want to switch/case on single char, not first char of string */
+  if (key_input[0] != '\0' && key_input[1] == '\0')
+    key = g_ascii_tolower (key_input[0]);
+
+  switch (key) {
+    case 'k':
+      print_keyboard_help ();
+      break;
+    case ' ':
+      toggle_paused (self);
+      break;
+    case 'q':
+    case 'Q':
+      g_application_quit (G_APPLICATION (self));
+      break;
+    case '+':
+      if (ABS (self->priv->rate) < 2.0)
+        play_set_relative_playback_rate (self, 0.1, FALSE);
+      else if (ABS (self->priv->rate) < 4.0)
+        play_set_relative_playback_rate (self, 0.5, FALSE);
+      else
+        play_set_relative_playback_rate (self, 1.0, FALSE);
+      break;
+    case '-':
+      if (ABS (self->priv->rate) <= 2.0)
+        play_set_relative_playback_rate (self, -0.1, FALSE);
+      else if (ABS (self->priv->rate) <= 4.0)
+        play_set_relative_playback_rate (self, -0.5, FALSE);
+      else
+        play_set_relative_playback_rate (self, -1.0, FALSE);
+      break;
+    case 't':
+      play_switch_trick_mode (self);
+      break;
+    case 27:                   /* ESC */
+      if (key_input[1] == '\0') {
+        g_application_quit (G_APPLICATION (self));
+        break;
+      }
+    case '0':
+      play_do_seek (self, 0, self->priv->rate, self->priv->trick_mode);
+      break;
+    default:
+      if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
+        relative_seek (self, +0.08);
+      } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
+        relative_seek (self, -0.01);
+      } else {
+        GST_INFO ("keyboard input:");
+        for (; *key_input != '\0'; ++key_input)
+          GST_INFO ("  code %3d", *key_input);
+      }
+      break;
+  }
 }
 
 static void
 _startup (GApplication * application)
 {
   GESLauncher *self = GES_LAUNCHER (application);
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
 
 #ifdef G_OS_UNIX
   self->priv->signal_watch_id =
@@ -798,10 +1589,19 @@ _startup (GApplication * application)
 
   /* Initialize the GStreamer Editing Services */
   if (!ges_init ()) {
-    g_printerr ("Error initializing GES\n");
+    ges_printerr ("Error initializing GES\n");
     goto done;
   }
 
+  if (opts->interactive && !opts->outputuri) {
+    if (gst_play_kb_set_key_handler (keyboard_cb, self)) {
+      gst_print ("Press 'k' to see a list of keyboard shortcuts.\n");
+      atexit (restore_terminal);
+    } else {
+      gst_print ("Interactive keyboard handling in terminal not available.\n");
+    }
+  }
+
   if (opts->list_transitions) {
     _print_transition_list ();
     goto done;
@@ -816,9 +1616,6 @@ _startup (GApplication * application)
   if (!_set_playback_details (self))
     goto failure;
 
-  if (!_set_rendering_details (self))
-    goto failure;
-
   if (!_run_pipeline (self))
     goto failure;
 
@@ -838,7 +1635,7 @@ _shutdown (GApplication * application)
 {
   gint validate_res = 0;
   GESLauncher *self = GES_LAUNCHER (application);
-  ParsedOptions *opts = &self->priv->parsed_options;
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
 
   _save_timeline (self);
 
@@ -860,11 +1657,36 @@ _shutdown (GApplication * application)
 }
 
 static void
+_finalize (GObject * object)
+{
+  GESLauncher *self = GES_LAUNCHER (object);
+  GESLauncherParsedOptions *opts = &self->priv->parsed_options;
+
+  g_free (opts->load_path);
+  g_free (opts->save_path);
+  g_free (opts->save_only_path);
+  g_free (opts->outputuri);
+  g_free (opts->format);
+  g_free (opts->encoding_profile);
+  g_free (opts->profile_from);
+  g_free (opts->videosink);
+  g_free (opts->audiosink);
+  g_free (opts->video_track_caps);
+  g_free (opts->audio_track_caps);
+  g_free (opts->scenario);
+  g_free (opts->testfile);
+
+  G_OBJECT_CLASS (ges_launcher_parent_class)->finalize (object);
+}
+
+static void
 ges_launcher_class_init (GESLauncherClass * klass)
 {
   G_APPLICATION_CLASS (klass)->local_command_line = _local_command_line;
   G_APPLICATION_CLASS (klass)->startup = _startup;
   G_APPLICATION_CLASS (klass)->shutdown = _shutdown;
+
+  G_OBJECT_CLASS (klass)->finalize = _finalize;
 }
 
 static void
@@ -873,6 +1695,10 @@ ges_launcher_init (GESLauncher * self)
   self->priv = ges_launcher_get_instance_private (self);
   self->priv->parsed_options.track_types =
       GES_TRACK_TYPE_AUDIO | GES_TRACK_TYPE_VIDEO;
+  self->priv->parsed_options.interactive = TRUE;
+  self->priv->desired_state = GST_STATE_PLAYING;
+  self->priv->rate = 1.0;
+  self->priv->trick_mode = GST_PLAY_TRICK_MODE_NONE;
 }
 
 gint
index fd24398..108ca84 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef _GES_LAUNCHER
-#define _GES_LAUNCHER
+#pragma once
 
 #include <ges/ges.h>
 
+#include "utils.h"
+
 G_BEGIN_DECLS
 
 #define GES_TYPE_LAUNCHER ges_launcher_get_type()
 
-#define GES_LAUNCHER(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GES_TYPE_LAUNCHER, GESLauncher))
-
-#define GES_LAUNCHER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), GES_TYPE_LAUNCHER, GESLauncherClass))
-
-#define GES_IS_LAUNCHER(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GES_TYPE_LAUNCHER))
-
-#define GES_IS_LAUNCHER_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), GES_TYPE_LAUNCHER))
-
-#define GES_LAUNCHER_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), GES_TYPE_LAUNCHER, GESLauncherClass))
-
 typedef struct _GESLauncherPrivate GESLauncherPrivate;
-typedef struct _GESLauncher GESLauncher;
-typedef struct _GESLauncherClass GESLauncherClass;
+G_DECLARE_FINAL_TYPE(GESLauncher, ges_launcher, GES, LAUNCHER, GApplication);
 
 struct _GESLauncher {
   GApplication parent;
@@ -55,19 +40,8 @@ struct _GESLauncher {
   gpointer _ges_reserved[GES_PADDING];
 };
 
-struct _GESLauncherClass {
-  /*< private >*/
-  GApplicationClass parent_class;
-
-  /* Padding for API extension */
-  gpointer _ges_reserved[GES_PADDING];
-};
-
-GType ges_launcher_get_type (void);
-
 GESLauncher* ges_launcher_new (void);
 gint ges_launcher_get_exit_status (GESLauncher *self);
+gboolean ges_launcher_parse_options(GESLauncher* self, gchar*** arguments, gint *argc, GOptionContext* ctx, GError** error);
 
 G_END_DECLS
-
-#endif /* _GES_LAUNCHER */
index dd755a7..5ea786e 100644 (file)
 #include "config.h"
 #endif
 
+#include "utils.h"
 #include "ges-validate.h"
 
 #include <string.h>
-#include <ges/ges.h>
+
+static gboolean
+_print_position (GstElement * pipeline)
+{
+  gint64 position = 0, duration = -1;
+
+  if (pipeline) {
+    gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
+        &position);
+    gst_element_query_duration (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
+        &duration);
+
+    gst_print ("<position: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT
+        "/>\r", GST_TIME_ARGS (position), GST_TIME_ARGS (duration));
+  }
+
+  return TRUE;
+}
 
 #ifdef HAVE_GST_VALIDATE
 #include <gst/validate/gst-validate-scenario.h>
 #include <gst/validate/validate.h>
 #include <gst/validate/gst-validate-utils.h>
 #include <gst/validate/gst-validate-element-monitor.h>
+#include <gst/validate/gst-validate-bin-monitor.h>
 
 #define MONITOR_ON_PIPELINE "validate-monitor"
 #define RUNNER_ON_PIPELINE "runner-monitor"
@@ -61,7 +80,6 @@ bin_element_added (GstTracer * runner, GstClockTime ts,
   if (!monitor->is_decoder)
     return;
 
-
   parent = gst_object_get_parent (GST_OBJECT (element));
   do {
     if (GES_IS_TRACK (parent)) {
@@ -100,25 +118,39 @@ ges_validate_register_issues (void)
           " in a Video track).", GST_VALIDATE_REPORT_LEVEL_CRITICAL));
 }
 
-
 gboolean
-ges_validate_activate (GstPipeline * pipeline, const gchar * scenario,
-    gboolean * needs_setting_state)
+ges_validate_activate (GstPipeline * pipeline, GESLauncher * launcher,
+    GESLauncherParsedOptions * opts)
 {
   GstValidateRunner *runner = NULL;
   GstValidateMonitor *monitor = NULL;
 
-  ges_validate_register_action_types ();
-  ges_validate_register_issues ();
+  if (!opts->enable_validate) {
+    opts->needs_set_state = TRUE;
+    g_object_set_data (G_OBJECT (pipeline), "pposition-id",
+        GUINT_TO_POINTER (g_timeout_add (200,
+                (GSourceFunc) _print_position, pipeline)));
+    return TRUE;
+  }
+
+  gst_validate_init_debug ();
 
-  if (scenario) {
-    if (g_strcmp0 (scenario, "none")) {
-      gchar *scenario_name = g_strconcat (scenario, "->gespipeline*", NULL);
+  if (opts->testfile) {
+    if (opts->scenario)
+      g_error ("Can not specify scenario and testfile at the same time");
+    gst_validate_setup_test_file (opts->testfile, opts->mute);
+  } else if (opts->scenario) {
+    if (g_strcmp0 (opts->scenario, "none")) {
+      gchar *scenario_name =
+          g_strconcat (opts->scenario, "->gespipeline*", NULL);
       g_setenv ("GST_VALIDATE_SCENARIO", scenario_name, TRUE);
       g_free (scenario_name);
     }
   }
 
+  ges_validate_register_action_types ();
+  ges_validate_register_issues ();
+
   runner = gst_validate_runner_new ();
   gst_tracing_register_hook (GST_TRACER (runner), "bin-add-post",
       G_CALLBACK (bin_element_added));
@@ -127,11 +159,39 @@ ges_validate_activate (GstPipeline * pipeline, const gchar * scenario,
   monitor =
       gst_validate_monitor_factory_create (GST_OBJECT_CAST (pipeline), runner,
       NULL);
+  if (GST_VALIDATE_BIN_MONITOR (monitor)->scenario) {
+    GstStructure *metas =
+        GST_VALIDATE_BIN_MONITOR (monitor)->scenario->description;
+
+    if (metas) {
+      gchar **ges_options = gst_validate_utils_get_strv (metas, "ges-options");
+      if (!ges_options)
+        ges_options = gst_validate_utils_get_strv (metas, "args");
+
+      gst_structure_get_boolean (metas, "ignore-eos", &opts->ignore_eos);
+      if (ges_options) {
+        gint i;
+        gchar **ges_options_full =
+            g_new0 (gchar *, g_strv_length (ges_options) + 2);
+
+        ges_options_full[0] = g_strdup ("something");
+        for (i = 0; ges_options[i]; i++)
+          ges_options_full[i + 1] = g_strdup (ges_options[i]);
+
+        ges_launcher_parse_options (launcher, &ges_options_full, NULL, NULL,
+            NULL);
+        opts->sanitized_timeline =
+            sanitize_timeline_description (ges_options_full, opts);
+        g_strfreev (ges_options_full);
+        g_strfreev (ges_options);
+      }
+    }
+  }
 
   gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor));
 
-  g_object_get (monitor, "handles-states", needs_setting_state, NULL);
-  *needs_setting_state = !*needs_setting_state;
+  g_object_get (monitor, "handles-states", &opts->needs_set_state, NULL);
+  opts->needs_set_state = !opts->needs_set_state;
   g_object_set_data (G_OBJECT (pipeline), MONITOR_ON_PIPELINE, monitor);
   g_object_set_data (G_OBJECT (pipeline), RUNNER_ON_PIPELINE, runner);
 
@@ -149,13 +209,15 @@ ges_validate_clean (GstPipeline * pipeline)
 
   if (runner)
     res = gst_validate_runner_exit (runner, TRUE);
+  else
+    g_source_remove (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pipeline),
+                "pposition-id")));
 
   gst_object_unref (pipeline);
-  if (runner) {
+  if (runner)
     gst_object_unref (runner);
-    if (monitor)
-      gst_object_unref (monitor);
-  }
+  if (monitor)
+    gst_object_unref (monitor);
 
   return res;
 }
@@ -171,7 +233,7 @@ ges_validate_handle_request_state_change (GstMessage * message,
   if (GST_IS_VALIDATE_SCENARIO (GST_MESSAGE_SRC (message))
       && state == GST_STATE_NULL) {
     gst_validate_printf (GST_MESSAGE_SRC (message),
-        "State change request NULL, " "quiting application\n");
+        "State change request NULL, " "quitting application\n");
     g_application_quit (application);
   }
 }
@@ -190,31 +252,20 @@ ges_validate_print_action_types (const gchar ** types, gint num_types)
 }
 
 #else
-static gboolean
-_print_position (GstElement * pipeline)
+gboolean
+ges_validate_activate (GstPipeline * pipeline, GESLauncher * launcher,
+    GESLauncherParsedOptions * opts)
 {
-  gint64 position = 0, duration = -1;
-
-  if (pipeline) {
-    gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
-        &position);
-    gst_element_query_duration (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
-        &duration);
+  if (opts->testfile) {
+    GST_WARNING ("Trying to run testfile %s, but gst-validate not supported",
+        opts->testfile);
 
-    g_print ("<position: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT
-        "/>\r", GST_TIME_ARGS (position), GST_TIME_ARGS (duration));
+    return FALSE;
   }
 
-  return TRUE;
-}
-
-gboolean
-ges_validate_activate (GstPipeline * pipeline, const gchar * scenario,
-    gboolean * needs_setting_state)
-{
-  if (scenario) {
+  if (opts->scenario) {
     GST_WARNING ("Trying to run scenario %s, but gst-validate not supported",
-        scenario);
+        opts->scenario);
 
     return FALSE;
   }
@@ -223,7 +274,7 @@ ges_validate_activate (GstPipeline * pipeline, const gchar * scenario,
       GUINT_TO_POINTER (g_timeout_add (200,
               (GSourceFunc) _print_position, pipeline)));
 
-  *needs_setting_state = TRUE;
+  opts->needs_set_state = TRUE;
 
   return TRUE;
 }
index 4363e89..1bdc16a 100644 (file)
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
-#ifndef _GES_VALIDATE_
-#define _GES_VALIDATE_
+#pragma once
 
 #include <glib.h>
 #include <gio/gio.h>
 #include <gst/gst.h>
+#include <ges/ges.h>
+
+#include "utils.h"
+#include "ges-launcher.h"
 
 G_BEGIN_DECLS
 
 gboolean
-ges_validate_activate (GstPipeline *pipeline, const gchar *scenario, gboolean *needs_set_state);
-void ges_launch_validate_uri (const gchar *nid);
+ges_validate_activate(GstPipeline* pipeline, GESLauncher *launcher, GESLauncherParsedOptions* opts);
+void ges_launch_validate_uri(const gchar* nid);
 
 gint
 ges_validate_clean (GstPipeline *pipeline);
@@ -37,5 +40,3 @@ void ges_validate_handle_request_state_change (GstMessage *message, GApplication
 gint ges_validate_print_action_types (const gchar **types, gint num_types);
 
 G_END_DECLS
-
-#endif  /* _GES_VALIDATE */
index 60fe5ed..a3c7a29 100644 (file)
@@ -1,4 +1,4 @@
-deps = [ges_dep, gstpbutils_dep, gio_dep]
+deps = [ges_dep, gstpbutils_dep, gio_dep, gstvideo_dep, gstaudio_dep]
 
 ges_tool_args = [ges_c_args]
 if gstvalidate_dep.found()
@@ -6,11 +6,48 @@ if gstvalidate_dep.found()
   ges_tool_args += ['-DGST_USE_UNSTABLE_API']
 endif
 
-executable('ges-launch-@0@'.format(apiversion),
-    'ges-validate.c', 'ges-launch.c', 'ges-launcher.c', 'utils.c',
+ges_launch = executable('ges-launch-@0@'.format(apiversion),
+    'ges-validate.c', 'ges-launch.c', 'ges-launcher.c', 'utils.c', 'ges-launcher-kb.c',
     c_args : [ges_tool_args],
     dependencies : deps,
     install: true
 )
 
-install_man('ges-launch-1.0.1')
+#install_man('ges-launch-1.0.1')
+
+# bash completion
+bashcomp_option = get_option('bash-completion')
+bashcomp_dep = dependency('bash-completion', version : '>= 2.0', required : bashcomp_option)
+bash_completions_dir = ''
+bash_helpers_dir = ''
+
+bashcomp_found = false
+if bashcomp_dep.found()
+  bashcomp_found = true
+  bashcomp_dir_override = bashcomp_dep.version().version_compare('>= 2.10') ? ['datadir', datadir] : ['prefix', prefix]
+  bash_completions_dir = bashcomp_dep.get_pkgconfig_variable('completionsdir', define_variable: bashcomp_dir_override)
+  if bash_completions_dir == ''
+    msg = 'Found bash-completion but the .pc file did not set \'completionsdir\'.'
+    if bashcomp_option.enabled()
+      error(msg)
+    else
+      message(msg)
+    endif
+    bashcomp_found = false
+  endif
+
+  bash_helpers_dir = bashcomp_dep.get_pkgconfig_variable('helpersdir', define_variable: bashcomp_dir_override)
+  if bash_helpers_dir == ''
+    msg = 'Found bash-completion, but the .pc file did not set \'helpersdir\'.'
+    if bashcomp_option.enabled()
+      error(msg)
+    else
+      message(msg)
+    endif
+    bashcomp_found = false
+  endif
+
+  if bashcomp_found
+    install_data('../data/completions/ges-launch-1.0', install_dir : bash_completions_dir)
+  endif
+endif
index 23d7e42..9b368fe 100644 (file)
 #include <string.h>
 #include <gst/gst.h>
 #include "utils.h"
+#include "../ges/ges-internal.h"
 
-#define IS_ALPHANUM(c) (g_ascii_isalnum((c)) || ((c) == '-') || ((c) == '+'))
+#undef GST_CAT_DEFAULT
+
+/* Copy of GST_ASCII_IS_STRING */
+#define ASCII_IS_STRING(c) (g_ascii_isalnum((c)) || ((c) == '_') || \
+    ((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == ':') || \
+    ((c) == '.'))
 
 /* g_free after usage */
 static gchar *
-_sanitize_argument (gchar * arg)
+_sanitize_argument (gchar * arg, const gchar * prev_arg)
 {
-  gboolean has_non_alphanum = FALSE;
-  char *equal_index = strstr (arg, "=");
+  gboolean expect_equal = !(arg[0] == '+' || g_str_has_prefix (arg, "set-")
+      || prev_arg == NULL || prev_arg[0] == '+'
+      || g_str_has_prefix (prev_arg, "set-"));
+  gboolean need_wrap = FALSE;
+  gchar *first_equal = NULL;
+  gchar *wrap_start;
   gchar *new_string, *tmp_string;
+  gsize num_escape;
 
   for (tmp_string = arg; *tmp_string != '\0'; tmp_string++) {
-    if (!IS_ALPHANUM (*tmp_string)) {
-      has_non_alphanum = TRUE;
-
+    if (expect_equal && first_equal == NULL && *tmp_string == '=') {
+      first_equal = tmp_string;
+      /* if this is the first equal, then don't count it as necessarily
+       * needing a wrap */
+    } else if (!ASCII_IS_STRING (*tmp_string)) {
+      need_wrap = TRUE;
       break;
     }
   }
 
-  if (!has_non_alphanum)
+  if (!need_wrap)
     return g_strdup (arg);
 
-  if (!equal_index)
-    return g_strdup_printf ("\"%s\"", arg);
+  if (first_equal)
+    wrap_start = first_equal + 1;
+  else
+    wrap_start = arg;
 
-  tmp_string = new_string = g_malloc (sizeof (gchar) * (strlen (arg) + 3));
-  for (; *arg != '\0'; arg++) {
-    *tmp_string = *arg;
-    tmp_string += 1;
-    if (*arg == '=') {
-      *tmp_string = '"';
-      tmp_string += 1;
-    }
+  /* need to escape any '"' or '\\' to correctly parse in as a structure */
+  num_escape = 0;
+  for (tmp_string = wrap_start; *tmp_string != '\0'; tmp_string++) {
+    if (*tmp_string == '"' || *tmp_string == '\\')
+      num_escape++;
   }
-  *tmp_string = '"';
-  tmp_string += 1;
+
+  tmp_string = new_string =
+      g_malloc (sizeof (gchar) * (strlen (arg) + num_escape + 3));
+
+  while (arg != wrap_start)
+    *(tmp_string++) = *(arg++);
+  (*tmp_string++) = '"';
+
+  while (*arg != '\0') {
+    if (*arg == '"' || *arg == '\\')
+      (*tmp_string++) = '\\';
+    *(tmp_string++) = *(arg++);
+  }
+  *(tmp_string++) = '"';
   *tmp_string = '\0';
 
   return new_string;
 }
 
 gchar *
-sanitize_timeline_description (int argc, char **argv)
+sanitize_timeline_description (gchar ** args, GESLauncherParsedOptions * opts)
 {
   gint i;
+  gchar *prev_arg = NULL;
+  GString *track_def;
+  GString *timeline_str;
 
   gchar *string = g_strdup (" ");
 
-  for (i = 1; i < argc; i++) {
+  for (i = 1; args[i]; i++) {
     gchar *new_string;
-    gchar *sanitized = _sanitize_argument (argv[i]);
+    gchar *sanitized = _sanitize_argument (args[i], prev_arg);
 
     new_string = g_strconcat (string, " ", sanitized, NULL);
 
     g_free (sanitized);
     g_free (string);
     string = new_string;
+    prev_arg = args[i];
+  }
+
+  if (i == 1) {
+    g_free (string);
+
+    return NULL;
+  }
+
+  if (strstr (string, "+track")) {
+    gchar *res = g_strconcat ("ges:", string, NULL);
+    g_free (string);
+
+    return res;
   }
 
-  return string;
+  timeline_str = g_string_new (string);
+  g_free (string);
+
+  if (opts->track_types & GES_TRACK_TYPE_VIDEO) {
+    track_def = g_string_new (" +track video ");
+
+    if (opts->video_track_caps)
+      g_string_append_printf (track_def, " restrictions=[%s] ",
+          opts->video_track_caps);
+
+    g_string_prepend (timeline_str, track_def->str);
+    g_string_free (track_def, TRUE);
+  }
+
+  if (opts->track_types & GES_TRACK_TYPE_AUDIO) {
+    track_def = g_string_new (" +track audio ");
+
+    if (opts->audio_track_caps)
+      g_string_append_printf (track_def, " restrictions=[%s] ",
+          opts->audio_track_caps);
+
+    g_string_prepend (timeline_str, track_def->str);
+    g_string_free (track_def, TRUE);
+  }
+
+  g_string_prepend (timeline_str, "ges:");
+
+  return g_string_free (timeline_str, FALSE);
 }
 
-guint
-get_flags_from_string (GType type, const gchar * str_flags)
+gboolean
+get_flags_from_string (GType type, const gchar * str_flags, guint * flags)
 {
-  guint i;
-  gint flags = 0;
-  GFlagsClass *class = g_type_class_ref (type);
+  GValue value = G_VALUE_INIT;
+  g_value_init (&value, type);
 
-  for (i = 0; i < class->n_values; i++) {
-    if (g_strrstr (str_flags, class->values[i].value_nick)) {
-      flags |= class->values[i].value;
-    }
+  if (!gst_value_deserialize (&value, str_flags)) {
+    g_value_unset (&value);
+
+    return FALSE;
   }
-  g_type_class_unref (class);
 
-  return flags;
+  *flags = g_value_get_flags (&value);
+  g_value_unset (&value);
+
+  return TRUE;
 }
 
 gchar *
@@ -137,8 +207,288 @@ print_enum (GType enum_type)
   guint i;
 
   for (i = 0; i < enum_class->n_values; i++) {
-    g_printf ("%s\n", enum_class->values[i].value_nick);
+    gst_print ("%s\n", enum_class->values[i].value_nick);
   }
 
   g_type_class_unref (enum_class);
 }
+
+void
+ges_print (GstDebugColorFlags c, gboolean err, gboolean nline,
+    const gchar * format, va_list var_args)
+{
+  GString *str = g_string_new (NULL);
+  GstDebugColorMode color_mode;
+  gchar *color = NULL;
+  const gchar *clear = NULL;
+
+  color_mode = gst_debug_get_color_mode ();
+#ifdef G_OS_WIN32
+  if (color_mode == GST_DEBUG_COLOR_MODE_UNIX) {
+#else
+  if (color_mode != GST_DEBUG_COLOR_MODE_OFF) {
+#endif
+    clear = "\033[00m";
+    color = gst_debug_construct_term_color (c);
+  }
+
+  if (color) {
+    g_string_append (str, color);
+    g_free (color);
+  }
+
+  g_string_append_vprintf (str, format, var_args);
+
+  if (nline)
+    g_string_append_c (str, '\n');
+
+  if (clear)
+    g_string_append (str, clear);
+
+  if (err)
+    gst_printerr ("%s", str->str);
+  else
+    gst_print ("%s", str->str);
+
+  g_string_free (str, TRUE);
+}
+
+void
+ges_ok (const gchar * format, ...)
+{
+  va_list var_args;
+
+  va_start (var_args, format);
+  ges_print (GST_DEBUG_FG_GREEN, FALSE, TRUE, format, var_args);
+  va_end (var_args);
+}
+
+void
+ges_warn (const gchar * format, ...)
+{
+  va_list var_args;
+
+  va_start (var_args, format);
+  ges_print (GST_DEBUG_FG_YELLOW, TRUE, TRUE, format, var_args);
+  va_end (var_args);
+}
+
+void
+ges_printerr (const gchar * format, ...)
+{
+  va_list var_args;
+
+  va_start (var_args, format);
+  ges_print (GST_DEBUG_FG_RED, TRUE, TRUE, format, var_args);
+  va_end (var_args);
+}
+
+gchar *
+get_file_extension (gchar * uri)
+{
+  size_t len;
+  gint find;
+
+  len = strlen (uri);
+  find = len - 1;
+
+  while (find >= 0) {
+    if (uri[find] == '.')
+      break;
+    find--;
+  }
+
+  if (find <= 0)
+    return NULL;
+
+  return g_strdup (&uri[find + 1]);
+}
+
+static const gchar *
+get_type_icon (gpointer obj)
+{
+  if (GST_IS_ENCODING_AUDIO_PROFILE (obj) || GST_IS_DISCOVERER_AUDIO_INFO (obj))
+    return "♫";
+  else if (GST_IS_ENCODING_VIDEO_PROFILE (obj)
+      || GST_IS_DISCOVERER_VIDEO_INFO (obj))
+    return "▶";
+  else if (GST_IS_ENCODING_CONTAINER_PROFILE (obj)
+      || GST_IS_DISCOVERER_CONTAINER_INFO (obj))
+    return "∋";
+  else
+    return "";
+}
+
+static void
+print_profile (GstEncodingProfile * profile, const gchar * prefix)
+{
+  const gchar *name = gst_encoding_profile_get_name (profile);
+  const gchar *desc = gst_encoding_profile_get_description (profile);
+  GstCaps *format = gst_encoding_profile_get_format (profile);
+  gchar *capsdesc = NULL;
+
+  if (gst_caps_is_fixed (format))
+    capsdesc = gst_pb_utils_get_codec_description (format);
+  if (!capsdesc)
+    capsdesc = gst_caps_to_string (format);
+
+  if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
+    gst_print ("%s> %s %s: %s%s%s%s\n", prefix,
+        get_type_icon (profile),
+        capsdesc, name ? name : "",
+        desc ? " (" : "", desc ? desc : "", desc ? ")" : "");
+
+  } else {
+    gst_print ("%s%s %s%s%s%s%s%s", prefix, get_type_icon (profile),
+        name ? name : capsdesc, desc ? ": " : "", desc ? desc : "",
+        name ? " (" : "", name ? capsdesc : "", name ? ")" : "");
+
+    if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) {
+      GstCaps *caps = gst_encoding_profile_get_restriction (profile);
+
+      if (!caps && gst_caps_is_fixed (format))
+        caps = gst_caps_ref (format);
+
+      if (caps) {
+        GstVideoInfo info;
+
+        if (gst_video_info_from_caps (&info, caps)) {
+          gst_print (" (%dx%d", info.width, info.height);
+          if (info.fps_n)
+            gst_print ("@%d/%dfps", info.fps_n, info.fps_d);
+          gst_print (")");
+        }
+        gst_caps_unref (caps);
+      }
+    } else if (GST_IS_ENCODING_AUDIO_PROFILE (profile)) {
+      GstCaps *caps = gst_encoding_profile_get_restriction (profile);
+
+      if (!caps && gst_caps_is_fixed (format))
+        caps = gst_caps_ref (format);
+
+      if (caps) {
+        GstAudioInfo info;
+
+        if (gst_caps_is_fixed (caps) && gst_audio_info_from_caps (&info, caps))
+          gst_print (" (%d channels @ %dhz)", info.channels, info.rate);
+        gst_caps_unref (caps);
+      }
+    }
+
+
+    gst_print ("\n");
+  }
+
+  gst_caps_unref (format);
+
+  g_free (capsdesc);
+}
+
+void
+describe_encoding_profile (GstEncodingProfile * profile)
+{
+  g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
+
+  print_profile (profile, "     ");
+  if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
+    const GList *tmp;
+
+    for (tmp =
+        gst_encoding_container_profile_get_profiles
+        (GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next)
+      print_profile (tmp->data, "       - ");
+  }
+}
+
+static void
+describe_stream_info (GstDiscovererStreamInfo * sinfo, GString * desc)
+{
+  gchar *capsdesc;
+  GstCaps *caps;
+
+  caps = gst_discoverer_stream_info_get_caps (sinfo);
+  capsdesc = gst_pb_utils_get_codec_description (caps);
+  if (!capsdesc)
+    capsdesc = gst_caps_to_string (caps);
+  gst_caps_unref (caps);
+
+  g_string_append_printf (desc, "%s%s%s", desc->len ? ", " : "",
+      get_type_icon (sinfo), capsdesc);
+
+  if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) {
+    GList *tmp, *streams;
+
+    streams =
+        gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
+        (sinfo));
+    for (tmp = streams; tmp; tmp = tmp->next)
+      describe_stream_info (tmp->data, desc);
+    gst_discoverer_stream_info_list_free (streams);
+  }
+}
+
+static gchar *
+describe_discoverer (GstDiscovererInfo * info)
+{
+  GString *desc = g_string_new (NULL);
+  GstDiscovererStreamInfo *sinfo = gst_discoverer_info_get_stream_info (info);
+
+  describe_stream_info (sinfo, desc);
+  gst_discoverer_stream_info_unref (sinfo);
+
+  return g_string_free (desc, FALSE);
+}
+
+void
+print_timeline (GESTimeline * timeline)
+{
+  gchar *uri;
+  GList *layer, *clip, *clips;
+
+  if (!timeline->layers)
+    return;
+
+  uri = ges_command_line_formatter_get_timeline_uri (timeline);
+  gst_print ("\nTimeline description: `%s`\n", &uri[5]);
+  g_free (uri);
+  gst_print ("====================\n\n");
+  for (layer = timeline->layers; layer; layer = layer->next) {
+    clips = ges_layer_get_clips (layer->data);
+
+    if (!clips)
+      continue;
+
+    gst_printerr ("  layer %d: \n", ges_layer_get_priority (layer->data));
+    gst_printerr ("  --------\n");
+    for (clip = clips; clip; clip = clip->next) {
+      gchar *name;
+
+      if (GES_IS_URI_CLIP (clip->data)) {
+        GESUriClipAsset *asset =
+            GES_URI_CLIP_ASSET (ges_extractable_get_asset (clip->data));
+        gchar *asset_desc =
+            describe_discoverer (ges_uri_clip_asset_get_info (asset));
+
+        name = g_strdup_printf ("Clip from: '%s' [%s]",
+            ges_asset_get_id (GES_ASSET (asset)), asset_desc);
+        g_free (asset_desc);
+      } else {
+        name = g_strdup (GES_TIMELINE_ELEMENT_NAME (clip->data));
+      }
+      gst_print ("    - %s\n        start=%" GST_TIME_FORMAT,
+          name, GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (clip->data)));
+      g_free (name);
+      if (GES_TIMELINE_ELEMENT_INPOINT (clip->data))
+        gst_print (" inpoint=%" GST_TIME_FORMAT,
+            GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (clip->data)));
+      gst_print (" duration=%" GST_TIME_FORMAT "\n",
+          GST_TIME_ARGS (GES_TIMELINE_ELEMENT_END (clip->data)));
+    }
+    if (layer->next)
+      gst_printerr ("\n");
+
+    g_list_free_full (clips, gst_object_unref);
+  }
+
+  gst_print ("\n");
+}
index c4e63e9..09ba6f2 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
+#include <ges/ges.h>
+#include <gst/pbutils/pbutils.h>
 #include <gst/pbutils/encoding-profile.h>
 
-gchar * sanitize_timeline_description (int argc, char **argv);
-guint get_flags_from_string (GType type, const gchar * str_flags);
+#pragma once
+
+typedef struct
+{
+  gboolean mute;
+  gboolean disable_mixing;
+  gchar *save_path;
+  gchar *save_only_path;
+  gchar *load_path;
+  GESTrackType track_types;
+  gboolean needs_set_state;
+  gboolean smartrender;
+  gchar *scenario;
+  gchar *testfile;
+  gchar *format;
+  gchar *outputuri;
+  gchar *encoding_profile;
+  gchar *profile_from;
+  gchar *videosink;
+  gchar *audiosink;
+  gboolean list_transitions;
+  gboolean inspect_action_type;
+  gchar *sanitized_timeline;
+  gchar *video_track_caps;
+  gchar *audio_track_caps;
+  gboolean embed_nesteds;
+  gboolean enable_validate;
+
+  gboolean ignore_eos;
+  gboolean interactive;
+  gboolean forward_tags;
+} GESLauncherParsedOptions;
+
+gchar * sanitize_timeline_description (gchar **args, GESLauncherParsedOptions *opts);
+gboolean get_flags_from_string (GType type, const gchar * str_flags, guint *val);
 gchar * ensure_uri (const gchar * location);
 GstEncodingProfile * parse_encoding_profile (const gchar * format);
 void print_enum (GType enum_type);
+
+void ges_print (GstDebugColorFlags c, gboolean err, gboolean nline, const gchar * format, va_list var_args);
+void ges_ok (const gchar * format, ...);
+void ges_warn (const gchar * format, ...);
+void ges_printerr (const gchar * format, ...);
+
+gchar * get_file_extension (gchar * uri);
+void describe_encoding_profile (GstEncodingProfile *profile);
+void print_timeline(GESTimeline *timeline);