mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-03-30 12:49:40 +00:00
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
This commit is contained in:
parent
5f3adbc1a3
commit
a5fa2c35aa
8 changed files with 634 additions and 126 deletions
|
@ -73,7 +73,6 @@ typedef enum
|
|||
struct _GESBaseXmlFormatterPrivate
|
||||
{
|
||||
GMarkupParseContext *parsecontext;
|
||||
gchar *xmlcontent;
|
||||
gsize xmlsize;
|
||||
LoadingState state;
|
||||
|
||||
|
@ -139,6 +138,12 @@ G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESBaseXmlFormatter,
|
|||
static gint
|
||||
compare_assets_for_loading (PendingAsset * a, PendingAsset * b)
|
||||
{
|
||||
if (a->extractable_type == GES_TYPE_TIMELINE)
|
||||
return -1;
|
||||
|
||||
if (b->extractable_type == GES_TYPE_TIMELINE)
|
||||
return 1;
|
||||
|
||||
if (a->proxy_id)
|
||||
return -1;
|
||||
|
||||
|
@ -157,7 +162,7 @@ _parse (GESBaseXmlFormatter * self, GError ** error, LoadingState state)
|
|||
GES_BASE_XML_FORMATTER_GET_CLASS (self);
|
||||
GESBaseXmlFormatterPrivate *priv = _GET_PRIV (self);
|
||||
|
||||
if (!priv->xmlcontent || g_strcmp0 (priv->xmlcontent, "") == 0) {
|
||||
if (!self->xmlcontent || g_strcmp0 (self->xmlcontent, "") == 0) {
|
||||
err = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
|
||||
"Nothing contained in the project file.");
|
||||
|
||||
|
@ -169,7 +174,7 @@ _parse (GESBaseXmlFormatter * self, GError ** error, LoadingState state)
|
|||
|
||||
priv->state = state;
|
||||
GST_DEBUG_OBJECT (self, "Running %s pass", loading_state_name (state));
|
||||
if (!g_markup_parse_context_parse (parsecontext, priv->xmlcontent,
|
||||
if (!g_markup_parse_context_parse (parsecontext, self->xmlcontent,
|
||||
priv->xmlsize, &err))
|
||||
goto failed;
|
||||
|
||||
|
@ -215,7 +220,8 @@ _load_and_parse (GESBaseXmlFormatter * self, const gchar * uri, GError ** error,
|
|||
|
||||
GError *err = NULL;
|
||||
|
||||
GST_INFO_OBJECT (self, "loading xml from %s", uri);
|
||||
GST_DEBUG_OBJECT (self, "loading xml from %s, %s", uri,
|
||||
loading_state_name (state));
|
||||
|
||||
file = g_file_new_for_uri (uri);
|
||||
/* TODO Handle GCancellable */
|
||||
|
@ -225,8 +231,8 @@ _load_and_parse (GESBaseXmlFormatter * self, const gchar * uri, GError ** error,
|
|||
goto failed;
|
||||
}
|
||||
|
||||
g_clear_pointer (&priv->xmlcontent, g_free);
|
||||
if (!g_file_load_contents (file, NULL, &priv->xmlcontent, &priv->xmlsize,
|
||||
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);
|
||||
|
@ -374,11 +380,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_free (priv->xmlcontent);
|
||||
g_clear_pointer (&self->xmlcontent, g_free);
|
||||
|
||||
g_list_free_full (priv->groups, (GDestroyNotify) _free_pending_group);
|
||||
priv->groups = NULL;
|
||||
|
|
|
@ -45,8 +45,9 @@ struct _GESBaseXmlFormatter
|
|||
/*< public > */
|
||||
/* <private> */
|
||||
GESBaseXmlFormatterPrivate *priv;
|
||||
gchar *xmlcontent;
|
||||
|
||||
gpointer _ges_reserved[GES_PADDING];
|
||||
gpointer _ges_reserved[GES_PADDING - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -326,6 +326,12 @@ 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_xml_formatter_deinit (void);
|
||||
|
||||
G_GNUC_INTERNAL gboolean set_property_foreach (GQuark field_id,
|
||||
const GValue * value,
|
||||
GObject * object);
|
||||
|
@ -345,11 +351,6 @@ G_GNUC_INTERNAL gint element_end_compare (GESTimelineElement *
|
|||
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);
|
||||
|
||||
/****************************************************
|
||||
* GESContainer *
|
||||
|
|
|
@ -43,6 +43,24 @@
|
|||
* 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
|
||||
* with 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"
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,13 +26,6 @@
|
|||
* GES needs to be initialized after GStreamer itself. This section
|
||||
* contains the various functions to do so.
|
||||
*/
|
||||
/* TODO
|
||||
* Add a deinit function
|
||||
*
|
||||
* Do not forget to
|
||||
* + g_ptr_array_unref (new_paths);
|
||||
* + g_hash_table_unref (tried_uris);
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
|
@ -229,6 +222,7 @@ ges_deinit (void)
|
|||
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);
|
||||
|
|
|
@ -62,24 +62,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
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
from . import overrides_hack
|
||||
|
||||
import tempfile # noqa
|
||||
import gi
|
||||
|
||||
gi.require_version("Gst", "1.0")
|
||||
|
@ -45,7 +46,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 +63,77 @@ 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_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()
|
||||
|
|
Loading…
Reference in a new issue