From abbc1b78b7ffea18b30db4355aca4e2033bedbb6 Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.dos.anjos@gmail.com> Date: Thu, 7 Nov 2013 20:33:11 +0100 Subject: [PATCH] Implementation of support functions and tests --- setup.py | 13 + xbob/io/__init__.py | 9 +- xbob/io/externals.cpp | 846 ++++++++++++++++++++++++++++++++++ xbob/io/file.cpp | 113 +++-- xbob/io/include/xbob.io/api.h | 49 +- xbob/io/main.cpp | 58 +-- xbob/io/test/test_file.py | 8 +- xbob/io/test/test_video.py | 39 +- xbob/io/test/utils.py | 19 +- xbob/io/utils.py | 2 + xbob/io/videoreader.cpp | 185 ++++++++ 11 files changed, 1237 insertions(+), 104 deletions(-) create mode 100644 xbob/io/externals.cpp create mode 100644 xbob/io/videoreader.cpp diff --git a/setup.py b/setup.py index b071fb2..4238287 100644 --- a/setup.py +++ b/setup.py @@ -97,10 +97,23 @@ setup( ], ext_modules = [ + Extension("xbob.io._externals", + [ + "xbob/io/externals.cpp", + ], + define_macros=define_macros, + include_dirs=include_dirs, + extra_compile_args=extra_compile_args, + library_dirs=bob_pkg.library_directories(), + runtime_library_dirs=bob_pkg.library_directories(), + libraries=bob_libraries, + language="c++", + ), Extension("xbob.io._library", [ "xbob/io/bobskin.cpp", "xbob/io/file.cpp", + "xbob/io/videoreader.cpp", "xbob/io/main.cpp", ], define_macros=define_macros, diff --git a/xbob/io/__init__.py b/xbob/io/__init__.py index 8a06256..39f2bbe 100644 --- a/xbob/io/__init__.py +++ b/xbob/io/__init__.py @@ -1,4 +1,5 @@ from ._library import __version__, __api_version__, File +from . import _externals import os @@ -146,28 +147,28 @@ def peek(filename): Effectively, this is the same as creating a :py:class:`bob.io.File` object with the mode flag set to `r` (read-only) and returning - :py:attr:`bob.io.File.type`. + :py:attr:`bob.io.File.describe()`. Parameters: filename The name of the file to peek information from """ - return File(filename, 'r').type + return File(filename, 'r').describe() def peek_all(filename): """Returns the type of array (for full readouts) saved in the given file. Effectively, this is the same as creating a :py:class:`bob.io.File` object with the mode flag set to `r` (read-only) and returning - :py:attr:`bob.io.File.type_all`. + :py:attr:`bob.io.File.describe(all=True)`. Parameters: filename The name of the file to peek information from """ - return File(filename, 'r').type_all + return File(filename, 'r').describe(all=True) # Keeps compatibility with the previously existing API open = File diff --git a/xbob/io/externals.cpp b/xbob/io/externals.cpp new file mode 100644 index 0000000..e474aaa --- /dev/null +++ b/xbob/io/externals.cpp @@ -0,0 +1,846 @@ +/** + * @author Andre Anjos <andre.anjos@idiap.ch> + * @date Thu 7 Nov 13:50:16 2013 + * + * @brief Binds configuration information available from bob + */ + +#define XBOB_IO_MODULE +#include <xbob.io/config.h> + +#define XBOB_IO_VERSIONS_MODULE_NAME _externals + +#include <string> +#include <cstdlib> +#include <boost/preprocessor/stringize.hpp> +#include <boost/format.hpp> + +#include <bob/config.h> +#include <bob/io/CodecRegistry.h> +#include <bob/io/VideoUtilities.h> + +extern "C" { + +#include <Python.h> + +#ifdef NO_IMPORT_ARRAY +#undef NO_IMPORT_ARRAY +#endif +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include <numpy/arrayobject.h> + +#include <hdf5.h> +#include <jpeglib.h> + +#define PNG_SKIP_SETJMP_CHECK +// #define requires because of the problematic pngconf.h. +// Look at the thread here: +// https://bugs.launchpad.net/ubuntu/+source/libpng/+bug/218409 +#include <png.h> + +#if WITH_FFMPEG +# include <libavformat/avformat.h> +# include <libavcodec/avcodec.h> +# include <libavutil/avutil.h> +# include <libswscale/swscale.h> +# include <libavutil/opt.h> +# include <libavutil/pixdesc.h> +#endif + +#include <gif_lib.h> + +#if WITH_MATIO +#include <matio.h> +#endif + +#include <tiffio.h> + +} + +static int dict_set(PyObject* d, const char* key, const char* value) { + PyObject* v = Py_BuildValue("s", value); + if (!v) return 0; + int retval = PyDict_SetItemString(d, key, v); + Py_DECREF(v); + if (retval == 0) return 1; //all good + return 0; //a problem occurred +} + +static int dict_steal(PyObject* d, const char* key, PyObject* value) { + if (!value) return 0; + int retval = PyDict_SetItemString(d, key, value); + Py_DECREF(value); + if (retval == 0) return 1; //all good + return 0; //a problem occurred +} + +/** + * Creates an str object, from a C or C++ string. Returns a **new + * reference**. + */ +static PyObject* make_object(const char* s) { + return Py_BuildValue("s", s); +} + +#if WITH_FFMPEG + +static PyObject* make_object(bool v) { + if (v) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +static PyObject* make_object(unsigned int v) { + return Py_BuildValue("n", v); +} + +static PyObject* make_object(double v) { + return PyFloat_FromDouble(v); +} + +static PyObject* make_object(PyObject* v) { + Py_INCREF(v); + return v; +} + +/** + * Sets a dictionary entry using a string as key and another one as value. + * Returns 1 in case of success, 0 in case of failure. + */ +template <typename T> +int dict_set_string(boost::shared_ptr<PyObject> d, const char* key, T value) { + PyObject* pyvalue = make_object(value); + if (!pyvalue) return 0; + int retval = PyDict_SetItemString(d.get(), key, pyvalue); + Py_DECREF(pyvalue); + if (retval == 0) return 1; //all good + return 0; //a problem occurred +} + +/** + * Sets a dictionary entry using a string as key and another one as value. + * Returns 1 in case of success, 0 in case of faiulre. + */ +template <typename T> +int list_append(boost::shared_ptr<PyObject> l, T value) { + PyObject* pyvalue = make_object(value); + if (!pyvalue) return 0; + int retval = PyList_Append(l.get(), pyvalue); + Py_DECREF(pyvalue); + if (retval == 0) return 1; //all good + return 0; //a problem occurred +} + +/** + * A deleter, for shared_ptr's + */ +void pyobject_deleter(PyObject* o) { + Py_XDECREF(o); +} + +/** + * Checks if it is a Python string for Python 2.x or 3.x + */ +int check_string(PyObject* o) { +# if PY_VERSION_HEX >= 0x03000000 + return PyUnicode_Check(o); +# else + return PyString_Check(o); +# endif +} + +#endif /* WITH_FFMPEG */ + +/*********************************************************** + * Version number generation + ***********************************************************/ + +static PyObject* hdf5_version() { + boost::format f("%s.%s.%s"); + f % BOOST_PP_STRINGIZE(H5_VERS_MAJOR); + f % BOOST_PP_STRINGIZE(H5_VERS_MINOR); + f % BOOST_PP_STRINGIZE(H5_VERS_RELEASE); + return Py_BuildValue("s", f.str().c_str()); +} + +/** + * FFmpeg version + */ +static PyObject* ffmpeg_version() { + PyObject* retval = PyDict_New(); + if (!retval) return 0; + +#if WITH_FFMPEG +# if defined(FFMPEG_VERSION) + if (std::strlen(FFMPEG_VERSION)) { + if (!dict_set(retval, "ffmpeg", FFMPEG_VERSION)) { + Py_DECREF(retval); + return 0; + } + } +# endif + if (!dict_set(retval, "avformat", BOOST_PP_STRINGIZE(LIBAVFORMAT_VERSION))) { + Py_DECREF(retval); + return 0; + } + if (!dict_set(retval, "avcodec", BOOST_PP_STRINGIZE(LIBAVCODEC_VERSION))) { + Py_DECREF(retval); + return 0; + } + if (!dict_set(retval, "avutil", BOOST_PP_STRINGIZE(LIBAVUTIL_VERSION))) { + Py_DECREF(retval); + return 0; + } + if (!dict_set(retval, "swscale", BOOST_PP_STRINGIZE(LIBSWSCALE_VERSION))) { + Py_DECREF(retval); + return 0; + } +#else + if (!dict_set(retval, "ffmpeg", "unavailable")) { + Py_DECREF(retval); + return 0; + } +#endif + return retval; +} + +/** + * LibJPEG version + */ +static PyObject* libjpeg_version() { + boost::format f("%d (compiled with %d bits depth)"); + f % JPEG_LIB_VERSION; + f % BITS_IN_JSAMPLE; + return Py_BuildValue("s", f.str().c_str()); +} + +/** + * Libpng version + */ +static PyObject* libpng_version() { + return Py_BuildValue("s", PNG_LIBPNG_VER_STRING); +} + +/** + * Libtiff version + */ +static PyObject* libtiff_version() { + + static const std::string beg_str("LIBTIFF, Version "); + static const size_t beg_len = beg_str.size(); + std::string vtiff(TIFFGetVersion()); + + // Remove first part if it starts with "LIBTIFF, Version " + if(vtiff.compare(0, beg_len, beg_str) == 0) + vtiff = vtiff.substr(beg_len); + + // Remove multiple (copyright) lines if any + size_t end_line = vtiff.find("\n"); + if(end_line != std::string::npos) + vtiff = vtiff.substr(0,end_line); + + return Py_BuildValue("s", vtiff.c_str()); + +} + +/** + * Version of giflib support + */ +static PyObject* giflib_version() { +#ifdef GIF_LIB_VERSION + return Py_BuildValue("s", GIF_LIB_VERSION); +#else + boost::format f("%s.%s.%s"); + f % BOOST_PP_STRINGIZE(GIFLIB_MAJOR); + f % BOOST_PP_STRINGIZE(GIFLIB_MINOR); + f % BOOST_PP_STRINGIZE(GIFLIB_RELEASE); + return Py_BuildValue("s", f.str().c_str()); +#endif +} + + +/** + * Matio, if compiled with such support + */ +static PyObject* matio_version() { +#if WITH_MATIO + boost::format f("%s.%s.%s"); + f % BOOST_PP_STRINGIZE(MATIO_MAJOR_VERSION); + f % BOOST_PP_STRINGIZE(MATIO_MINOR_VERSION); + f % BOOST_PP_STRINGIZE(MATIO_RELEASE_LEVEL); + return Py_BuildValue("s", f.str().c_str()); +#else + return Py_BuildValue("s", "unavailable"); +#endif +} + +static PyObject* build_version_dictionary() { + + PyObject* retval = PyDict_New(); + if (!retval) return 0; + + if (!dict_steal(retval, "HDF5", hdf5_version())) { + Py_DECREF(retval); + return 0; + } + + if (!dict_steal(retval, "FFmpeg", ffmpeg_version())) { + Py_DECREF(retval); + return 0; + } + + if (!dict_steal(retval, "libjpeg", libjpeg_version())) { + Py_DECREF(retval); + return 0; + } + + if (!dict_set(retval, "libnetpbm", "Unknown version")) { + Py_DECREF(retval); + return 0; + } + + if (!dict_steal(retval, "libpng", libpng_version())) { + Py_DECREF(retval); + return 0; + } + + if (!dict_steal(retval, "libtiff", libtiff_version())) { + Py_DECREF(retval); + return 0; + } + + if (!dict_steal(retval, "giflib", giflib_version())) { + Py_DECREF(retval); + return 0; + } + + if (!dict_steal(retval, "MatIO", matio_version())) { + Py_DECREF(retval); + return 0; + } + + return retval; +} + +/*********************************************************** + * FFmpeg information + ***********************************************************/ + +#if WITH_FFMPEG + +/** + * Describes a given codec. We return a **new reference** to a dictionary + * containing the codec properties. + */ +static PyObject* describe_codec(const AVCodec* codec) { + + /** + * We wrap the returned object into a smart pointer until we + * are absolutely sure all went good. At this point, we free + * the PyObject* from the boost encapsulation and return it. + */ + boost::shared_ptr<PyObject> retval(PyDict_New(), &pyobject_deleter); + if (!retval) return 0; + + /* Sets basic properties for the codec */ + if (!dict_set_string(retval, "name", codec->name)) return 0; + if (!dict_set_string(retval, "long_name", codec->long_name)) return 0; + if (!dict_set_string(retval, "id", (unsigned int)codec->id)) return 0; + + /** + * If pixel formats are available, creates and attaches a + * list with all their names + */ + + boost::shared_ptr<PyObject> pixfmt; + if (codec->pix_fmts) { + pixfmt.reset(PyList_New(0), &pyobject_deleter); + if (!pixfmt) return 0; + + unsigned int i=0; + while(codec->pix_fmts[i] != -1) { + if (!list_append(pixfmt, +#if LIBAVUTIL_VERSION_INT >= 0x320f01 //50.15.1 @ ffmpeg-0.6 + av_get_pix_fmt_name +#else + avcodec_get_pix_fmt_name +#endif + (codec->pix_fmts[i++]))) return 0; + } + pixfmt.reset(PySequence_Tuple(pixfmt.get()), &pyobject_deleter); + } + else { + Py_INCREF(Py_None); + pixfmt.reset(Py_None, &pyobject_deleter); + } + + if (!dict_set_string(retval, "pixfmts", pixfmt.get())) return 0; + + /* Get specific framerates for the codec, if any */ + const AVRational* rate = codec->supported_framerates; + boost::shared_ptr<PyObject> rates(PyList_New(0), &pyobject_deleter); + if (!rates) return 0; + + while (rate && rate->num && rate->den) { + list_append(rates, ((double)rate->num)/((double)rate->den)); + ++rate; + } + rates.reset(PySequence_Tuple(rates.get()), &pyobject_deleter); + if (!rates) return 0; + + if (!dict_set_string(retval, "specific_framerates_hz", rates.get())) return 0; + + /* Other codec capabilities */ +# ifdef CODEC_CAP_LOSSLESS + if (!dict_set_string(retval, "lossless", (bool)(codec->capabilities & CODEC_CAP_LOSSLESS))) return 0; +# endif +# ifdef CODEC_CAP_EXPERIMENTAL + if (!dict_set_string(retval, "experimental", (bool)(codec->capabilities & CODEC_CAP_EXPERIMENTAL))) return 0; +# endif +# ifdef CODEC_CAP_DELAY + if (!dict_set_string(retval, "delay", (bool)(codec->capabilities & CODEC_CAP_DELAY))) return 0; +# endif +# ifdef CODEC_CAP_HWACCEL + if (!dict_set_string(retval, "hardware_accelerated", (bool)(codec->capabilities & CODEC_CAP_HWACCEL))) return 0; +# endif + if (!dict_set_string(retval, "encode", (bool)(avcodec_find_encoder(codec->id)))) return 0; + if (!dict_set_string(retval, "decode", (bool)(avcodec_find_decoder(codec->id)))) return 0; + + /* If all went OK, detach the returned value from the smart pointer **/ + Py_INCREF(retval.get()); + return retval.get(); + +} + +/** + * Describes a given codec or raises, in case the codec cannot be accessed + */ +static PyObject* PyBobIo_DescribeEncoder(PyObject*, PyObject *args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = {"key", 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + PyObject* key = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &key)) return 0; + + if (!PyNumber_Check(key) && !check_string(key)) { + PyErr_SetString(PyExc_TypeError, "input `key' must be a number identifier or a string with the codec name"); + return 0; + } + + if (PyNumber_Check(key)) { + + /* If you get to this point, the user passed a number - re-parse */ + int id = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &id)) return 0; + + AVCodec* codec = avcodec_find_encoder((AVCodecID)id); + if (!codec) { + PyErr_Format(PyExc_RuntimeError, "ffmpeg::avcodec_find_encoder(%d == 0x%x) did not return a valid codec", id, id); + return 0; + } + + return describe_codec(codec); + } + + /* If you get to this point, the user passed a string - re-parse */ + const char* name = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &name)) return 0; + + AVCodec* codec = avcodec_find_encoder_by_name(name); + if (!codec) { + PyErr_Format(PyExc_RuntimeError, "ffmpeg::avcodec_find_encoder_by_name(`%s') did not return a valid codec", name); + return 0; + } + + return describe_codec(codec); +} + +PyDoc_STRVAR(s_describe_encoder_str, "describe_encoder"); +PyDoc_STRVAR(s_describe_encoder_doc, +"describe_encoder([key]) -> dict\n\ +\n\ +Parameters:\n\ +\n\ +key\n\ + [int|str, optional] A key which can be either the encoder\n\ + name or its numerical identifier.\n\ +\n\ +Returns a dictionary containing a description of properties in\n\ +the given encoder.\n\ +"); + +/** + * Describes a given codec or raises, in case the codec cannot be accessed + */ +static PyObject* PyBobIo_DescribeDecoder(PyObject*, PyObject *args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = {"key", 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + PyObject* key = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &key)) return 0; + + if (!PyNumber_Check(key) && !check_string(key)) { + PyErr_SetString(PyExc_TypeError, "input `key' must be a number identifier or a string with the codec name"); + return 0; + } + + if (PyNumber_Check(key)) { + + /* If you get to this point, the user passed a number - re-parse */ + int id = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &id)) return 0; + + AVCodec* codec = avcodec_find_decoder((AVCodecID)id); + if (!codec) { + PyErr_Format(PyExc_RuntimeError, "ffmpeg::avcodec_find_decoder(%d == 0x%x) did not return a valid codec", id, id); + return 0; + } + + return describe_codec(codec); + } + + /* If you get to this point, the user passed a string - re-parse */ + const char* name = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &name)) return 0; + + AVCodec* codec = avcodec_find_decoder_by_name(name); + if (!codec) { + PyErr_Format(PyExc_RuntimeError, "ffmpeg::avcodec_find_decoder_by_name(`%s') did not return a valid codec", name); + return 0; + } + + return describe_codec(codec); +} + +PyDoc_STRVAR(s_describe_decoder_str, "describe_decoder"); +PyDoc_STRVAR(s_describe_decoder_doc, +"describe_decoder([key]) -> dict\n\ +\n\ +Parameters:\n\ +\n\ +key\n\ + [int|str, optional] A key which can be either the decoder\n\ + name or its numerical identifier.\n\ +\n\ +Returns a dictionary containing a description of properties in\n\ +the given decoder.\n\ +"); + +static PyObject* PyBobIo_SupportedCodecs(PyObject*) { + + std::map<std::string, const AVCodec*> m; + bob::io::detail::ffmpeg::codecs_supported(m); + + PyObject* retval = PyDict_New(); + if (!retval) return 0; + + for (auto k=m.begin(); k!=m.end(); ++k) { + PyObject* pyvalue = describe_codec(k->second); + if (!pyvalue) { + Py_DECREF(retval); + return 0; + } + if (PyDict_SetItemString(retval, k->first.c_str(), pyvalue) != 0) { + Py_DECREF(pyvalue); + Py_DECREF(retval); + return 0; + } + Py_DECREF(pyvalue); + } + + return retval; + +} + +PyDoc_STRVAR(s_supported_codecs_str, "supported_video_codecs"); +PyDoc_STRVAR(s_supported_codecs_doc, +"supported_video_codecs() -> dict\n\ +\n\ +Returns a dictionary with currently supported video codec properties.\n\ +\n\ +Returns a dictionary containing a detailed description of the\n\ +built-in codecs for videos that are fully supported.\n\ +"); + +static PyObject* PyBobIo_AvailableCodecs(PyObject*) { + + std::map<std::string, const AVCodec*> m; + bob::io::detail::ffmpeg::codecs_installed(m); + + PyObject* retval = PyDict_New(); + if (!retval) return 0; + + for (auto k=m.begin(); k!=m.end(); ++k) { + PyObject* pyvalue = describe_codec(k->second); + if (!pyvalue) { + Py_DECREF(retval); + return 0; + } + if (PyDict_SetItemString(retval, k->first.c_str(), pyvalue) != 0) { + Py_DECREF(pyvalue); + Py_DECREF(retval); + return 0; + } + Py_DECREF(pyvalue); + } + + return retval; + +} + +PyDoc_STRVAR(s_available_codecs_str, "available_video_codecs"); +PyDoc_STRVAR(s_available_codecs_doc, +"available_video_codecs() -> dict\n\ +\n\ +Returns a dictionary with all available video codec properties.\n\ +\n\ +Returns a dictionary containing a detailed description of the\n\ +built-in codecs for videos that are available but **not necessarily\n\ +supported**.\n\ +"); + +/** + * Returns all input formats supported, related codecs and extensions + */ +/** +static dict supported_iformat_dictionary() { + std::map<std::string, AVInputFormat*> m; + bob::io::detail::ffmpeg::iformats_supported(m); + dict retval; + + for (auto k=m.begin(); k!=m.end(); ++k) { + dict property; + property["name"] = k->second->name; + property["long_name"] = k->second->long_name; + + // get extensions + std::vector<std::string> exts; + bob::io::detail::ffmpeg::tokenize_csv(k->second->extensions, exts); + list ext_list; + for (auto ext=exts.begin(); ext!=exts.end(); ++ext) ext_list.append(*ext); + property["extensions"] = tuple(ext_list); + + retval[k->first] = property; + } + + return retval; +} + +static dict available_iformat_dictionary() { + std::map<std::string, AVInputFormat*> m; + bob::io::detail::ffmpeg::iformats_installed(m); + dict retval; + + for (auto k=m.begin(); k!=m.end(); ++k) { + dict property; + property["name"] = k->second->name; + property["long_name"] = k->second->long_name; + + // get extensions + std::vector<std::string> exts; + bob::io::detail::ffmpeg::tokenize_csv(k->second->extensions, exts); + list ext_list; + for (auto ext=exts.begin(); ext!=exts.end(); ++ext) ext_list.append(*ext); + property["extensions"] = tuple(ext_list); + + retval[k->first] = property; + } + + return retval; +} +**/ + +/** + * Returns all output formats supported, related codecs and extensions + */ +/** +static dict supported_oformat_dictionary() { + std::map<std::string, AVOutputFormat*> m; + bob::io::detail::ffmpeg::oformats_supported(m); + dict retval; + + for (auto k=m.begin(); k!=m.end(); ++k) { + dict property; + property["name"] = k->second->name; + property["long_name"] = k->second->long_name; + property["mime_type"] = k->second->mime_type; + + // get extensions + std::vector<std::string> exts; + bob::io::detail::ffmpeg::tokenize_csv(k->second->extensions, exts); + list ext_list; + for (auto ext=exts.begin(); ext!=exts.end(); ++ext) ext_list.append(*ext); + property["extensions"] = tuple(ext_list); + + // get recommended codec + if (!k->second->video_codec) { + property["default_codec"] = object(); + } + else { + AVCodec* codec = avcodec_find_encoder(k->second->video_codec); + if (!codec) property["default_codec"] = object(); + else property["default_codec"] = describe_codec(codec); + } + + // supported codec list + std::vector<const AVCodec*> codecs; + bob::io::detail::ffmpeg::oformat_supported_codecs(k->second->name, codecs); + dict supported_codecs; + for (auto c=codecs.begin(); c!=codecs.end(); ++c) { + supported_codecs[(*c)->name] = describe_codec(*c); + } + property["supported_codecs"] = supported_codecs; + + retval[k->first] = property; + } + + return retval; +} +**/ + +/** +static dict available_oformat_dictionary() { + std::map<std::string, AVOutputFormat*> m; + bob::io::detail::ffmpeg::oformats_installed(m); + dict retval; + + for (auto k=m.begin(); k!=m.end(); ++k) { + dict property; + property["name"] = k->second->name; + property["long_name"] = k->second->long_name; + property["mime_type"] = k->second->mime_type; + + // get extensions + std::vector<std::string> exts; + bob::io::detail::ffmpeg::tokenize_csv(k->second->extensions, exts); + list ext_list; + for (auto ext=exts.begin(); ext!=exts.end(); ++ext) ext_list.append(*ext); + property["extensions"] = tuple(ext_list); + + // get recommended codec + if (!k->second->video_codec) { + property["default_codec"] = object(); + } + else { + AVCodec* codec = avcodec_find_encoder(k->second->video_codec); + if (!codec) property["default_codec"] = object(); + else property["default_codec"] = describe_codec(codec); + } + + retval[k->first] = property; + } + + return retval; +} +**/ + +/** + def("available_videoreader_formats", &available_iformat_dictionary, "Returns a dictionary containing a detailed description of the built-in input formats that are available, but **not necessarily supported**"); + + def("supported_videoreader_formats", &supported_iformat_dictionary, "Returns a dictionary containing a detailed description of the built-in input formats that are fully supported"); + + def("available_videowriter_formats", &available_oformat_dictionary, "Returns a dictionary containing a detailed description of the built-in output formats and default encoders for videos that are available, but **not necessarily supported**"); + + def("supported_videowriter_formats", &supported_oformat_dictionary, "Returns a dictionary containing a detailed description of the built-in output formats and default encoders for videos that are fully supported"); + +**/ + +#endif /* WITH_FFMPEG */ + +static PyObject* PyBobIo_Extensions(PyObject*) { + + typedef std::map<std::string, std::string> map_type; + const map_type& table = bob::io::CodecRegistry::getExtensions(); + + PyObject* retval = PyDict_New(); + if (!retval) return 0; + + for (auto it=table.begin(); it!=table.end(); ++it) { + PyObject* pyvalue = make_object(it->second.c_str()); + if (!pyvalue) { + Py_DECREF(retval); + return 0; + } + if (PyDict_SetItemString(retval, it->first.c_str(), pyvalue) != 0) { + Py_DECREF(pyvalue); + Py_DECREF(retval); + return 0; + } + Py_DECREF(pyvalue); + } + return retval; + +} + +PyDoc_STRVAR(s_extensions_str, "extensions"); +PyDoc_STRVAR(s_extensions_doc, +"as_blitz(x) -> dict\n\ +\n\ +Returns a dictionary containing all extensions and descriptions\n\ +currently stored on the global codec registry\n\ +"); + +static PyMethodDef module_methods[] = { + { + s_extensions_str, + (PyCFunction)PyBobIo_Extensions, + METH_NOARGS, + s_extensions_doc, + }, + +#if WITH_FFMPEG + { + s_describe_encoder_str, + (PyCFunction)PyBobIo_DescribeEncoder, + METH_VARARGS|METH_KEYWORDS, + s_describe_encoder_doc, + }, + { + s_describe_decoder_str, + (PyCFunction)PyBobIo_DescribeDecoder, + METH_VARARGS|METH_KEYWORDS, + s_describe_decoder_doc, + }, + { + s_supported_codecs_str, + (PyCFunction)PyBobIo_SupportedCodecs, + METH_NOARGS, + s_supported_codecs_doc, + }, + { + s_available_codecs_str, + (PyCFunction)PyBobIo_AvailableCodecs, + METH_NOARGS, + s_available_codecs_doc, + }, + +#endif /* WITH_FFMPEG */ + + {0} /* Sentinel */ +}; + +PyDoc_STRVAR(module_docstr, +"Information about software used to compile the C++ Bob API" +); + +int PyXbobCoreRandom_APIVersion = XBOB_IO_API_VERSION; + +#define ENTRY_FUNCTION_INNER(a) init ## a +#define ENTRY_FUNCTION(a) ENTRY_FUNCTION_INNER(a) + +PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_VERSIONS_MODULE_NAME) (void) { + + PyObject* m = Py_InitModule3(BOOST_PP_STRINGIZE(XBOB_IO_VERSIONS_MODULE_NAME), module_methods, module_docstr); + + /* register some constants */ + PyModule_AddIntConstant(m, "__api_version__", XBOB_IO_API_VERSION); + PyModule_AddStringConstant(m, "__version__", BOOST_PP_STRINGIZE(XBOB_IO_VERSION)); + PyModule_AddObject(m, "versions", build_version_dictionary()); + + /* imports the NumPy C-API */ + import_array(); + +} diff --git a/xbob/io/file.cpp b/xbob/io/file.cpp index 4187cc3..70b5c20 100644 --- a/xbob/io/file.cpp +++ b/xbob/io/file.cpp @@ -44,31 +44,30 @@ static int PyBobIoFile_Init(PyBobIoFileObject* self, PyObject *args, PyObject* k static char** kwlist = const_cast<char**>(const_kwlist); char* filename = 0; - char* mode = 0; - int mode_len = 0; + char mode = 0; char* pretend_extension = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss#|s", kwlist, &filename, - &mode, &mode_len, &pretend_extension)) return -1; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sc|s", kwlist, &filename, + &mode, &pretend_extension)) return -1; - if (mode_len != 1 || !(mode[0] == 'r' || mode[0] == 'w' || mode[0] == 'a')) { + 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; } try { if (pretend_extension) { - self->f = bob::io::open(filename, mode[0], pretend_extension); + self->f = bob::io::open(filename, mode, pretend_extension); } else { - self->f = bob::io::open(filename, mode[0]); + self->f = bob::io::open(filename, mode); } } catch (std::exception& e) { - PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%s': %s", filename, mode, e.what()); + PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%c': %s", filename, mode, e.what()); return -1; } catch (...) { - PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%s': unknown exception caught", filename, mode); + PyErr_Format(PyExc_RuntimeError, "cannot open file `%s' with mode `%c': unknown exception caught", filename, mode); return -1; } @@ -76,7 +75,7 @@ static int PyBobIoFile_Init(PyBobIoFileObject* self, PyObject *args, PyObject* k } PyDoc_STRVAR(s_file_doc, -"file(filename, mode, [pretend_extension]) -> new bob::io::File\n\ +"File(filename, mode, [pretend_extension]) -> new bob::io::File\n\ \n\ Use this object to read and write data into files.\n\ \n\ @@ -455,6 +454,71 @@ Returns the current position of the newly written array.\n\ " ); +static PyObject* PyBobIoFile_Describe(PyBobIoFileObject* self, PyObject *args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = {"all", 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + PyObject* all = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &all)) return 0; + + if (all && !PyBool_Check(all)) { + PyErr_SetString(PyExc_TypeError, "argument to `all' must be a boolean"); + return 0; + } + + const bob::core::array::typeinfo* info = 0; + if (all && all == Py_True) info = &self->f->type_all(); + else info = &self->f->type(); + + /* Now return type description and tuples with shape and strides */ + int type_num = PyBobIo_AsTypenum(info->dtype); + if (type_num == NPY_NOTYPE) return 0; + + /* Get data-type */ + PyObject* dtype = reinterpret_cast<PyObject*>(PyArray_DescrFromType(type_num)); + + /* Build shape and stride */ + PyObject* shape = PyTuple_New(info->nd); + PyObject* stride = PyTuple_New(info->nd); + + for (Py_ssize_t i=0; i<info->nd; ++i) { +# if PY_VERSION_HEX >= 0x03000000 + PyObject* shape_item = PyLong_FromSsize_t(info->shape[i]); + PyObject* stride_item = PyLong_FromSsize_t(info->stride[i]); +# else + PyObject* shape_item = PyInt_FromSsize_t(info->shape[i]); + PyObject* stride_item = PyInt_FromSsize_t(info->stride[i]); +# endif + PyTuple_SetItem(shape, i, shape_item); + PyTuple_SetItem(stride, i, stride_item); + } + + /* Return a new tuple */ + PyObject* retval = PyTuple_New(3); + PyTuple_SetItem(retval, 0, dtype); + PyTuple_SetItem(retval, 1, shape); + PyTuple_SetItem(retval, 2, stride); + + return retval; + +} + +PyDoc_STRVAR(s_describe_str, "describe"); +PyDoc_STRVAR(s_describe_doc, +"describe([all]) -> tuple\n\ +\n\ +Returns a description (dtype, shape, stride) of data at the file.\n\ +\n\ +Parameters:\n\ +\n\ +all\n\ + [bool] If set, return the shape and strides for reading\n\ + the whole file contents in one go.\n\ +\n\ +"); + static PyMethodDef PyBobIoFile_Methods[] = { { s_read_str, @@ -474,6 +538,12 @@ static PyMethodDef PyBobIoFile_Methods[] = { METH_VARARGS|METH_KEYWORDS, s_append_doc, }, + { + s_describe_str, + (PyCFunction)PyBobIoFile_Describe, + METH_VARARGS|METH_KEYWORDS, + s_describe_doc, + }, {0} /* Sentinel */ }; @@ -518,26 +588,3 @@ PyTypeObject PyBobIoFile_Type = { 0, /* tp_alloc */ PyBobIoFile_New, /* tp_new */ }; - -/** -static dict extensions() { - typedef std::map<std::string, std::string> map_type; - dict retval; - const map_type& table = bob::io::CodecRegistry::getExtensions(); - for (map_type::const_iterator it=table.begin(); it!=table.end(); ++it) { - retval[it->first] = it->second; - } - return retval; -} - -void bind_io_file() { - - .add_property("type_all", make_function(&bob::io::File::type_all, return_value_policy<copy_const_reference>()), "Typing information to load all of the file at once") - - .add_property("type", make_function(&bob::io::File::type, return_value_policy<copy_const_reference>()), "Typing information to load the file as an Arrayset") - - def("extensions", &extensions, "Returns a dictionary containing all extensions and descriptions currently stored on the global codec registry"); - -} - -**/ diff --git a/xbob/io/include/xbob.io/api.h b/xbob/io/include/xbob.io/api.h index 64f54cf..f1a8e6a 100644 --- a/xbob/io/include/xbob.io/api.h +++ b/xbob/io/include/xbob.io/api.h @@ -10,12 +10,14 @@ #include <xbob.io/config.h> #include <bob/io/File.h> + +#if WITH_FFMPEG +#include <bob/io/VideoReader.h> +#endif /* WITH_FFMPEG */ + #include <boost/preprocessor/stringize.hpp> #include <boost/shared_ptr.hpp> - -extern "C" { #include <Python.h> -} #define XBOB_IO_MODULE_PREFIX xbob.io #define XBOB_IO_MODULE_NAME _library @@ -55,8 +57,31 @@ typedef struct { #define PyBobIo_AsTypenum_RET int #define PyBobIo_AsTypenum_PROTO (bob::core::array::ElementType et) +#if WITH_FFMPEG + +/****************** + * Video bindings * + ******************/ + +typedef struct { + PyObject_HEAD + + /* Type-specific fields go here. */ + boost::shared_ptr<bob::io::VideoReader> v; + +} PyBobIoVideoReaderObject; + +#define PyBobIoVideoReader_Type_NUM 3 +#define PyBobIoVideoReader_Type_TYPE PyTypeObject + +#endif /* WITH_FFMPEG */ + /* Total number of C API pointers */ -#define PyXbobIo_API_pointers 3 +#if WITH_FFMPEG +# define PyXbobIo_API_pointers 4 +#else +# define PyXbobIo_API_pointers 3 +#endif /* WITH_FFMPEG */ #ifdef XBOB_IO_MODULE @@ -80,6 +105,14 @@ typedef struct { PyBobIo_AsTypenum_RET PyBobIo_AsTypenum PyBobIo_AsTypenum_PROTO; +#if WITH_FFMPEG + /****************** + * Video bindings * + ******************/ + + extern PyBobIoVideoReader_Type_TYPE PyBobIoVideoReader_Type; +#endif /* WITH_FFMPEG */ + #else /* This section is used in modules that use `blitz.array's' C-API */ @@ -126,6 +159,14 @@ typedef struct { # define PyBobIo_AsTypenum (*(PyBobIo_AsTypenum_RET (*)PyBobIo_AsTypenum_PROTO) PyXbobIo_API[PyBobIo_AsTypenum_NUM]) +#if WITH_FFMPEG + /****************** + * Video bindings * + ******************/ + +# define PyBobIoVideoReader_Type (*(PyBobIoVideoReader_Type_TYPE *)PyXbobIo_API[PyBobIoVideoReader_Type_NUM]) +#endif /* WITH_FFMPEG */ + /** * Returns -1 on error, 0 on success. PyCapsule_Import will set an exception * if there's an error. diff --git a/xbob/io/main.cpp b/xbob/io/main.cpp index b5cccef..58b8557 100644 --- a/xbob/io/main.cpp +++ b/xbob/io/main.cpp @@ -7,53 +7,13 @@ #define XBOB_IO_MODULE #include <xbob.io/api.h> -#include <bob/io/CodecRegistry.h> #ifdef NO_IMPORT_ARRAY #undef NO_IMPORT_ARRAY #endif #include <blitz.array/capi.h> -static PyObject* PyBobIo_Extensions(PyObject*) { - - typedef std::map<std::string, std::string> map_type; - const map_type& table = bob::io::CodecRegistry::getExtensions(); - - PyObject* retval = PyDict_New(); - if (!retval) return 0; - - for (auto it=table.begin(); it!=table.end(); ++it) { -# if PY_VERSION_HEX >= 0x03000000 - PyObject* value = PyString_FromString(it->second.c_str()); -# else - PyObject* value = PyUnicode_FromString(it->second.c_str()); -# endif - if (!value) { - Py_DECREF(retval); - return 0; - } - PyDict_SetItemString(retval, it->first.c_str(), value); - Py_DECREF(value); - } - return retval; - -} - -PyDoc_STRVAR(s_extensions_str, "extensions"); -PyDoc_STRVAR(s_extensions_doc, -"as_blitz(x) -> dict\n\ -\n\ -Returns a dictionary containing all extensions and descriptions\n\ -currently stored on the global codec registry\n\ -"); - static PyMethodDef module_methods[] = { - { - s_extensions_str, - (PyCFunction)PyBobIo_Extensions, - METH_NOARGS, - s_extensions_doc, - }, {0} /* Sentinel */ }; @@ -69,6 +29,11 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { PyBobIoFile_Type.tp_new = PyType_GenericNew; if (PyType_Ready(&PyBobIoFile_Type) < 0) return; +#if WITH_FFMPEG + PyBobIoVideoReader_Type.tp_new = PyType_GenericNew; + if (PyType_Ready(&PyBobIoVideoReader_Type) < 0) return; +#endif /* WITH_FFMPEG */ + PyObject* m = Py_InitModule3(BOOST_PP_STRINGIZE(XBOB_IO_MODULE_NAME), module_methods, module_docstr); @@ -80,6 +45,11 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { Py_INCREF(&PyBobIoFile_Type); PyModule_AddObject(m, "File", (PyObject *)&PyBobIoFile_Type); +#if WITH_FFMPEG + Py_INCREF(&PyBobIoVideoReader_Type); + PyModule_AddObject(m, "VideoReader", (PyObject *)&PyBobIoVideoReader_Type); +#endif /* WITH_FFMPEG */ + static void* PyXbobIo_API[PyXbobIo_API_pointers]; /* exhaustive list of C APIs */ @@ -102,6 +72,14 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { PyXbobIo_API[PyBobIo_AsTypenum_NUM] = (void *)PyBobIo_AsTypenum; +#if WITH_FFMPEG + /****************** + * Video bindings * + ******************/ + + PyXbobIo_API[PyBobIoVideoReader_Type_NUM] = (void *)&PyBobIoVideoReader_Type; +#endif /* WITH_FFMPEG */ + /* imports the NumPy C-API */ import_array(); diff --git a/xbob/io/test/test_file.py b/xbob/io/test/test_file.py index 643bc7c..0fa83f9 100644 --- a/xbob/io/test/test_file.py +++ b/xbob/io/test/test_file.py @@ -14,7 +14,7 @@ import sys import numpy import nose.tools -from .. import load, write, File +from .. import load, write, peek, peek_all, File from . import utils as testutils def transcode(filename): @@ -272,3 +272,9 @@ def test_csv(): arrayset_readwrite('.csv', a1, close=True) arrayset_readwrite(".csv", a2, close=True) arrayset_readwrite('.csv', a3, close=True) + +def test_peek(): + + f = testutils.datafile('test1.hdf5', __name__) + assert peek(f) == (numpy.uint16, (3,), (1,)) + assert peek_all(f) == (numpy.uint16, (3,3), (3,1)) diff --git a/xbob/io/test/test_video.py b/xbob/io/test/test_video.py index 65991b0..5627997 100644 --- a/xbob/io/test/test_video.py +++ b/xbob/io/test/test_video.py @@ -4,18 +4,6 @@ # Wed Jun 22 17:50:08 2011 +0200 # # Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. """Runs some video tests """ @@ -23,13 +11,31 @@ import os import sys import numpy +import nose.tools from . import utils as testutils -from .._library import supported_videowriter_formats from ..utils import color_distortion, frameskip_detection, quality_degradation # These are some global parameters for the test. INPUT_VIDEO = testutils.datafile('test.mov', __name__) -SUPPORTED = supported_videowriter_formats() + +@testutils.ffmpeg_found() +def test_codec_support(): + + # Describes all encoders + from .._externals import describe_encoder, describe_decoder, supported_video_codecs + + supported = supported_video_codecs() + + for k,v in supported.items(): + # note: searching by name (using `k') will not always work + if v['decode']: assert describe_decoder(v['id']) + if v['encode']: assert describe_encoder(v['id']) + + # Assert we support, at least, some known codecs + for codec in ('ffv1', 'zlib', 'wmv2', 'mpeg4', 'mjpeg'): + assert codec in supported + assert supported[codec]['encode'] + assert supported[codec]['decode'] @testutils.ffmpeg_found() def test_can_use_array_interface(): @@ -144,7 +150,8 @@ def test_format_codecs(): distortions['mpeg2video']['color'] = 9.0 distortions['mpeg2video']['frameskip'] = 1.4 - + from .._library import supported_videowriter_formats + SUPPORTED = supported_videowriter_formats() for format in SUPPORTED: for codec in SUPPORTED[format]['supported_codecs']: for method in methods: @@ -224,6 +231,8 @@ def test_user_video(): msmpeg4v2 = 2.3, ) + from .._library import supported_videowriter_formats + SUPPORTED = supported_videowriter_formats() for format in SUPPORTED: for codec in SUPPORTED[format]['supported_codecs']: check_user_video.description = "%s.test_user_video_format_%s_codec_%s" % (__name__, format, codec) diff --git a/xbob/io/test/utils.py b/xbob/io/test/utils.py index 34f103c..5f72f91 100644 --- a/xbob/io/test/utils.py +++ b/xbob/io/test/utils.py @@ -2,6 +2,8 @@ # vim: set fileencoding=utf-8 : # Andre Anjos <andre.anjos@idiap.ch> # Thu Feb 7 09:58:22 2013 +# +# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland """Re-usable decorators and utilities for xbob test code """ @@ -59,14 +61,17 @@ ffmpeg_versions = { '0.11': [ SV('54.23.100'), SV('54.6.100'), SV('51.54.100') ], '1.0': [ SV('54.59.100'), SV('54.29.104'), SV('51.73.101') ], '1.1': [ SV('54.86.100'), SV('54.59.106'), SV('52.13.100') ], + '1.2': [ SV('54.92.100'), SV('54.63.104'), SV('52.18.100') ], + '2.0': [ SV('55.18.102'), SV('55.12.100'), SV('52.38.100') ], + '2.1': [ SV('55.39.100'), SV('55.19.104'), SV('52.48.100') ], } def ffmpeg_version_lessthan(v): '''Returns true if the version of ffmpeg compiled-in is at least the version indicated as a string parameter.''' - from ..io._io import version - avcodec_inst= SV(version['FFmpeg']['avcodec']) + from .._externals import versions + avcodec_inst= SV(versions['FFmpeg']['avcodec']) avcodec_req = ffmpeg_versions[v][0] return avcodec_inst < avcodec_req @@ -95,10 +100,10 @@ def ffmpeg_found(version_geq=None): @functools.wraps(test) def wrapper(*args, **kwargs): try: - from ..io._io import version - avcodec_inst= SV(version['FFmpeg']['avcodec']) - avformat_inst= SV(version['FFmpeg']['avformat']) - avutil_inst= SV(version['FFmpeg']['avutil']) + from .._externals import versions + avcodec_inst = SV(versions['FFmpeg']['avcodec']) + avformat_inst = SV(versions['FFmpeg']['avformat']) + avutil_inst = SV(versions['FFmpeg']['avutil']) if version_geq is not None: avcodec_req,avformat_req,avutil_req = ffmpeg_versions[version_geq] if avcodec_inst < avcodec_req: @@ -136,7 +141,7 @@ def extension_available(extension): @functools.wraps(test) def wrapper(*args, **kwargs): - from .._library import extensions + from .._externals import extensions if extension in extensions(): return test(*args, **kwargs) else: diff --git a/xbob/io/utils.py b/xbob/io/utils.py index a60aec7..9e3d67b 100644 --- a/xbob/io/utils.py +++ b/xbob/io/utils.py @@ -2,6 +2,8 @@ # vim: set fileencoding=utf-8 : # Andre Anjos <andre.dos.anjos@gmail.com> # Thu 14 Mar 17:00:58 2013 +# +# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland """Some utilities to generate fake patterns """ diff --git a/xbob/io/videoreader.cpp b/xbob/io/videoreader.cpp new file mode 100644 index 0000000..51ab96d --- /dev/null +++ b/xbob/io/videoreader.cpp @@ -0,0 +1,185 @@ +/** + * @author Andre Anjos <andre.anjos@idiap.ch> + * @date Wed 6 Nov 21:44:34 2013 + * + * @brief Bindings to bob::io::VideoReader + */ + +#define XBOB_IO_MODULE +#include <xbob.io/api.h> + +#if WITH_FFMPEG +#include <boost/make_shared.hpp> + +#define VIDEOREADER_NAME VideoReader +PyDoc_STRVAR(s_videoreader_str, BOOST_PP_STRINGIZE(XBOB_IO_MODULE_PREFIX) "." BOOST_PP_STRINGIZE(VIDEOREADER_NAME)); + +/* How to create a new PyBobIoVideoReaderObject */ +static PyObject* PyBobIoVideoReader_New(PyTypeObject* type, PyObject*, PyObject*) { + + /* Allocates the python object itself */ + PyBobIoVideoReaderObject* self = (PyBobIoVideoReaderObject*)type->tp_alloc(type, 0); + + self->v.reset(); + + return reinterpret_cast<PyObject*>(self); +} + +static void PyBobIoVideoReader_Delete (PyBobIoVideoReaderObject* o) { + + o->v.reset(); + o->ob_type->tp_free((PyObject*)o); + +} + +/* The __init__(self) method */ +static int PyBobIoVideoReader_Init(PyBobIoVideoReaderObject* self, + PyObject *args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = {"filename", "check", 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + char* filename = 0; + PyObject* pycheck = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|O", kwlist, + &filename, &pycheck)) return -1; + + if (pycheck && !PyBool_Check(pycheck)) { + PyErr_SetString(PyExc_TypeError, "argument to `check' must be a boolean"); + return -1; + } + + bool check = false; + if (pycheck && (pycheck == Py_True)) check = true; + + try { + self->v = boost::make_shared<bob::io::VideoReader>(filename, check); + } + catch (std::exception& e) { + PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for reading: %s", filename, e.what()); + return -1; + } + catch (...) { + PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for reading: unknown exception caught", filename); + return -1; + } + + return 0; ///< SUCCESS +} + +PyDoc_STRVAR(s_videoreader_doc, +"VideoReader(filename, [check=True]) -> new bob::io::VideoReader\n\ +\n\ +Use this object to read frames from video files.\n\ +\n\ +Constructor parameters:\n\ +\n\ +filename\n\ + [str] The file path to the file you want to read data from\n\ +\n\ +check\n\ + [bool] Format and codec will be extracted from the video metadata.\n\ + By default, if the format and/or the codec are not\n\ + supported by this version of Bob, an exception will be raised.\n\ + You can (at your own risk) set this flag to ``False`` to\n\ + avoid this check.\n\ +\n\ +VideoReader objects can read data from video files. The current\n\ +implementation uses `FFmpeg <http://ffmpeg.org>`_ (or\n\ +`libav <http://libav.org>`_ if FFmpeg is not available) which is\n\ +a stable freely available video encoding and decoding library,\n\ +designed specifically for these tasks. You can read an entire\n\ +video in memory by using the 'load()' method or use iterators\n\ +to read it frame by frame and avoid overloading your machine's\n\ +memory. The maximum precision data `FFmpeg` will yield is a 24-bit\n\ +(8-bit per band) representation of each pixel (32-bit depths are\n\ +also supported by `FFmpeg`, but not by this extension presently).\n\ +So, the output of data is done with ``uint8`` as data type.\n\ +Output will be colored using the RGB standard, with each band\n\ +varying between 0 and 255, with zero meaning pure black and 255,\n\ +pure white (color).\n\ +"); + +PyTypeObject PyBobIoVideoReader_Type = { + PyObject_HEAD_INIT(0) + 0, /*ob_size*/ + s_videoreader_str, /*tp_name*/ + sizeof(PyBobIoVideoReaderObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyBobIoVideoReader_Delete, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, //(reprfunc)PyBobIoVideoReader_Repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, //&PyBobIoVideoReader_Sequence, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, //(reprfunc)PyBobIoVideoReader_Repr, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + s_videoreader_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, //PyBobIoVideoReader_Methods, /* tp_methods */ + 0, /* tp_members */ + 0, //PyBobIoVideoReader_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)PyBobIoVideoReader_Init, /* tp_init */ + 0, /* tp_alloc */ + PyBobIoVideoReader_New, /* tp_new */ +}; +/** + + .add_property("filename", make_function(&bob::io::VideoReader::filename, return_value_policy<copy_const_reference>()), "The full path to the file that will be decoded by this object") + + .add_property("height", &bob::io::VideoReader::height, "The height of each frame in the video (a multiple of 2)") + + .add_property("width", &bob::io::VideoReader::width, "The width of each frame in the video (a multiple of 2)") + + .add_property("number_of_frames", &bob::io::VideoReader::numberOfFrames, "The number of frames in this video file") + + .def("__len__", &bob::io::VideoReader::numberOfFrames) + + .add_property("duration", &bob::io::VideoReader::duration, "Total duration of this video file in microseconds (long)") + + .add_property("format_name", make_function(&bob::io::VideoReader::formatName, return_value_policy<copy_const_reference>()), "Short name of the format in which this video file was recorded in") + + .add_property("format_long_name", make_function(&bob::io::VideoReader::formatLongName, return_value_policy<copy_const_reference>()), "Verbose name of the format in which this video file was recorded in") + + .add_property("codec_name", make_function(&bob::io::VideoReader::codecName, return_value_policy<copy_const_reference>()), "Short name of the codec that will be used to decode this video file") + + .add_property("codec_long_name", make_function(&bob::io::VideoReader::codecLongName, return_value_policy<copy_const_reference>()), "Verbose name of the codec that will be used to decode this video file") + + .add_property("frame_rate", &bob::io::VideoReader::frameRate, "Video's announced frame rate (note there are video formats with variable frame rates)") + + .add_property("info", make_function(&bob::io::VideoReader::info, return_value_policy<copy_const_reference>()), "Informative string containing many details of this video and available ffmpeg bindings that will read it") + + .add_property("video_type", make_function(&bob::io::VideoReader::video_type, return_value_policy<copy_const_reference>()), "Typing information to load all of the file at once") + + .add_property("frame_type", make_function(&bob::io::VideoReader::frame_type, return_value_policy<copy_const_reference>()), "Typing information to load the file frame by frame.") + + .def("__load__", &videoreader_load, videoreader_load_overloads((arg("self"), arg("raise_on_error")=false), "Loads all of the video stream in a numpy ndarray organized in this way: (frames, color-bands, height, width). I'll dynamically allocate the output array and return it to you. The flag ``raise_on_error``, which is set to ``False`` by default influences the error reporting in case problems are found with the video file. If you set it to ``True``, we will report problems raising exceptions. If you either don't set it or set it to ``False``, we will truncate the file at the frame with problems and will not report anything. It is your task to verify if the number of frames returned matches the expected number of frames as reported by the property ``number_of_frames`` in this object.")) + + .def("__iter__", &bob::io::VideoReader::begin, with_custodian_and_ward_postcall<0,1>()) + + .def("__getitem__", &videoreader_getitem) + + .def("__getitem__", &videoreader_getslice) + +**/ + +#endif /* WITH_FFMPEG */ -- GitLab