Reimplement gstpython plugin on top of PyGobject
authorThibault Saunier <thibault.saunier@collabora.com>
Thu, 6 Feb 2014 15:17:03 +0000 (16:17 +0100)
committerThibault Saunier <tsaunier@gnome.org>
Fri, 6 Jun 2014 08:28:58 +0000 (10:28 +0200)
Makefile.am
acinclude.m4
configure.ac
plugin/Makefile.am [new file with mode: 0644]
plugin/gstpythonplugin.c [new file with mode: 0644]

index a71f705..e444968 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = common gi
+SUBDIRS = common gi plugin
 
 # Examples and testsuite need to be ported to 1.0
 #examples testsuite
index 4f93951..c0d1a5e 100644 (file)
@@ -47,6 +47,11 @@ py_prefix=`$PYTHON -c "import sys; print(sys.prefix)"`
 py_exec_prefix=`$PYTHON -c "import sys; print(sys.exec_prefix)"`
 if $PYTHON-config --help 1>/dev/null 2>/dev/null; then
   PYTHON_INCLUDES=`$PYTHON-config --includes 2>/dev/null`
+  if $PYTHON-config --abiflags 1>/dev/null 2>/dev/null; then
+    PYTHON_ABI_FLAGS=`$PYTHON-config --abiflags 2>/dev/null`
+  else
+    PYTHON_ABI_FLAGS=
+  fi
 else
   PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
   if test "$py_prefix" != "$py_exec_prefix"; then
@@ -86,8 +91,12 @@ if $PYTHON-config --help 1>/dev/null 2>/dev/null; then
 
     # default to prefix/lib for distros that don't have a link in
     # .../pythonX.Y/config/
-    if test ! -e $PYTHON_LIB_LOC/libpython${PYTHON_VERSION}.so; then
-      PYTHON_LIB_LOC=${py_prefix}/lib
+    if test ! -e $PYTHON_LIB_LOC/libpython${PYTHON_VERSION}${PYTHON_ABI_FLAGS}.so; then
+      if test -e ${py_prefix}/lib64/libpython${PYTHON_VERSION}${PYTHON_ABI_FLAGS}.so; then
+        PYTHON_LIB_LOC=${py_prefix}/lib64
+      else
+        PYTHON_LIB_LOC=${py_prefix}/lib
+      fi
     fi
   fi
 else
@@ -111,6 +120,7 @@ fi
 
 AC_SUBST(PYTHON_LIBS)
 AC_SUBST(PYTHON_LIB_LOC)
+AC_SUBST(PYTHON_ABI_FLAGS)
 AC_SUBST(PYTHON_LIB_SUFFIX)
 dnl check if the headers exist:
 save_LIBS="$LIBS"
index 924c834..ef1cb0c 100644 (file)
@@ -64,7 +64,21 @@ if sys.version_info < minver:
   sys.exit(1)
 sys.exit(0)"
 
+dnl check for GStreamer
+GST_API_VERSION=1.0
+AC_SUBST(GST_API_VERSION)
+PKG_CHECK_MODULES(GST, gstreamer-$GST_API_VERSION >= $GST_REQ)
+AC_DEFINE_UNQUOTED(GST_API_VERSION, "$GST_API_VERSION", [Gst API version])
+GST_CFLAGS="$GST_CFLAGS $GLIB_EXTRA_CFLAGS"
+AC_SUBST(GST_CFLAGS)
+AC_SUBST(GST_LIBS)
 
+
+dnl check for pygobject
+PKG_CHECK_MODULES(PYGOBJECT, pygobject-3.0 >= $PYGOBJECT_REQ)
+AC_SUBST(PYGOBJECT_CFLAGS)
+
+dnl check for python
 if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC
 then
   AC_MSG_RESULT(okay)
@@ -109,6 +123,8 @@ GST_CFLAGS="$GST_CFLAGS $GLIB_EXTRA_CFLAGS"
 AC_SUBST(GST_CFLAGS)
 AC_SUBST(GST_LIBS)
 
+AG_GST_SET_PLUGINDIR
+
 dnl check for pygobject
 PKG_CHECK_MODULES(PYGOBJECT, pygobject-3.0 >= $PYGOBJECT_REQ)
 AC_SUBST(PYGOBJECT_CFLAGS)
@@ -117,6 +133,8 @@ dnl and set the override directory
 AC_ARG_WITH([pygi_overrides_dir],
             AC_HELP_STRING([--with-pygi-overrides-dir], [Path to pygobject overrides directory]))
 
+AM_CHECK_PYTHON_LIBS(, AC_MSG_ERROR([Python libs not found. Windows requires Python modules to be explicitly linked to libpython.]))
+
 AG_GST_VALGRIND_CHECK
 
 dnl set release date/time
@@ -133,5 +151,6 @@ AC_OUTPUT([
   common/Makefile
   common/m4/Makefile
   gi/Makefile
+  plugin/Makefile
   gi/overrides/Makefile
 ])
diff --git a/plugin/Makefile.am b/plugin/Makefile.am
new file mode 100644 (file)
index 0000000..234ea4e
--- /dev/null
@@ -0,0 +1,13 @@
+plugin_LTLIBRARIES = libgstpythonplugin.la
+
+INCLUDES = $(PYGOBJECT_CFLAGS) $(GST_CFLAGS)\
+-DPYTHON_VERSION=\"$(PYTHON_VERSION)\"         \
+       -DPY_LIB_LOC="\"$(PYTHON_LIB_LOC)\""            \
+       -DPY_ABI_FLAGS="\"$(PYTHON_ABI_FLAGS)\""                \
+       -DPY_LIB_SUFFIX=$(PYTHON_LIB_SUFFIX) \
+       $(PYTHON_INCLUDES)
+
+libgstpythonplugin_la_SOURCES = gstpythonplugin.c 
+libgstpythonplugin_la_LDFLAGS = 
+libgstpythonplugin_la_LIBADD =  $(PYTHON_LIBS) $(PYGOBJECT_LIBS) $(GST_LIBS)
+libgstpythonplugin_la_CFLAGS = $(GST_CFLAGS) $(PYGOBJECT_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(PYTHON_INCLUDES)
diff --git a/plugin/gstpythonplugin.c b/plugin/gstpythonplugin.c
new file mode 100644 (file)
index 0000000..17c857b
--- /dev/null
@@ -0,0 +1,304 @@
+/* gst-python
+ * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com>
+ *               2005 Benjamin Otte <otte@gnome.org>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* include this first, before NO_IMPORT_PYGOBJECT is defined */
+#include <pygobject.h>
+#include <gst/gst.h>
+#include <gmodule.h>
+#include <Python.h>
+
+void *_PyGstElement_Type;
+
+GST_DEBUG_CATEGORY_STATIC (pyplugindebug);
+#define GST_CAT_DEFAULT pyplugindebug
+
+#define GST_ORIGIN "http://gstreamer.freedesktop.org"
+
+static PyObject *element;
+
+static gboolean
+gst_python_plugin_load_file (GstPlugin * plugin, const char *name)
+{
+  PyObject *main_module, *main_locals;
+  PyObject *elementfactory;
+  PyObject *module;
+  const gchar *facname;
+  guint rank;
+  PyObject *class;
+
+  GST_DEBUG ("loading plugin %s", name);
+
+  main_module = PyImport_AddModule ("__main__");
+  if (main_module == NULL) {
+    GST_WARNING ("Could not get __main__, ignoring plugin %s", name);
+    PyErr_Print ();
+    PyErr_Clear ();
+    return FALSE;
+  }
+
+  main_locals = PyModule_GetDict (main_module);
+  module =
+      PyImport_ImportModuleEx ((char *) name, main_locals, main_locals, NULL);
+  if (!module) {
+    GST_DEBUG ("Could not load module, ignoring plugin %s", name);
+    PyErr_Print ();
+    PyErr_Clear ();
+    return FALSE;
+  }
+
+  /* Get __gstelementfactory__ from file */
+  elementfactory = PyObject_GetAttrString (module, "__gstelementfactory__");
+  if (!elementfactory) {
+    GST_DEBUG ("python file doesn't contain __gstelementfactory__");
+    PyErr_Clear ();
+    return FALSE;
+  }
+
+  /* parse tuple : name, rank, gst.ElementClass */
+  if (!PyArg_ParseTuple (elementfactory, "sIO", &facname, &rank, &class)) {
+    GST_WARNING ("__gstelementfactory__ isn't correctly formatted");
+    PyErr_Print ();
+    PyErr_Clear ();
+    Py_DECREF (elementfactory);
+    return FALSE;
+  }
+
+  if (!PyObject_IsSubclass (class, (PyObject *) & PyGObject_Type)) {
+    GST_WARNING ("the class provided isn't a subclass of GObject.Object");
+    PyErr_Print ();
+    PyErr_Clear ();
+    Py_DECREF (elementfactory);
+    Py_DECREF (class);
+    return FALSE;
+  }
+
+  if (!g_type_is_a (pyg_type_from_object (class), GST_TYPE_ELEMENT)) {
+    GST_WARNING ("the class provided isn't a subclass of Gst.Element");
+    PyErr_Print ();
+    PyErr_Clear ();
+    Py_DECREF (elementfactory);
+    Py_DECREF (class);
+    return FALSE;
+  }
+
+  GST_INFO ("Valid plugin");
+  Py_DECREF (elementfactory);
+
+  return gst_element_register (plugin, facname, rank,
+      pyg_type_from_object (class));
+}
+
+static gboolean
+gst_python_load_directory (GstPlugin * plugin, gchar * path)
+{
+  GDir *dir;
+  const gchar *file;
+  GError *error = NULL;
+  gboolean ret = TRUE;
+
+  dir = g_dir_open (path, 0, &error);
+  if (!dir) {
+    /*retval should probably be depending on error, but since we ignore it... */
+    GST_DEBUG ("Couldn't open Python plugin dir: %s", error->message);
+    g_error_free (error);
+    return FALSE;
+  }
+  while ((file = g_dir_read_name (dir))) {
+    /* FIXME : go down in subdirectories */
+    if (g_str_has_suffix (file, ".py")) {
+      gsize len = strlen (file) - 3;
+      gchar *name = g_strndup (file, len);
+      ret &= gst_python_plugin_load_file (plugin, name);
+      g_free (name);
+    }
+  }
+  return TRUE;
+}
+
+static gboolean
+gst_python_plugin_load (GstPlugin * plugin)
+{
+  PyObject *sys_path;
+  const gchar *plugin_path;
+  gboolean ret = TRUE;
+
+  sys_path = PySys_GetObject ("path");
+
+  /* Mimic the order in which the registry is checked in core */
+
+  /* 1. check env_variable GST_PLUGIN_PATH */
+  plugin_path = g_getenv ("GST_PLUGIN_PATH");
+  if (plugin_path) {
+    char **list;
+    int i;
+
+    GST_DEBUG ("GST_PLUGIN_PATH set to %s", plugin_path);
+    list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0);
+    for (i = 0; list[i]; i++) {
+      gchar *sysdir = g_build_filename (list[i], "python", NULL);
+      PyList_Insert (sys_path, 0, PyUnicode_FromString (sysdir));
+      gst_python_load_directory (plugin, sysdir);
+      g_free (sysdir);
+    }
+
+    g_strfreev (list);
+  }
+
+  /* 2. Check for GST_PLUGIN_SYSTEM_PATH */
+  plugin_path = g_getenv ("GST_PLUGIN_SYSTEM_PATH");
+  if (plugin_path == NULL) {
+    char *home_plugins;
+
+    /* 2.a. Scan user and system-wide plugin directory */
+    GST_DEBUG ("GST_PLUGIN_SYSTEM_PATH not set");
+
+    /* plugins in the user's home directory take precedence over
+     * system-installed ones */
+    home_plugins = g_build_filename (g_get_home_dir (),
+        ".gstreamer-" GST_API_VERSION, "plugins", "python", NULL);
+    PyList_Insert (sys_path, 0, PyUnicode_FromString (home_plugins));
+    gst_python_load_directory (plugin, home_plugins);
+    g_free (home_plugins);
+
+    /* add the main (installed) library path */
+    PyList_Insert (sys_path, 0, PyUnicode_FromString (PLUGINDIR "/python"));
+    gst_python_load_directory (plugin, PLUGINDIR "/python");
+  } else {
+    gchar **list;
+    gint i;
+
+    /* 2.b. Scan GST_PLUGIN_SYSTEM_PATH */
+    GST_DEBUG ("GST_PLUGIN_SYSTEM_PATH set to %s", plugin_path, plugin_path);
+    list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0);
+    for (i = 0; list[i]; i++) {
+      gchar *sysdir;
+
+      sysdir = g_build_filename (list[i], "python", NULL);
+
+      PyList_Insert (sys_path, 0, PyUnicode_FromString (sysdir));
+      gst_python_load_directory (plugin, sysdir);
+      g_free (sysdir);
+    }
+    g_strfreev (list);
+  }
+
+
+  return ret;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  PyGILState_STATE state;
+  PyObject *gst, *dict, *pyplugin;
+  gboolean we_initialized = FALSE;
+  GModule *libpython;
+  gpointer has_python = NULL;
+  PyObject *seq, *list;
+  int i, len;
+
+  GST_DEBUG_CATEGORY_INIT (pyplugindebug, "pyplugin", 0,
+      "Python plugin loader");
+
+  gst_plugin_add_dependency_simple (plugin,
+      "HOME/.gstreamer-" GST_API_VERSION
+      "/plugins/python:GST_PLUGIN_SYSTEM_PATH/python:GST_PLUGIN_PATH/python",
+      PLUGINDIR "/python:HOME/.gstreamer-" GST_API_VERSION "/plugins/python:"
+      "GST_PLUGIN_SYSTEM_PATH/python:GST_PLUGIN_PATH/python", NULL,
+      GST_PLUGIN_DEPENDENCY_FLAG_NONE);
+
+  GST_LOG ("Checking to see if libpython is already loaded");
+  g_module_symbol (g_module_open (NULL, G_MODULE_BIND_LOCAL), "_Py_NoneStruct",
+      &has_python);
+  if (has_python) {
+    GST_LOG ("libpython is already loaded");
+  } else {
+    GST_LOG ("loading libpython");
+    libpython =
+        g_module_open (PY_LIB_LOC "/libpython" PYTHON_VERSION PY_ABI_FLAGS
+        "." PY_LIB_SUFFIX, 0);
+    if (!libpython) {
+      GST_WARNING ("Couldn't g_module_open libpython. Reason: %s",
+          g_module_error ());
+      return FALSE;
+    }
+  }
+
+  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 ();
+  }
+
+  GST_LOG ("initializing pygobject");
+  if (!pygobject_init (3, 0, 0)) {
+    GST_WARNING ("pygobject initialization failed");
+    return FALSE;
+  }
+
+  gst = PyImport_ImportModule ("gi.repository.Gst");
+  if (we_initialized) {
+    PyObject *tmp;
+
+    dict = PyModule_GetDict (gst);
+    if (!dict) {
+      GST_ERROR ("no dict?!");
+    }
+
+
+    tmp =
+        PyObject_GetAttr (PyMapping_GetItemString (dict,
+            "_introspection_module"), PyUnicode_FromString ("__dict__"));
+
+    _PyGstElement_Type = PyMapping_GetItemString (tmp, "Element");
+
+    if (!_PyGstElement_Type) {
+      g_error ("Could not get Gst.Element");
+      Py_DECREF (pyplugin);
+    }
+    pyplugin = pygobject_new (G_OBJECT (plugin));
+    if (!pyplugin || PyModule_AddObject (gst, "__plugin__", pyplugin) != 0) {
+      g_warning ("Couldn't set plugin");
+      Py_DECREF (pyplugin);
+    }
+  }
+
+  gst_python_plugin_load (plugin);
+
+  if (we_initialized) {
+    /* We need to release the GIL since we're going back to C land */
+    PyEval_SaveThread ();
+  } else
+    PyGILState_Release (state);
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR, python,
+    "loader for plugins written in python",
+    plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_ORIGIN)