mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 02:01:12 +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-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
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@']
|
||||
)
|
||||
|
||||
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,
|
||||
|
|
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)
|
||||
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'))
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue