From 886ac8eeb34c319481c1695aa69707ab6f6ee7e8 Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.dos.anjos@gmail.com>
Date: Wed, 29 Jan 2014 14:28:27 +0100
Subject: [PATCH] Python3 C-API support

---
 doc/c_cpp_api.rst             |  19 ++++++
 setup.py                      |   1 +
 xbob/io/file.cpp              |  54 +++++++++++++---
 xbob/io/hdf5.cpp              |  97 ++++++++++++++++++++++-------
 xbob/io/include/xbob.io/api.h | 113 +++++++++++++++++++---------------
 xbob/io/main.cpp              | 111 ++++++++++++++-------------------
 xbob/io/videoreader.cpp       |  20 ++++--
 xbob/io/videowriter.cpp       |  21 +++++--
 8 files changed, 283 insertions(+), 153 deletions(-)

diff --git a/doc/c_cpp_api.rst b/doc/c_cpp_api.rst
index cf90f56..2333977 100644
--- a/doc/c_cpp_api.rst
+++ b/doc/c_cpp_api.rst
@@ -69,6 +69,25 @@ Generic Functions
    described above in case of success.
 
 
+.. cpp:function:: int PyBobIo_FilenameConverter (PyObject* o, PyObject** b)
+
+   This function is meant to be used with :c:func:`PyArg_ParseTupleAndKeywords`
+   family of functions in the Python C-API. It converts an arbitrary input
+   object into a ``PyStringObject`` (in Python2.x) and into a ``PyBytesObject``
+   (in Python3.x). If the input object is of type ``PyUnicodeObject``, which is
+   the default in Python3.x, the unicode code is properly decoded using
+   :c:func:`PyUnicode_AsEncodedString` with ``encoding`` set to
+   ``Py_FileSystemDefaultEncoding`` and ``errors`` set to ``"strict"``. On
+   versions of Python >= 3.2, this is just an alias for
+   :c:func:`PyUnicode_FSConverter`, which does a similar job.
+
+   Objects which are not ``PyUnicodeObject`` are coerced into a bytes/string
+   object using :c:func:`PyObject_Bytes` (on Python3.x) and
+   :c:func:`PyObject_Str` (on Python 2.x).
+
+   Returns 0 if an error is detected, 1 on success.
+
+
 Bob File Support
 ----------------
 
diff --git a/setup.py b/setup.py
index e20f5b3..0e4947d 100644
--- a/setup.py
+++ b/setup.py
@@ -33,6 +33,7 @@ setup(
     install_requires=[
       'setuptools',
       'xbob.blitz',
+      'pillow',
     ],
 
     namespace_packages=[
diff --git a/xbob/io/file.cpp b/xbob/io/file.cpp
index d7c4fe4..e4b3cc6 100644
--- a/xbob/io/file.cpp
+++ b/xbob/io/file.cpp
@@ -12,6 +12,7 @@
 #include <bob/io/utils.h>
 #include <numpy/arrayobject.h>
 #include <xbob.blitz/capi.h>
+#include <xbob.blitz/cleanup.h>
 #include <stdexcept>
 
 #define FILETYPE_NAME "File"
@@ -62,6 +63,25 @@ static void PyBobIoFile_Delete (PyBobIoFileObject* o) {
 
 }
 
+int PyBobIo_FilenameConverter (PyObject* o, PyObject** b) {
+#if PY_VERSION_HEX >= 0x03020000
+  if (!PyUnicode_FSConverter(o, b)) return 0;
+#else
+  if (PyUnicode_Check(o)) {
+    *b = PyUnicode_AsEncodedString(o, Py_FileSystemDefaultEncoding, "strict");
+  }
+  else {
+#if PY_VERSION_HEX >= 0x03000000
+    *b = PyObject_Bytes(o);
+#else
+    *b = PyObject_Str(o);
+#endif
+  }
+  if (!b) return 0;
+#endif
+  return 1;
+}
+
 /* The __init__(self) method */
 static int PyBobIoFile_Init(PyBobIoFileObject* self, PyObject *args, PyObject* kwds) {
 
@@ -69,23 +89,41 @@ static int PyBobIoFile_Init(PyBobIoFileObject* self, PyObject *args, PyObject* k
   static const char* const_kwlist[] = {"filename", "mode", "pretend_extension", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  char* filename = 0;
-  char mode = 'r';
+  PyObject* filename = 0;
   char* pretend_extension = 0;
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|cs", kwlist, &filename,
-        &mode, &pretend_extension)) return -1;
+
+#if PY_VERSION_HEX >= 0x03000000
+#  define MODE_CHAR "C"
+  int mode = 'r';
+#else
+#  define MODE_CHAR "c"
+  char mode = 'r';
+#endif
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|" MODE_CHAR "s", kwlist,
+        &PyBobIo_FilenameConverter, &filename, &mode, &pretend_extension)) return -1;
+
+#undef MODE_CHAR
+
+  auto filename_ = make_safe(filename);
 
   if (mode != 'r' && mode != 'w' && mode != 'a') {
     PyErr_Format(PyExc_ValueError, "file open mode string should have 1 element and be either 'r' (read), 'w' (write) or 'a' (append)");
     return -1;
   }
 
+#if PY_VERSION_HEX >= 0x03000000
+  const char* c_filename = PyBytes_AS_STRING(filename);
+#else
+  const char* c_filename = PyString_AS_STRING(filename);
+#endif
+
   try {
     if (pretend_extension) {
-      self->f = bob::io::open(filename, mode, pretend_extension);
+      self->f = bob::io::open(c_filename, mode, pretend_extension);
     }
     else {
-      self->f = bob::io::open(filename, mode);
+      self->f = bob::io::open(c_filename, mode);
     }
   }
   catch (std::exception& e) {
@@ -93,7 +131,7 @@ static int PyBobIoFile_Init(PyBobIoFileObject* self, PyObject *args, PyObject* k
     return -1;
   }
   catch (...) {
-    PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%c': unknown exception caught", filename, mode);
+    PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%c': unknown exception caught", c_filename, mode);
     return -1;
   }
 
@@ -107,7 +145,7 @@ static PyObject* PyBobIoFile_Repr(PyBobIoFileObject* self) {
 # else
   PyString_FromFormat
 # endif
-  ("%s(filename='%s', codec='%s')", Py_TYPE(self)->tp_name, 
+  ("%s(filename='%s', codec='%s')", Py_TYPE(self)->tp_name,
    self->f->filename().c_str(), self->f->name().c_str());
 }
 
diff --git a/xbob/io/hdf5.cpp b/xbob/io/hdf5.cpp
index 17cf508..68ff783 100644
--- a/xbob/io/hdf5.cpp
+++ b/xbob/io/hdf5.cpp
@@ -11,7 +11,9 @@
 #include <boost/make_shared.hpp>
 #include <numpy/arrayobject.h>
 #include <xbob.blitz/cppapi.h>
+#include <xbob.blitz/cleanup.h>
 #include <stdexcept>
+#include <cstring>
 #include "bobskin.h"
 
 #define HDF5FILE_NAME "HDF5File"
@@ -82,25 +84,44 @@ static int PyBobIoHDF5File_Init(PyBobIoHDF5FileObject* self,
   static const char* const_kwlist[] = {"filename", "mode", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  const char* filename = 0;
+  PyObject* filename = 0;
+
+#if PY_VERSION_HEX >= 0x03000000
+#  define MODE_CHAR "C"
+  int mode = 'r';
+#else
+#  define MODE_CHAR "c"
   char mode = 'r';
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|c", kwlist, &filename, &mode))
+#endif
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|" MODE_CHAR, kwlist,
+        &PyBobIo_FilenameConverter, &filename, &mode))
     return -1;
 
+#undef MODE_CHAR
+
+  auto filename_ = make_safe(filename);
+
   if (mode != 'r' && mode != 'w' && mode != 'a' && mode != 'x') {
     PyErr_Format(PyExc_ValueError, "file open mode string should have 1 element and be either 'r' (read), 'w' (write), 'a' (append), 'x' (exclusive)");
     return -1;
   }
 
+#if PY_VERSION_HEX >= 0x03000000
+  const char* c_filename = PyBytes_AS_STRING(filename);
+#else
+  const char* c_filename = PyString_AS_STRING(filename);
+#endif
+
   try {
-    self->f.reset(new bob::io::HDF5File(filename, mode));
+    self->f.reset(new bob::io::HDF5File(c_filename, mode));
   }
   catch (std::exception& e) {
     PyErr_SetString(PyExc_RuntimeError, e.what());
     return -1;
   }
   catch (...) {
-    PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%c': unknown exception caught", filename, mode);
+    PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%c': unknown exception caught", c_filename, mode);
     return -1;
   }
 
@@ -846,18 +867,48 @@ value >= 0, or a list of arrays otherwise.\n\
  * simple scalar.
  */
 
-static char* PyBobIo_GetString(PyObject* o) {
+static void null_char_array_deleter(char*) {}
+static void char_array_deleter(char* o) { delete[] o; }
+
+static std::shared_ptr<char> PyBobIo_GetString(PyObject* o) {
+
 #if PY_VERSION_HEX < 0x03000000
-  return PyString_AsString(o);
+
+  return std::shared_ptr<char>(PyString_AsString(o), null_char_array_deleter);
+
 #else
-  return PyBytes_AsString(o);
+
+  if (PyBytes_Check(o)) {
+    //fast way out
+    return std::shared_ptr<char>(PyBytes_AsString(o), null_char_array_deleter);
+  }
+
+  PyObject* bytes = 0;
+
+  if (PyUnicode_Check(o)) {
+    //re-encode using utf-8
+    bytes = PyUnicode_AsEncodedString(o, "utf-8", "strict");
+  }
+  else {
+    //tries coercion
+    bytes = PyObject_Bytes(o);
+  }
+  auto bytes_ = make_safe(bytes); ///< protects acquired resource
+
+  Py_ssize_t length = PyBytes_GET_SIZE(bytes)+1;
+  char* copy = new char[length];
+  std::strncpy(copy, PyBytes_AsString(bytes), length);
+
+  return std::shared_ptr<char>(copy, char_array_deleter);
+
 #endif
+
 }
 
 static int PyBobIoHDF5File_SetStringType(bob::io::HDF5Type& t, PyObject* o) {
-  const char* value = PyBobIo_GetString(o);
+  auto value = PyBobIo_GetString(o);
   if (!value) return -1;
-  t = bob::io::HDF5Type(value);
+  t = bob::io::HDF5Type(value.get());
   return 0;
 }
 
@@ -1074,9 +1125,9 @@ static PyObject* PyBobIoHDF5File_Replace(PyBobIoHDF5FileObject* self, PyObject*
       switch(type.type()) {
         case bob::io::s:
           {
-            const char* value = PyBobIo_GetString(data);
+            auto value = PyBobIo_GetString(data);
             if (!value) return 0;
-            self->f->replace<std::string>(path, pos, value);
+            self->f->replace<std::string>(path, pos, value.get());
             Py_RETURN_NONE;
           }
         case bob::io::b:
@@ -1208,9 +1259,9 @@ static int PyBobIoHDF5File_InnerAppend(PyBobIoHDF5FileObject* self, const char*
       switch(type.type()) {
         case bob::io::s:
           {
-            const char* value = PyBobIo_GetString(data);
+            auto value = PyBobIo_GetString(data);
             if (!value) return 0;
-            self->f->append<std::string>(path, value);
+            self->f->append<std::string>(path, value.get());
             return 1;
           }
         case bob::io::b:
@@ -1400,9 +1451,9 @@ static PyObject* PyBobIoHDF5File_Set(PyBobIoHDF5FileObject* self, PyObject* args
       switch(type.type()) {
         case bob::io::s:
           {
-            const char* value = PyBobIo_GetString(data);
+            auto value = PyBobIo_GetString(data);
             if (!value) return 0;
-            self->f->set<std::string>(path, value);
+            self->f->set<std::string>(path, value.get());
             Py_RETURN_NONE;
           }
           break;
@@ -1804,11 +1855,11 @@ template <> PyObject* PyBobIoHDF5File_WriteScalarAttribute<const char*>
 (PyBobIoHDF5FileObject* self, const char* path, const char* name,
  const bob::io::HDF5Type& type, PyObject* o) {
 
-  const char* value = PyBobIo_GetString(o);
+  auto value = PyBobIo_GetString(o);
   if (!value) return 0;
 
   try {
-    self->f->write_attribute(path, name, type, static_cast<const void*>(value));
+    self->f->write_attribute(path, name, type, static_cast<const void*>(value.get()));
   }
   catch (std::exception& e) {
     PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -1988,16 +2039,16 @@ static PyObject* PyBobIoHDF5File_SetAttributes(PyBobIoHDF5FileObject* self, PyOb
     bob::io::HDF5Type type;
     PyObject* converted = 0;
 
-    const char* name = PyBobIo_GetString(key);
+    auto name = PyBobIo_GetString(key);
     if (!name) return 0;
 
     int is_array = PyBobIoHDF5File_GetObjectType(value, type, &converted);
     if (is_array < 0) { ///< error condition, signal
-      PyErr_Format(PyExc_TypeError, "error setting attribute `%s' of resource `%s' at HDF5 file `%s': no support for storing objects of type `%s' on HDF5 files", name, path, self->f->filename().c_str(), Py_TYPE(value)->tp_name);
+      PyErr_Format(PyExc_TypeError, "error setting attribute `%s' of resource `%s' at HDF5 file `%s': no support for storing objects of type `%s' on HDF5 files", name.get(), path, self->f->filename().c_str(), Py_TYPE(value)->tp_name);
       return 0;
     }
 
-    PyObject* retval = PyBobIoHDF5File_WriteAttribute(self, path, name, type, value, is_array, converted);
+    PyObject* retval = PyBobIoHDF5File_WriteAttribute(self, path, name.get(), type, value, is_array, converted);
     if (!retval) return 0;
     Py_DECREF(retval);
 
@@ -2106,14 +2157,14 @@ static PyObject* PyBobIoHDF5File_DelAttributes(PyBobIoHDF5FileObject* self, PyOb
     PyObject* iter = PyObject_GetIter(attrs);
     if (!iter) return 0;
     while (PyObject* item = PyIter_Next(iter)) {
-      const char* name = PyBobIo_GetString(item);
+      auto name = PyBobIo_GetString(item);
       Py_DECREF(item);
       if (!name) {
         Py_DECREF(iter);
         return 0;
       }
       try {
-        self->f->deleteAttribute(path, name);
+        self->f->deleteAttribute(path, name.get());
       }
       catch (std::exception& e) {
         PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -2121,7 +2172,7 @@ static PyObject* PyBobIoHDF5File_DelAttributes(PyBobIoHDF5FileObject* self, PyOb
         return 0;
       }
       catch (...) {
-        PyErr_Format(PyExc_RuntimeError, "cannot delete attribute `%s' at resource `%s' of HDF5 file `%s': unknown exception caught", name, path, self->f->filename().c_str());
+        PyErr_Format(PyExc_RuntimeError, "cannot delete attribute `%s' at resource `%s' of HDF5 file `%s': unknown exception caught", name.get(), path, self->f->filename().c_str());
         Py_DECREF(iter);
         return 0;
       }
diff --git a/xbob/io/include/xbob.io/api.h b/xbob/io/include/xbob.io/api.h
index a53cc22..b605378 100644
--- a/xbob/io/include/xbob.io/api.h
+++ b/xbob/io/include/xbob.io/api.h
@@ -78,6 +78,33 @@ typedef struct {
 #define PyBobIo_TypeInfoAsTuple_RET PyObject*
 #define PyBobIo_TypeInfoAsTuple_PROTO (const bob::core::array::typeinfo& ti)
 
+#define PyBobIo_FilenameConverter_NUM 5
+#define PyBobIo_FilenameConverter_RET int
+#define PyBobIo_FilenameConverter_PROTO (PyObject* o, PyObject** b)
+
+/*****************
+ * HDF5 bindings *
+ *****************/
+
+typedef struct {
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  boost::shared_ptr<bob::io::HDF5File> f;
+
+} PyBobIoHDF5FileObject;
+
+#define PyBobIoHDF5File_Type_NUM 6
+#define PyBobIoHDF5File_Type_TYPE PyTypeObject
+
+#define PyBobIoHDF5File_Check_NUM 7
+#define PyBobIoHDF5File_Check_RET int
+#define PyBobIoHDF5File_Check_PROTO (PyObject* o)
+
+#define PyBobIoHDF5File_Converter_NUM 8
+#define PyBobIoHDF5File_Converter_RET int
+#define PyBobIoHDF5File_Converter_PROTO (PyObject* o, PyBobIoHDF5FileObject** a)
+
 #if WITH_FFMPEG
 
 /******************
@@ -92,7 +119,7 @@ typedef struct {
 
 } PyBobIoVideoReaderObject;
 
-#define PyBobIoVideoReader_Type_NUM 5
+#define PyBobIoVideoReader_Type_NUM 9
 #define PyBobIoVideoReader_Type_TYPE PyTypeObject
 
 typedef struct {
@@ -104,7 +131,7 @@ typedef struct {
 
 } PyBobIoVideoReaderIteratorObject;
 
-#define PyBobIoVideoReaderIterator_Type_NUM 5
+#define PyBobIoVideoReaderIterator_Type_NUM 10
 #define PyBobIoVideoReaderIterator_Type_TYPE PyTypeObject
 
 typedef struct {
@@ -115,39 +142,16 @@ typedef struct {
 
 } PyBobIoVideoWriterObject;
 
-#define PyBobIoVideoWriter_Type_NUM 6
+#define PyBobIoVideoWriter_Type_NUM 11
 #define PyBobIoVideoWriter_Type_TYPE PyTypeObject
 
 #endif /* WITH_FFMPEG */
 
-/*****************
- * HDF5 bindings *
- *****************/
-
-typedef struct {
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  boost::shared_ptr<bob::io::HDF5File> f;
-
-} PyBobIoHDF5FileObject;
-
-#define PyBobIoHDF5File_Type_NUM 7
-#define PyBobIoHDF5File_Type_TYPE PyTypeObject
-
-#define PyBobIoHDF5File_Check_NUM 8
-#define PyBobIoHDF5File_Check_RET int
-#define PyBobIoHDF5File_Check_PROTO (PyObject* o)
-
-#define PyBobIoHDF5File_Converter_NUM 9
-#define PyBobIoHDF5File_Converter_RET int
-#define PyBobIoHDF5File_Converter_PROTO (PyObject* o, PyBobIoHDF5FileObject** a)
-
 /* Total number of C API pointers */
 #if WITH_FFMPEG
-#  define PyXbobIo_API_pointers 10
+#  define PyXbobIo_API_pointers 12
 #else
-#  define PyXbobIo_API_pointers 11
+#  define PyXbobIo_API_pointers 9
 #endif /* WITH_FFMPEG */
 
 #ifdef XBOB_IO_MODULE
@@ -175,6 +179,18 @@ typedef struct {
 
   PyBobIo_TypeInfoAsTuple_RET PyBobIo_TypeInfoAsTuple PyBobIo_TypeInfoAsTuple_PROTO;
 
+  PyBobIo_FilenameConverter_RET PyBobIo_FilenameConverter PyBobIo_FilenameConverter_PROTO;
+
+/*****************
+ * HDF5 bindings *
+ *****************/
+
+  extern PyBobIoHDF5File_Type_TYPE PyBobIoHDF5File_Type;
+
+  PyBobIoHDF5File_Check_RET PyBobIoHDF5File_Check PyBobIoHDF5File_Check_PROTO;
+
+  PyBobIoHDF5File_Converter_RET PyBobIoHDF5File_Converter PyBobIoHDF5File_Converter_PROTO;
+
 #if WITH_FFMPEG
   /******************
    * Video bindings *
@@ -187,16 +203,6 @@ typedef struct {
   extern PyBobIoVideoWriter_Type_TYPE PyBobIoVideoWriter_Type;
 #endif /* WITH_FFMPEG */
 
-/*****************
- * HDF5 bindings *
- *****************/
-
-  extern PyBobIoHDF5File_Type_TYPE PyBobIoHDF5File_Type;
-
-  PyBobIoHDF5File_Check_RET PyBobIoHDF5File_Check PyBobIoHDF5File_Check_PROTO;
-
-  PyBobIoHDF5File_Converter_RET PyBobIoHDF5File_Converter PyBobIoHDF5File_Converter_PROTO;
-
 #else
 
   /* This section is used in modules that use `xbob.io's' C-API */
@@ -244,6 +250,18 @@ typedef struct {
 
 # define PyBobIo_TypeInfoAsTuple (*(PyBobIo_TypeInfoAsTuple_RET (*)PyBobIo_TypeInfoAsTuple_PROTO) PyXbobIo_API[PyBobIo_TypeInfoAsTuple_NUM])
 
+# define PyBobIo_FilenameConverter (*(PyBobIo_FilenameConverter_RET (*)PyBobIo_FilenameConverter_PROTO) PyXbobIo_API[PyBobIo_FilenameConverter_NUM])
+
+  /*****************
+   * HDF5 bindings *
+   *****************/
+
+# define PyBobIoHDF5File_Type (*(PyBobIoHDF5File_Type_TYPE *)PyXbobIo_API[PyBobIoHDF5File_Type_NUM])
+
+# define PyBobIoHDF5File_Check (*(PyBobIoHDF5File_Check_RET (*)PyBobIoHDF5File_Check_PROTO) PyXbobIo_API[PyBobIoHDF5File_Check_NUM])
+
+# define PyBobIoHDF5File_Converter (*(PyBobIoHDF5File_Converter_RET (*)PyBobIoHDF5File_Converter_PROTO) PyXbobIo_API[PyBobIoHDF5File_Converter_NUM])
+
 #if WITH_FFMPEG
   /******************
    * Video bindings *
@@ -256,18 +274,10 @@ typedef struct {
 # define PyBobIoVideoWriterIterator_Type (*(PyBobIoVideoWriterIterator_Type_TYPE *)PyXbobIo_API[PyBobIoVideoWriterIterator_Type_NUM])
 #endif /* WITH_FFMPEG */
 
-  /*****************
-   * HDF5 bindings *
-   *****************/
-
-# define PyBobIoHDF5File_Type (*(PyBobIoHDF5File_Type_TYPE *)PyXbobIo_API[PyBobIoHDF5File_Type_NUM])
-
-# define PyBobIoHDF5File_Check (*(PyBobIoHDF5File_Check_RET (*)PyBobIoHDF5File_Check_PROTO) PyXbobIo_API[PyBobIoHDF5File_Check_NUM])
-
-# define PyBobIoHDF5File_Converter (*(PyBobIoHDF5File_Converter_RET (*)PyBobIoHDF5File_Converter_PROTO) PyXbobIo_API[PyBobIoHDF5File_Converter_NUM])
-
 # if !defined(NO_IMPORT_ARRAY)
 
+#include <xbob.blitz/capi.h>
+
   /**
    * Returns -1 on error, 0 on success.
    */
@@ -320,6 +330,13 @@ typedef struct {
       return -1;
     }
 
+    /* Imports the xbob.blitz C-API */
+    if (import_xbob_blitz() < 0) {
+      PyErr_Print();
+      PyErr_SetString(PyExc_ImportError, "xbob.blitz failed to import");
+      return -1;
+    }
+
     /* If you get to this point, all is good */
     return 0;
 
diff --git a/xbob/io/main.cpp b/xbob/io/main.cpp
index c30def1..36a54b1 100644
--- a/xbob/io/main.cpp
+++ b/xbob/io/main.cpp
@@ -12,6 +12,7 @@
 #undef NO_IMPORT_ARRAY
 #endif
 #include <xbob.blitz/capi.h>
+#include <xbob.blitz/cleanup.h>
 
 static PyMethodDef module_methods[] = {
     {0}  /* Sentinel */
@@ -32,86 +33,61 @@ static PyModuleDef module_definition = {
 };
 #endif
 
-PyMODINIT_FUNC XBOB_EXT_ENTRY_NAME (void) {
+static PyObject* create_module (void) {
 
   PyBobIoFile_Type.tp_new = PyType_GenericNew;
-  if (PyType_Ready(&PyBobIoFile_Type) < 0) return
-# if PY_VERSION_HEX >= 0x03000000
-    0
-# endif
-    ;
+  if (PyType_Ready(&PyBobIoFile_Type) < 0) return 0;
 
   PyBobIoFileIterator_Type.tp_new = PyType_GenericNew;
-  if (PyType_Ready(&PyBobIoFileIterator_Type) < 0) return
-# if PY_VERSION_HEX >= 0x03000000
-    0
-# endif
-    ;
+  if (PyType_Ready(&PyBobIoFileIterator_Type) < 0) return 0;
+
+  PyBobIoHDF5File_Type.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&PyBobIoHDF5File_Type) < 0) return 0;
 
 #if WITH_FFMPEG
   PyBobIoVideoReader_Type.tp_new = PyType_GenericNew;
-  if (PyType_Ready(&PyBobIoVideoReader_Type) < 0) return
-# if PY_VERSION_HEX >= 0x03000000
-    0
-# endif
-    ;
+  if (PyType_Ready(&PyBobIoVideoReader_Type) < 0) return 0;
 
   PyBobIoVideoReaderIterator_Type.tp_new = PyType_GenericNew;
-  if (PyType_Ready(&PyBobIoVideoReaderIterator_Type) < 0) return
-# if PY_VERSION_HEX >= 0x03000000
-    0
-# endif
-    ;
+  if (PyType_Ready(&PyBobIoVideoReaderIterator_Type) < 0) return 0;
 
   PyBobIoVideoWriter_Type.tp_new = PyType_GenericNew;
-  if (PyType_Ready(&PyBobIoVideoWriter_Type) < 0) return
-# if PY_VERSION_HEX >= 0x03000000
-    0
-# endif
-    ;
+  if (PyType_Ready(&PyBobIoVideoWriter_Type) < 0) return 0;
 #endif /* WITH_FFMPEG */
 
-  PyBobIoHDF5File_Type.tp_new = PyType_GenericNew;
-  if (PyType_Ready(&PyBobIoHDF5File_Type) < 0) return
-# if PY_VERSION_HEX >= 0x03000000
-    0
-# endif
-    ;
-
 # if PY_VERSION_HEX >= 0x03000000
   PyObject* m = PyModule_Create(&module_definition);
-  if (!m) return 0;
 # else
-  PyObject* m = Py_InitModule3(XBOB_EXT_MODULE_NAME, 
-      module_methods, module_docstr);
-  if (!m) return;
+  PyObject* m = Py_InitModule3(XBOB_EXT_MODULE_NAME, module_methods, module_docstr);
 # endif
+  if (!m) return 0;
+  auto m_ = make_safe(m);
 
   /* register some constants */
-  PyModule_AddIntConstant(m, "__api_version__", XBOB_IO_API_VERSION);
-  PyModule_AddStringConstant(m, "__version__", XBOB_EXT_MODULE_VERSION);
+  if (PyModule_AddIntConstant(m, "__api_version__", XBOB_IO_API_VERSION) < 0) return 0;
+  if (PyModule_AddStringConstant(m, "__version__", XBOB_EXT_MODULE_VERSION) < 0) return 0;
 
   /* register the types to python */
   Py_INCREF(&PyBobIoFile_Type);
-  PyModule_AddObject(m, "File", (PyObject *)&PyBobIoFile_Type);
+  if (PyModule_AddObject(m, "File", (PyObject *)&PyBobIoFile_Type) < 0) return 0;
 
   Py_INCREF(&PyBobIoFileIterator_Type);
-  PyModule_AddObject(m, "File.iter", (PyObject *)&PyBobIoFileIterator_Type);
+  if (PyModule_AddObject(m, "File.iter", (PyObject *)&PyBobIoFileIterator_Type) < 0) return 0;
+
+  Py_INCREF(&PyBobIoHDF5File_Type);
+  if (PyModule_AddObject(m, "HDF5File", (PyObject *)&PyBobIoHDF5File_Type) < 0) return 0;
 
 #if WITH_FFMPEG
   Py_INCREF(&PyBobIoVideoReader_Type);
-  PyModule_AddObject(m, "VideoReader", (PyObject *)&PyBobIoVideoReader_Type);
+  if (PyModule_AddObject(m, "VideoReader", (PyObject *)&PyBobIoVideoReader_Type) < 0) return 0;
 
   Py_INCREF(&PyBobIoVideoReaderIterator_Type);
-  PyModule_AddObject(m, "VideoReader.iter", (PyObject *)&PyBobIoVideoReaderIterator_Type);
+  if (PyModule_AddObject(m, "VideoReader.iter", (PyObject *)&PyBobIoVideoReaderIterator_Type) < 0) return 0;
 
   Py_INCREF(&PyBobIoVideoWriter_Type);
-  PyModule_AddObject(m, "VideoWriter", (PyObject *)&PyBobIoVideoWriter_Type);
+  if (PyModule_AddObject(m, "VideoWriter", (PyObject *)&PyBobIoVideoWriter_Type) < 0) return 0;
 #endif /* WITH_FFMPEG */
 
-  Py_INCREF(&PyBobIoHDF5File_Type);
-  PyModule_AddObject(m, "HDF5File", (PyObject *)&PyBobIoHDF5File_Type);
-
   static void* PyXbobIo_API[PyXbobIo_API_pointers];
 
   /* exhaustive list of C APIs */
@@ -138,6 +114,18 @@ PyMODINIT_FUNC XBOB_EXT_ENTRY_NAME (void) {
 
   PyXbobIo_API[PyBobIo_TypeInfoAsTuple_NUM] = (void *)PyBobIo_TypeInfoAsTuple;
 
+  PyXbobIo_API[PyBobIo_FilenameConverter_NUM] = (void *)PyBobIo_FilenameConverter;
+
+  /*****************
+   * HDF5 bindings *
+   *****************/
+
+  PyXbobIo_API[PyBobIoHDF5File_Type_NUM] = (void *)&PyBobIoHDF5File_Type;
+  
+  PyXbobIo_API[PyBobIoHDF5File_Check_NUM] = (void *)&PyBobIoHDF5File_Check;
+
+  PyXbobIo_API[PyBobIoHDF5File_Converter_NUM] = (void *)&PyBobIoHDF5File_Converter;
+
 #if WITH_FFMPEG
   /******************
    * Video bindings *
@@ -150,16 +138,6 @@ PyMODINIT_FUNC XBOB_EXT_ENTRY_NAME (void) {
   PyXbobIo_API[PyBobIoVideoWriter_Type_NUM] = (void *)&PyBobIoVideoWriter_Type;
 #endif /* WITH_FFMPEG */
 
-  /*****************
-   * HDF5 bindings *
-   *****************/
-
-  PyXbobIo_API[PyBobIoHDF5File_Type_NUM] = (void *)&PyBobIoHDF5File_Type;
-  
-  PyXbobIo_API[PyBobIoHDF5File_Check_NUM] = (void *)&PyBobIoHDF5File_Check;
-
-  PyXbobIo_API[PyBobIoHDF5File_Converter_NUM] = (void *)&PyBobIoHDF5File_Converter;
-
 #if PY_VERSION_HEX >= 0x02070000
 
   /* defines the PyCapsule */
@@ -173,16 +151,21 @@ PyMODINIT_FUNC XBOB_EXT_ENTRY_NAME (void) {
 
 #endif
 
-  if (c_api_object) PyModule_AddObject(m, "_C_API", c_api_object);
+  if (!c_api_object) return 0;
 
-  /* imports the NumPy C-API */
-  import_array();
+  if (PyModule_AddObject(m, "_C_API", c_api_object) < 0) return 0;
 
-  /* imports xbob.blitz C-API */
-  import_xbob_blitz();
+  /* imports xbob.blitz C-API + dependencies */
+  if (import_xbob_blitz() < 0) return 0;
 
-# if PY_VERSION_HEX >= 0x03000000
+  Py_INCREF(m);
   return m;
-# endif
 
 }
+
+PyMODINIT_FUNC XBOB_EXT_ENTRY_NAME (void) {
+# if PY_VERSION_HEX >= 0x03000000
+  return
+# endif
+    create_module();
+}
diff --git a/xbob/io/videoreader.cpp b/xbob/io/videoreader.cpp
index 439bd79..f5f499c 100644
--- a/xbob/io/videoreader.cpp
+++ b/xbob/io/videoreader.cpp
@@ -13,6 +13,7 @@
 #include <boost/make_shared.hpp>
 #include <numpy/arrayobject.h>
 #include <xbob.blitz/capi.h>
+#include <xbob.blitz/cleanup.h>
 #include <stdexcept>
 
 #define VIDEOREADER_NAME "VideoReader"
@@ -78,23 +79,32 @@ static int PyBobIoVideoReader_Init(PyBobIoVideoReaderObject* self,
   static const char* const_kwlist[] = {"filename", "check", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  char* filename = 0;
+  PyObject* filename = 0;
+
   PyObject* pycheck = 0;
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|O", kwlist,
-        &filename, &pycheck)) return -1;
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O", kwlist,
+        &PyBobIo_FilenameConverter, &filename, &pycheck)) return -1;
+
+  auto filename_ = make_safe(filename);
 
   bool check = false;
   if (pycheck && PyObject_IsTrue(pycheck)) check = true;
 
+#if PY_VERSION_HEX >= 0x03000000
+  const char* c_filename = PyBytes_AS_STRING(filename);
+#else
+  const char* c_filename = PyString_AS_STRING(filename);
+#endif
+
   try {
-    self->v = boost::make_shared<bob::io::VideoReader>(filename, check);
+    self->v = boost::make_shared<bob::io::VideoReader>(c_filename, check);
   }
   catch (std::exception& e) {
     PyErr_SetString(PyExc_RuntimeError, e.what());
     return -1;
   }
   catch (...) {
-    PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for reading: unknown exception caught", filename);
+    PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for reading: unknown exception caught", c_filename);
     return -1;
   }
 
diff --git a/xbob/io/videowriter.cpp b/xbob/io/videowriter.cpp
index 5d5c5f3..c8ebce2 100644
--- a/xbob/io/videowriter.cpp
+++ b/xbob/io/videowriter.cpp
@@ -13,6 +13,7 @@
 #include <boost/make_shared.hpp>
 #include <numpy/arrayobject.h>
 #include <xbob.blitz/cppapi.h>
+#include <xbob.blitz/cleanup.h>
 #include <stdexcept>
 
 #define VIDEOWRITER_NAME "VideoWriter"
@@ -100,7 +101,8 @@ static int PyBobIoVideoWriter_Init(PyBobIoVideoWriterObject* self,
     0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  char* filename = 0;
+  PyObject* filename = 0;
+
   Py_ssize_t height = 0;
   Py_ssize_t width = 0;
 
@@ -111,10 +113,13 @@ static int PyBobIoVideoWriter_Init(PyBobIoVideoWriterObject* self,
   char* format = 0;
   PyObject* pycheck = 0;
 
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "snn|ddnssO", kwlist,
-        &filename, &height, &width, &framerate, &bitrate, &gop, &codec,
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&nn|ddnssO", kwlist,
+        &PyBobIo_FilenameConverter, &filename, 
+        &height, &width, &framerate, &bitrate, &gop, &codec,
         &format, &pycheck)) return -1;
 
+  auto filename_ = make_safe(filename);
+
   if (pycheck && PyObject_IsTrue(pycheck)) {
     PyErr_SetString(PyExc_TypeError, "argument to `check' must be a boolean");
     return -1;
@@ -123,8 +128,14 @@ static int PyBobIoVideoWriter_Init(PyBobIoVideoWriterObject* self,
   bool check = false;
   if (pycheck && (pycheck == Py_True)) check = true;
 
+#if PY_VERSION_HEX >= 0x03000000
+  const char* c_filename = PyBytes_AS_STRING(filename);
+#else
+  const char* c_filename = PyString_AS_STRING(filename);
+#endif
+
   try {
-    self->v = boost::make_shared<bob::io::VideoWriter>(filename, height, width,
+    self->v = boost::make_shared<bob::io::VideoWriter>(c_filename, height, width,
         framerate, bitrate, gop, codec, format, check);
   }
   catch (std::exception& e) {
@@ -132,7 +143,7 @@ static int PyBobIoVideoWriter_Init(PyBobIoVideoWriterObject* self,
     return -1;
   }
   catch (...) {
-    PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for writing: unknown exception caught", filename);
+    PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for writing: unknown exception caught", c_filename);
     return -1;
   }
 
-- 
GitLab