mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
Implement a formatter based on [OpenTimelineIO]
[OpenTimelineIO]: http://opentimeline.io/
This commit is contained in:
parent
451f67e3d6
commit
c5c451fc1c
6 changed files with 303 additions and 8 deletions
|
@ -36,6 +36,14 @@
|
||||||
#include "ges-formatter.h"
|
#include "ges-formatter.h"
|
||||||
#include "ges-internal.h"
|
#include "ges-internal.h"
|
||||||
#include "ges.h"
|
#include "ges.h"
|
||||||
|
#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
|
||||||
|
|
||||||
/* TODO Add a GCancellable somewhere in the API */
|
/* TODO Add a GCancellable somewhere in the API */
|
||||||
static void ges_extractable_interface_init (GESExtractableInterface * iface);
|
static void ges_extractable_interface_init (GESExtractableInterface * iface);
|
||||||
|
@ -100,10 +108,9 @@ _register_metas (GESExtractableInterface * iface, GObjectClass * class,
|
||||||
ges_meta_container_register_meta_string (container, GES_META_READ_WRITE,
|
ges_meta_container_register_meta_string (container, GES_META_READ_WRITE,
|
||||||
GES_META_FORMAT_VERSION, NULL);
|
GES_META_FORMAT_VERSION, NULL);
|
||||||
|
|
||||||
g_clear_pointer (&fclass->name, g_free);
|
/* We are leaking the metadata but we don't really have choice here
|
||||||
g_clear_pointer (&fclass->description, g_free);
|
* as calling ges_init() after deinit() is allowed.
|
||||||
g_clear_pointer (&fclass->extension, g_free);
|
*/
|
||||||
g_clear_pointer (&fclass->mimetype, g_free);
|
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -513,11 +520,110 @@ _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 ()) {
|
||||||
|
PyErr_Print ();
|
||||||
|
PyErr_Clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (bytes)
|
||||||
|
g_bytes_unref (bytes);
|
||||||
|
|
||||||
|
if (we_initialized) {
|
||||||
|
PyEval_SaveThread ();
|
||||||
|
} else {
|
||||||
|
PyGILState_Release (state);
|
||||||
|
}
|
||||||
|
#endif /* HAS_PYTHON */
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_init_formatter_assets (void)
|
_init_formatter_assets (void)
|
||||||
{
|
{
|
||||||
GType *formatters;
|
GType *formatters;
|
||||||
guint n_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
load_python_formatters ();
|
||||||
|
|
||||||
|
|
||||||
formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
|
formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
|
||||||
_list_formatters (formatters, n_formatters);
|
_list_formatters (formatters, n_formatters);
|
||||||
|
|
6
ges/ges.resource
Normal file
6
ges/ges.resource
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/ges/">
|
||||||
|
<file>python/gesotioformatter.py</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
|
@ -157,7 +157,16 @@ parser = custom_target('gesparselex',
|
||||||
command : [flex, '-Ppriv_ges_parse_yy', '--header-file=@OUTPUT1@', '-o', '@OUTPUT0@', '@INPUT@']
|
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,
|
version : libversion,
|
||||||
soversion : soversion,
|
soversion : soversion,
|
||||||
darwin_versions : osxversion,
|
darwin_versions : osxversion,
|
||||||
|
|
107
ges/python/gesotioformatter.py
Normal file
107
ges/python/gesotioformatter.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
#!/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
|
||||||
|
|
||||||
|
try:
|
||||||
|
import opentimelineio as otio
|
||||||
|
otio.adapters.from_name('xges')
|
||||||
|
except Exception as e:
|
||||||
|
Gst.info("Could not load OpenTimelineIO: %s" % e)
|
||||||
|
otio = None
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if otio is not None:
|
||||||
|
GObject.type_register(GESOtioFormatter)
|
||||||
|
known_extensions_mimetype_map = [
|
||||||
|
("otio", "xml", "fcpxml"),
|
||||||
|
("application/otio", "application/xmeml", "application/fcpxml")
|
||||||
|
]
|
||||||
|
|
||||||
|
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)
|
69
meson.build
69
meson.build
|
@ -110,9 +110,6 @@ if gstvalidate_dep.found()
|
||||||
cdata.set('HAVE_GST_VALIDATE', 1)
|
cdata.set('HAVE_GST_VALIDATE', 1)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
configure_file(output : 'config.h', configuration : cdata)
|
|
||||||
|
|
||||||
|
|
||||||
gir = find_program('g-ir-scanner', required : get_option('introspection'))
|
gir = find_program('g-ir-scanner', required : get_option('introspection'))
|
||||||
gnome = import('gnome')
|
gnome = import('gnome')
|
||||||
|
|
||||||
|
@ -128,6 +125,72 @@ gir_init_section = [ '--add-init-section=' + \
|
||||||
'gst_init(NULL,NULL);' + \
|
'gst_init(NULL,NULL);' + \
|
||||||
'ges_init();', '--quiet']
|
'ges_init();', '--quiet']
|
||||||
|
|
||||||
|
has_python = false
|
||||||
|
if build_gir
|
||||||
|
pymod = import('python')
|
||||||
|
python = pymod.find_installation(required: get_option('python'))
|
||||||
|
python_dep = python.dependency(required : get_option('python'))
|
||||||
|
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'
|
||||||
|
pylib_ldlibrary = python.get_variable('LDLIBRARY', '')
|
||||||
|
if host_machine.system() == 'darwin'
|
||||||
|
# OSX is a pain. Python as shipped by apple installs libpython in /usr/lib
|
||||||
|
# so we hardcode that. Other systems can use -Dlibpythondir to
|
||||||
|
# override this.
|
||||||
|
pylib_loc = '/usr/lib'
|
||||||
|
else
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
configure_file(output : 'config.h', configuration : cdata)
|
||||||
|
|
||||||
ges_c_args = ['-DHAVE_CONFIG_H', '-DG_LOG_DOMAIN="GES"']
|
ges_c_args = ['-DHAVE_CONFIG_H', '-DG_LOG_DOMAIN="GES"']
|
||||||
plugins_install_dir = '@0@/gstreamer-1.0'.format(get_option('libdir'))
|
plugins_install_dir = '@0@/gstreamer-1.0'.format(get_option('libdir'))
|
||||||
|
|
||||||
|
|
|
@ -8,3 +8,7 @@ option('xptv', type : 'feature', value : 'auto',
|
||||||
description : 'Build the deprecated xptv formater')
|
description : 'Build the deprecated xptv formater')
|
||||||
option('doc', type : 'feature', value : 'auto', yield: true,
|
option('doc', type : 'feature', value : 'auto', yield: true,
|
||||||
description: 'Enable documentation.')
|
description: 'Enable documentation.')
|
||||||
|
option('python', type : 'feature', value : 'auto', yield: true,
|
||||||
|
description: 'Enable python formatters.')
|
||||||
|
option('libpython-dir', type : 'string', value : '',
|
||||||
|
description: 'Path to find libpythonXX.so')
|
||||||
|
|
Loading…
Reference in a new issue