Implement a formatter based on [OpenTimelineIO]

[OpenTimelineIO]: http://opentimeline.io/
This commit is contained in:
Thibault Saunier 2019-02-05 15:46:49 -03:00
parent 451f67e3d6
commit c5c451fc1c
6 changed files with 303 additions and 8 deletions

View file

@ -36,6 +36,14 @@
#include "ges-formatter.h"
#include "ges-internal.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 */
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_FORMAT_VERSION, NULL);
g_clear_pointer (&fclass->name, g_free);
g_clear_pointer (&fclass->description, g_free);
g_clear_pointer (&fclass->extension, g_free);
g_clear_pointer (&fclass->mimetype, g_free);
/* We are leaking the metadata but we don't really have choice here
* as calling ges_init() after deinit() is allowed.
*/
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
_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);
}
load_python_formatters ();
formatters = g_type_children (GES_TYPE_FORMATTER, &n_formatters);
_list_formatters (formatters, n_formatters);

6
ges/ges.resource Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/ges/">
<file>python/gesotioformatter.py</file>
</gresource>
</gresources>

View file

@ -157,7 +157,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,

View 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)

View file

@ -110,9 +110,6 @@ if gstvalidate_dep.found()
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')
@ -128,6 +125,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'))
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"']
plugins_install_dir = '@0@/gstreamer-1.0'.format(get_option('libdir'))

View file

@ -8,3 +8,7 @@ option('xptv', type : 'feature', value : 'auto',
description : 'Build the deprecated xptv formater')
option('doc', type : 'feature', value : 'auto', yield: true,
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')