From daced345d2880a772391d93b3bbea476caa686f1 Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.dos.anjos@gmail.com> Date: Tue, 12 Nov 2013 09:52:46 +0100 Subject: [PATCH] New VideoWriter bindings --- setup.py | 1 + xbob/io/__init__.py | 2 +- xbob/io/file.cpp | 38 ++- xbob/io/include/xbob.io/api.h | 19 +- xbob/io/main.cpp | 8 + xbob/io/test/test_file.py | 8 + xbob/io/test/test_video.py | 219 ++----------- xbob/io/test/test_video_codec.py | 189 +++++++++++ xbob/io/videoreader.cpp | 63 ++-- xbob/io/videowriter.cpp | 540 +++++++++++++++++++++++++++++++ 10 files changed, 853 insertions(+), 234 deletions(-) create mode 100644 xbob/io/test/test_video_codec.py create mode 100644 xbob/io/videowriter.cpp diff --git a/setup.py b/setup.py index 4238287..85e1496 100644 --- a/setup.py +++ b/setup.py @@ -114,6 +114,7 @@ setup( "xbob/io/bobskin.cpp", "xbob/io/file.cpp", "xbob/io/videoreader.cpp", + "xbob/io/videowriter.cpp", "xbob/io/main.cpp", ], define_macros=define_macros, diff --git a/xbob/io/__init__.py b/xbob/io/__init__.py index 294551a..41e67e0 100644 --- a/xbob/io/__init__.py +++ b/xbob/io/__init__.py @@ -1,4 +1,4 @@ -from ._library import __version__, __api_version__, File, VideoReader +from ._library import __version__, __api_version__, File, VideoReader, VideoWriter from . import _externals import os diff --git a/xbob/io/file.cpp b/xbob/io/file.cpp index 9d4ec03..38cf12f 100644 --- a/xbob/io/file.cpp +++ b/xbob/io/file.cpp @@ -238,23 +238,39 @@ static PyObject* PyBobIoFile_GetIndex (PyBobIoFileObject* self, Py_ssize_t i) { } -static PyObject* PyBobIoFile_GetSlice (PyBobIoFileObject* self, Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step, Py_ssize_t length) { +static PyObject* PyBobIoFile_GetSlice (PyBobIoFileObject* self, PySliceObject* slice) { - PyObject* retval = PyList_New(length); - if (!retval) return 0; + Py_ssize_t start, stop, step, slicelength; + if (PySlice_GetIndicesEx(slice, self->f->size(), + &start, &stop, &step, &slicelength) < 0) return 0; + //creates the return array const bob::core::array::typeinfo& info = self->f->type(); - npy_intp shape[NPY_MAXDIMS]; - for (int k=0; k<info.nd; ++k) shape[k] = info.shape[k]; - int type_num = PyBobIo_AsTypenum(info.dtype); if (type_num == NPY_NOTYPE) return 0; ///< failure + if (slicelength <= 0) return PyArray_SimpleNew(0, 0, type_num); + + npy_intp shape[NPY_MAXDIMS]; + shape[0] = slicelength; + for (int k=0; k<info.nd; ++k) shape[k+1] = info.shape[k]; + + PyObject* retval = PyArray_SimpleNew(info.nd+1, shape, type_num); + if (!retval) return 0; + Py_ssize_t counter = 0; for (auto i = start; (start<=stop)?i<stop:i>stop; i+=step) { - PyObject* item = PyArray_SimpleNew(info.nd, shape, type_num); + //get slice to fill + PyObject* islice = Py_BuildValue("n", counter++); + if (!islice) { + Py_DECREF(retval); + return 0; + } + + PyObject* item = PyObject_GetItem(retval, islice); + Py_DECREF(islice); if (!item) { Py_DECREF(retval); return 0; @@ -277,8 +293,6 @@ static PyObject* PyBobIoFile_GetSlice (PyBobIoFileObject* self, Py_ssize_t start return 0; } - PyList_SET_ITEM(retval, counter++, item); - } return retval; @@ -292,11 +306,7 @@ static PyObject* PyBobIoFile_GetItem (PyBobIoFileObject* self, PyObject* item) { return PyBobIoFile_GetIndex(self, i); } if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength; - if (PySlice_GetIndicesEx((PySliceObject*)item, self->f->size(), - &start, &stop, &step, &slicelength) < 0) return 0; - if (slicelength <= 0) return PyList_New(0); - return PyBobIoFile_GetSlice(self, start, stop, step, slicelength); + return PyBobIoFile_GetSlice(self, (PySliceObject*)item); } else { PyErr_Format(PyExc_TypeError, "File indices must be integers, not %s", diff --git a/xbob/io/include/xbob.io/api.h b/xbob/io/include/xbob.io/api.h index 6df961f..9185505 100644 --- a/xbob/io/include/xbob.io/api.h +++ b/xbob/io/include/xbob.io/api.h @@ -14,6 +14,7 @@ #if WITH_FFMPEG #include <bob/io/VideoReader.h> +#include <bob/io/VideoWriter.h> #endif /* WITH_FFMPEG */ #include <boost/preprocessor/stringize.hpp> @@ -103,13 +104,24 @@ typedef struct { #define PyBobIoVideoReaderIterator_Type_NUM 5 #define PyBobIoVideoReaderIterator_Type_TYPE PyTypeObject +typedef struct { + PyObject_HEAD + + /* Type-specific fields go here. */ + boost::shared_ptr<bob::io::VideoWriter> v; + +} PyBobIoVideoWriterObject; + +#define PyBobIoVideoWriter_Type_NUM 6 +#define PyBobIoVideoWriter_Type_TYPE PyTypeObject + #endif /* WITH_FFMPEG */ /* Total number of C API pointers */ #if WITH_FFMPEG -# define PyXbobIo_API_pointers 6 -#else # define PyXbobIo_API_pointers 7 +#else +# define PyXbobIo_API_pointers 8 #endif /* WITH_FFMPEG */ #ifdef XBOB_IO_MODULE @@ -144,6 +156,7 @@ typedef struct { extern PyBobIoVideoReader_Type_TYPE PyBobIoVideoReader_Type; extern PyBobIoVideoReaderIterator_Type_TYPE PyBobIoVideoReaderIterator_Type; + extern PyBobIoVideoWriter_Type_TYPE PyBobIoVideoWriter_Type; #endif /* WITH_FFMPEG */ #else @@ -203,6 +216,8 @@ typedef struct { # define PyBobIoVideoReader_Type (*(PyBobIoVideoReader_Type_TYPE *)PyXbobIo_API[PyBobIoVideoReader_Type_NUM]) # define PyBobIoVideoReaderIterator_Type (*(PyBobIoVideoReaderIterator_Type_TYPE *)PyXbobIo_API[PyBobIoVideoReaderIterator_Type_NUM]) + +# define PyBobIoVideoWriterIterator_Type (*(PyBobIoVideoWriterIterator_Type_TYPE *)PyXbobIo_API[PyBobIoVideoWriterIterator_Type_NUM]) #endif /* WITH_FFMPEG */ /** diff --git a/xbob/io/main.cpp b/xbob/io/main.cpp index 33cbc81..976d634 100644 --- a/xbob/io/main.cpp +++ b/xbob/io/main.cpp @@ -38,6 +38,9 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { PyBobIoVideoReaderIterator_Type.tp_new = PyType_GenericNew; if (PyType_Ready(&PyBobIoVideoReaderIterator_Type) < 0) return; + + PyBobIoVideoWriter_Type.tp_new = PyType_GenericNew; + if (PyType_Ready(&PyBobIoVideoWriter_Type) < 0) return; #endif /* WITH_FFMPEG */ PyObject* m = Py_InitModule3(BOOST_PP_STRINGIZE(XBOB_IO_MODULE_NAME), @@ -60,6 +63,9 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { Py_INCREF(&PyBobIoVideoReaderIterator_Type); PyModule_AddObject(m, "VideoReader.iter", (PyObject *)&PyBobIoVideoReaderIterator_Type); + + Py_INCREF(&PyBobIoVideoWriter_Type); + PyModule_AddObject(m, "VideoWriter", (PyObject *)&PyBobIoVideoWriter_Type); #endif /* WITH_FFMPEG */ static void* PyXbobIo_API[PyXbobIo_API_pointers]; @@ -96,6 +102,8 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { PyXbobIo_API[PyBobIoVideoReader_Type_NUM] = (void *)&PyBobIoVideoReader_Type; PyXbobIo_API[PyBobIoVideoReaderIterator_Type_NUM] = (void *)&PyBobIoVideoReaderIterator_Type; + + PyXbobIo_API[PyBobIoVideoWriter_Type_NUM] = (void *)&PyBobIoVideoWriter_Type; #endif /* WITH_FFMPEG */ /* imports the NumPy C-API */ diff --git a/xbob/io/test/test_file.py b/xbob/io/test/test_file.py index 018572a..f55e4c7 100644 --- a/xbob/io/test/test_file.py +++ b/xbob/io/test/test_file.py @@ -49,6 +49,14 @@ def test_indexing(): assert numpy.allclose(f[-1], objs[-1]) assert numpy.allclose(f[-2], objs[-2]) +def test_slicing_empty(): + + fname = testutils.datafile('matlab_2d.hdf5', __name__) + f = File(fname, 'r') + + objs = f[1:1] + assert objs.shape == tuple() + def test_slicing_0(): fname = testutils.datafile('matlab_2d.hdf5', __name__) diff --git a/xbob/io/test/test_video.py b/xbob/io/test/test_video.py index b53afc3..27d6416 100644 --- a/xbob/io/test/test_video.py +++ b/xbob/io/test/test_video.py @@ -8,12 +8,9 @@ """Runs some video tests """ -import os -import sys import numpy import nose.tools from . import utils as testutils -from ..utils import color_distortion, frameskip_detection, quality_degradation # These are some global parameters for the test. INPUT_VIDEO = testutils.datafile('test.mov', __name__) @@ -100,6 +97,22 @@ def test_video_reader_str(): assert repr(iv) assert str(iv) +@testutils.ffmpeg_found() +def test_can_iterate(): + + from .. import VideoReader + video = VideoReader(INPUT_VIDEO) + counter = 0 + for frame in video: + assert isinstance(frame, numpy.ndarray) + assert len(frame.shape) == 3 + assert frame.shape[0] == 3 #color-bands (RGB) + assert frame.shape[1] == 240 #height + assert frame.shape[2] == 320 #width + counter += 1 + + assert counter == len(video) #we have gone through all frames + @testutils.ffmpeg_found() def test_iteration(): @@ -130,6 +143,16 @@ def test_indexing(): assert numpy.allclose(f[len(f)-1], f[-1]) assert numpy.allclose(f[len(f)-2], f[-2]) +@testutils.ffmpeg_found() +def test_slicing_empty(): + + from .. import load, VideoReader + f = VideoReader(INPUT_VIDEO) + + objs = f[1:1] + assert objs.shape == tuple() + assert objs.dtype == numpy.uint8 + @testutils.ffmpeg_found() def test_slicing_0(): @@ -205,193 +228,3 @@ def test_can_use_array_interface(): for frame_id, frame in zip(range(array.shape[0]), iv.__iter__()): assert numpy.array_equal(array[frame_id,:,:,:], frame) - -@testutils.ffmpeg_found() -def test_can_iterate(): - - # This test shows how you can read image frames from a VideoReader created - # on the spot - from .. import VideoReader - video = VideoReader(INPUT_VIDEO) - counter = 0 - for frame in video: - assert isinstance(frame, numpy.ndarray) - assert len(frame.shape) == 3 - assert frame.shape[0] == 3 #color-bands (RGB) - assert frame.shape[1] == 240 #height - assert frame.shape[2] == 320 #width - counter += 1 - - assert counter == len(video) #we have gone through all frames - -@testutils.ffmpeg_found() -def check_format_codec(function, shape, framerate, format, codec, maxdist): - - length, height, width = shape - fname = testutils.temporary_filename(suffix='.%s' % format) - - try: - orig, framerate, encoded = function(shape, framerate, format, codec, fname) - reloaded = encoded.load() - - # test number of frames is correct - assert len(orig) == len(encoded), "original length %d != %d encoded" % (len(orig), len(encoded)) - assert len(orig) == len(reloaded), "original length %d != %d reloaded" % (len(orig), len(reloaded)) - - # test distortion patterns (quick sequential check) - dist = [] - for k, of in enumerate(orig): - dist.append(abs(of.astype('float64')-reloaded[k].astype('float64')).mean()) - assert max(dist) <= maxdist, "max(distortion) %g > %g allowed" % (max(dist), maxdist) - - # assert we can randomly access any frame (choose 3 at random) - for k in numpy.random.randint(length, size=(3,)): - rdist = abs(orig[k].astype('float64')-encoded[k].astype('float64')).mean() - assert rdist <= maxdist, "distortion(frame[%d]) %g > %g allowed" % (k, rdist, maxdist) - - # make sure that the encoded frame rate is not off by a big amount - assert abs(framerate - encoded.frame_rate) <= (1.0/length), "reloaded framerate %g differs from original %g by more than %g" % (encoded.frame_rate, framerate, 1.0/length) - - finally: - - if os.path.exists(fname): os.unlink(fname) - -def test_format_codecs(): - - length = 30 - width = 128 - height = 128 - framerate = 30. - shape = (length, height, width) - methods = dict( - frameskip = frameskip_detection, - color = color_distortion, - noise = quality_degradation, - ) - - # distortion patterns for specific codecs - distortions = dict( - # we require high standards by default - default = dict(frameskip=0.1, color=8.5, noise=45.), - - # high-quality encoders - zlib = dict(frameskip=0.0, color=0.0, noise=0.0), - ffv1 = dict(frameskip=0.05, color=9., noise=46.), - vp8 = dict(frameskip=0.3, color=9.0, noise=65.), - libvpx = dict(frameskip=0.3, color=9.0, noise=65.), - h264 = dict(frameskip=0.4, color=8.5, noise=50.), - libx264 = dict(frameskip=0.4, color=8.5, noise=50.), - theora = dict(frameskip=0.5, color=9.0, noise=70.), - libtheora = dict(frameskip=0.5, color=9.0, noise=70.), - mpeg4 = dict(frameskip=1.0, color=9.0, noise=55.), - - # older, but still good quality encoders - mjpeg = dict(frameskip=1.2, color=8.5, noise=50.), - mpegvideo = dict(frameskip=1.3, color=8.5, noise=55.), - mpeg2video = dict(frameskip=1.3, color=8.5, noise=55.), - mpeg1video = dict(frameskip=1.4, color=9.0, noise=50.), - - # low quality encoders - avoid using - available for compatibility - wmv2 = dict(frameskip=3.0, color=10., noise=50.), - wmv1 = dict(frameskip=2.5, color=10., noise=50.), - msmpeg4 = dict(frameskip=5., color=10., noise=50.), - msmpeg4v2 = dict(frameskip=5., color=10., noise=50.), - ) - - # some exceptions - if testutils.ffmpeg_version_lessthan('0.6'): - distortions['ffv1']['frameskip'] = 0.55 - distortions['mpeg1video']['frameskip'] = 1.5 - distortions['mpegvideo']['color'] = 9.0 - distortions['mpegvideo']['frameskip'] = 1.4 - distortions['mpeg2video']['color'] = 9.0 - distortions['mpeg2video']['frameskip'] = 1.4 - - from .._externals import supported_videowriter_formats - SUPPORTED = supported_videowriter_formats() - for format in SUPPORTED: - for codec in SUPPORTED[format]['supported_codecs']: - for method in methods: - check_format_codec.description = "%s.test_%s_format_%s_codec_%s" % (__name__, method, format, codec) - distortion = distortions.get(codec, distortions['default'])[method] - yield check_format_codec, methods[method], shape, framerate, format, codec, distortion - -@testutils.ffmpeg_found() -def check_user_video(format, codec, maxdist): - - from .. import VideoReader, VideoWriter - fname = testutils.temporary_filename(suffix='.%s' % format) - MAXLENTH = 10 #use only the first 10 frames - - try: - - orig_vreader = VideoReader(INPUT_VIDEO) - orig = orig_vreader[:MAXLENTH] - (olength, _, oheight, owidth) = orig.shape - assert len(orig) == MAXLENTH, "original length %d != %d MAXLENTH" % (len(orig), MAXLENTH) - - # encode the input video using the format and codec provided by the user - outv = VideoWriter(fname, oheight, owidth, orig_vreader.frame_rate, - codec=codec, format=format) - for k in orig: outv.append(k) - outv.close() - - # reload from saved file - encoded = VideoReader(fname) - reloaded = encoded.load() - - # test number of frames is correct - assert len(orig) == len(encoded), "original length %d != %d encoded" % (len(orig), len(encoded)) - assert len(orig) == len(reloaded), "original length %d != %d reloaded" % (len(orig), len(reloaded)) - - # test distortion patterns (quick sequential check) - dist = [] - for k, of in enumerate(orig): - dist.append(abs(of.astype('float64')-reloaded[k].astype('float64')).mean()) - assert max(dist) <= maxdist, "max(distortion) %g > %g allowed" % (max(dist), maxdist) - - # make sure that the encoded frame rate is not off by a big amount - assert abs(orig_vreader.frame_rate - encoded.frame_rate) <= (1.0/MAXLENTH), "original video framerate %g differs from encoded %g by more than %g" % (encoded.frame_rate, framerate, 1.0/MAXLENTH) - - finally: - - if os.path.exists(fname): os.unlink(fname) - -def test_user_video(): - - # distortion patterns for specific codecs - distortions = dict( - # we require high standards by default - default = 1.5, - - # high-quality encoders - zlib = 0.0, - ffv1 = 1.7, - vp8 = 2.7, - libvpx = 2.7, - h264 = 2.5, - libx264 = 2.5, - theora = 2.0, - libtheora = 2.0, - mpeg4 = 2.3, - - # older, but still good quality encoders - mjpeg = 1.8, - mpegvideo = 2.3, - mpeg2video = 2.3, - mpeg1video = 2.3, - - # low quality encoders - avoid using - available for compatibility - wmv2 = 2.3, - wmv1 = 2.3, - msmpeg4 = 2.3, - msmpeg4v2 = 2.3, - ) - - from .._externals 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) - distortion = distortions.get(codec, distortions['default']) - yield check_user_video, format, codec, distortion diff --git a/xbob/io/test/test_video_codec.py b/xbob/io/test/test_video_codec.py new file mode 100644 index 0000000..6578c57 --- /dev/null +++ b/xbob/io/test/test_video_codec.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Andre Anjos <andre.anjos@idiap.ch> +# Tue 12 Nov 09:25:56 2013 + +"""Runs quality tests on video codecs supported +""" + +import os +import numpy +import nose.tools +from . import utils as testutils +from ..utils import color_distortion, frameskip_detection, quality_degradation + +# These are some global parameters for the test. +INPUT_VIDEO = testutils.datafile('test.mov', __name__) + +@testutils.ffmpeg_found() +def check_format_codec(function, shape, framerate, format, codec, maxdist): + + length, height, width = shape + fname = testutils.temporary_filename(suffix='.%s' % format) + + try: + orig, framerate, encoded = function(shape, framerate, format, codec, fname) + reloaded = encoded.load() + + # test number of frames is correct + assert len(orig) == len(encoded), "original length %d != %d encoded" % (len(orig), len(encoded)) + assert len(orig) == len(reloaded), "original length %d != %d reloaded" % (len(orig), len(reloaded)) + + # test distortion patterns (quick sequential check) + dist = [] + for k, of in enumerate(orig): + dist.append(abs(of.astype('float64')-reloaded[k].astype('float64')).mean()) + assert max(dist) <= maxdist, "max(distortion) %g > %g allowed" % (max(dist), maxdist) + + # assert we can randomly access any frame (choose 3 at random) + for k in numpy.random.randint(length, size=(3,)): + rdist = abs(orig[k].astype('float64')-encoded[k].astype('float64')).mean() + assert rdist <= maxdist, "distortion(frame[%d]) %g > %g allowed" % (k, rdist, maxdist) + + # make sure that the encoded frame rate is not off by a big amount + assert abs(framerate - encoded.frame_rate) <= (1.0/length), "reloaded framerate %g differs from original %g by more than %g" % (encoded.frame_rate, framerate, 1.0/length) + + finally: + + if os.path.exists(fname): os.unlink(fname) + +def test_format_codecs(): + + length = 30 + width = 128 + height = 128 + framerate = 30. + shape = (length, height, width) + methods = dict( + frameskip = frameskip_detection, + color = color_distortion, + noise = quality_degradation, + ) + + # distortion patterns for specific codecs + distortions = dict( + # we require high standards by default + default = dict(frameskip=0.1, color=8.5, noise=45.), + + # high-quality encoders + zlib = dict(frameskip=0.0, color=0.0, noise=0.0), + ffv1 = dict(frameskip=0.05, color=9., noise=46.), + vp8 = dict(frameskip=0.3, color=9.0, noise=65.), + libvpx = dict(frameskip=0.3, color=9.0, noise=65.), + h264 = dict(frameskip=0.4, color=8.5, noise=50.), + libx264 = dict(frameskip=0.4, color=8.5, noise=50.), + theora = dict(frameskip=0.5, color=9.0, noise=70.), + libtheora = dict(frameskip=0.5, color=9.0, noise=70.), + mpeg4 = dict(frameskip=1.0, color=9.0, noise=55.), + + # older, but still good quality encoders + mjpeg = dict(frameskip=1.2, color=8.5, noise=50.), + mpegvideo = dict(frameskip=1.3, color=8.5, noise=55.), + mpeg2video = dict(frameskip=1.3, color=8.5, noise=55.), + mpeg1video = dict(frameskip=1.4, color=9.0, noise=50.), + + # low quality encoders - avoid using - available for compatibility + wmv2 = dict(frameskip=3.0, color=10., noise=50.), + wmv1 = dict(frameskip=2.5, color=10., noise=50.), + msmpeg4 = dict(frameskip=5., color=10., noise=50.), + msmpeg4v2 = dict(frameskip=5., color=10., noise=50.), + ) + + # some exceptions + if testutils.ffmpeg_version_lessthan('0.6'): + distortions['ffv1']['frameskip'] = 0.55 + distortions['mpeg1video']['frameskip'] = 1.5 + distortions['mpegvideo']['color'] = 9.0 + distortions['mpegvideo']['frameskip'] = 1.4 + distortions['mpeg2video']['color'] = 9.0 + distortions['mpeg2video']['frameskip'] = 1.4 + + from .._externals import supported_videowriter_formats + SUPPORTED = supported_videowriter_formats() + for format in SUPPORTED: + for codec in SUPPORTED[format]['supported_codecs']: + for method in methods: + check_format_codec.description = "%s.test_%s_format_%s_codec_%s" % (__name__, method, format, codec) + distortion = distortions.get(codec, distortions['default'])[method] + yield check_format_codec, methods[method], shape, framerate, format, codec, distortion + +@testutils.ffmpeg_found() +def check_user_video(format, codec, maxdist): + + from .. import VideoReader, VideoWriter + fname = testutils.temporary_filename(suffix='.%s' % format) + MAXLENTH = 10 #use only the first 10 frames + + try: + + orig_vreader = VideoReader(INPUT_VIDEO) + orig = orig_vreader[:MAXLENTH] + (olength, _, oheight, owidth) = orig.shape + assert len(orig) == MAXLENTH, "original length %d != %d MAXLENTH" % (len(orig), MAXLENTH) + + # encode the input video using the format and codec provided by the user + outv = VideoWriter(fname, oheight, owidth, orig_vreader.frame_rate, + codec=codec, format=format) + for k in orig: outv.append(k) + outv.close() + + # reload from saved file + encoded = VideoReader(fname) + reloaded = encoded.load() + + # test number of frames is correct + assert len(orig) == len(encoded), "original length %d != %d encoded" % (len(orig), len(encoded)) + assert len(orig) == len(reloaded), "original length %d != %d reloaded" % (len(orig), len(reloaded)) + + # test distortion patterns (quick sequential check) + dist = [] + for k, of in enumerate(orig): + dist.append(abs(of.astype('float64')-reloaded[k].astype('float64')).mean()) + assert max(dist) <= maxdist, "max(distortion) %g > %g allowed" % (max(dist), maxdist) + + # make sure that the encoded frame rate is not off by a big amount + assert abs(orig_vreader.frame_rate - encoded.frame_rate) <= (1.0/MAXLENTH), "original video framerate %g differs from encoded %g by more than %g" % (encoded.frame_rate, framerate, 1.0/MAXLENTH) + + finally: + + if os.path.exists(fname): os.unlink(fname) + +@nose.tools.nottest +def test_user_video(): + + # distortion patterns for specific codecs + distortions = dict( + # we require high standards by default + default = 1.5, + + # high-quality encoders + zlib = 0.0, + ffv1 = 1.7, + vp8 = 2.7, + libvpx = 2.7, + h264 = 2.5, + libx264 = 2.5, + theora = 2.0, + libtheora = 2.0, + mpeg4 = 2.3, + + # older, but still good quality encoders + mjpeg = 1.8, + mpegvideo = 2.3, + mpeg2video = 2.3, + mpeg1video = 2.3, + + # low quality encoders - avoid using - available for compatibility + wmv2 = 2.3, + wmv1 = 2.3, + msmpeg4 = 2.3, + msmpeg4v2 = 2.3, + ) + + from .._externals 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) + distortion = distortions.get(codec, distortions['default']) + yield check_user_video, format, codec, distortion diff --git a/xbob/io/videoreader.cpp b/xbob/io/videoreader.cpp index c20f5a7..54ff86c 100644 --- a/xbob/io/videoreader.cpp +++ b/xbob/io/videoreader.cpp @@ -325,7 +325,7 @@ static void Check_Interrupt() { } } -static PyObject* PyBobIoFile_Load(PyBobIoVideoReaderObject* self, PyObject *args, PyObject* kwds) { +static PyObject* PyBobIoVideoReader_Load(PyBobIoVideoReaderObject* self, PyObject *args, PyObject* kwds) { /* Parses input arguments in a single shot */ static const char* const_kwlist[] = {"raise_on_error", 0}; @@ -405,7 +405,7 @@ object.\n\ static PyMethodDef PyBobIoVideoReader_Methods[] = { { s_load_str, - (PyCFunction)PyBobIoFile_Load, + (PyCFunction)PyBobIoVideoReader_Load, METH_VARARGS|METH_KEYWORDS, s_load_doc, }, @@ -453,29 +453,51 @@ static PyObject* PyBobIoVideoReader_GetIndex (PyBobIoVideoReaderObject* self, Py } -static PyObject* PyBobIoVideoReader_GetSlice (PyBobIoVideoReaderObject* self, Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step, Py_ssize_t length) { +static PyObject* PyBobIoVideoReader_GetSlice (PyBobIoVideoReaderObject* self, PySliceObject* slice) { - PyObject* retval = PyList_New(length); - if (!retval) return 0; + Py_ssize_t start, stop, step, slicelength; + if (PySlice_GetIndicesEx(slice, self->v->numberOfFrames(), + &start, &stop, &step, &slicelength) < 0) return 0; + //creates the return array const bob::core::array::typeinfo& info = self->v->frame_type(); - npy_intp shape[NPY_MAXDIMS]; - for (int k=0; k<info.nd; ++k) shape[k] = info.shape[k]; - int type_num = PyBobIo_AsTypenum(info.dtype); if (type_num == NPY_NOTYPE) return 0; ///< failure - Py_ssize_t counter = 0; + if (slicelength <= 0) return PyArray_SimpleNew(0, 0, type_num); + + npy_intp shape[NPY_MAXDIMS]; + shape[0] = slicelength; + for (int k=0; k<info.nd; ++k) shape[k+1] = info.shape[k]; + + PyObject* retval = PyArray_SimpleNew(info.nd+1, shape, type_num); + if (!retval) return 0; + + Py_ssize_t counter; Py_ssize_t lo, hi, st; auto it = self->v->begin(); - - if (start <= stop) lo = start, hi = stop, st = step, it += lo; - else lo = stop, hi = start, st = -step, it += lo + (hi-lo)%st; + if (start <= stop) { + lo = start, hi = stop, st = step; + it += lo, counter = 0; + } + else { + lo = stop, hi = start, st = -step; + it += lo + (hi-lo)%st, counter = slicelength - 1; + } for (auto i=lo; i<hi; i+=st) { - PyObject* item = PyArray_SimpleNew(info.nd, shape, type_num); + //get slice to fill + PyObject* islice = Py_BuildValue("n", counter); + counter = (st == -step)? counter-1 : counter+1; + if (!islice) { + Py_DECREF(retval); + return 0; + } + + PyObject* item = PyObject_GetItem(retval, islice); + Py_DECREF(islice); if (!item) { Py_DECREF(retval); return 0; @@ -499,12 +521,8 @@ static PyObject* PyBobIoVideoReader_GetSlice (PyBobIoVideoReaderObject* self, Py return 0; } - PyList_SET_ITEM(retval, counter++, item); - } - if (st == -step) PyList_Reverse(retval); - return retval; } @@ -516,11 +534,7 @@ static PyObject* PyBobIoVideoReader_GetItem (PyBobIoVideoReaderObject* self, PyO return PyBobIoVideoReader_GetIndex(self, i); } if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength; - if (PySlice_GetIndicesEx((PySliceObject*)item, self->v->numberOfFrames(), - &start, &stop, &step, &slicelength) < 0) return 0; - if (slicelength <= 0) return PyList_New(0); - return PyBobIoVideoReader_GetSlice(self, start, stop, step, slicelength); + return PyBobIoVideoReader_GetSlice(self, (PySliceObject*)item); } else { PyErr_Format(PyExc_TypeError, "VideoReader indices must be integers, not %.200s", @@ -559,7 +573,8 @@ static PyObject* PyBobIoVideoReaderIterator_Iter (PyBobIoVideoReaderIteratorObje static PyObject* PyBobIoVideoReaderIterator_Next (PyBobIoVideoReaderIteratorObject* self) { - if (*self->iter == self->pyreader->v->end()) { + if ((*self->iter == self->pyreader->v->end()) || + (self->iter->cur() == self->pyreader->v->numberOfFrames())) { self->iter->reset(); self->iter.reset(); Py_XDECREF((PyObject*)self->pyreader); @@ -582,7 +597,7 @@ static PyObject* PyBobIoVideoReaderIterator_Next (PyBobIoVideoReaderIteratorObje self->iter->read(skin); } catch (std::exception& e) { - if (!PyErr_Occurred()) PyErr_Format(PyExc_RuntimeError, "caught std::exception while reading frame %" PY_FORMAT_SIZE_T "d from file `%s': %s", self->iter->cur(), self->pyreader->v->filename().c_str(), e.what()); + if (!PyErr_Occurred()) PyErr_Format(PyExc_RuntimeError, "caught std::exception while reading frame #%" PY_FORMAT_SIZE_T "d from file `%s': %s", self->iter->cur(), self->pyreader->v->filename().c_str(), e.what()); Py_DECREF(retval); return 0; } diff --git a/xbob/io/videowriter.cpp b/xbob/io/videowriter.cpp new file mode 100644 index 0000000..8f343ec --- /dev/null +++ b/xbob/io/videowriter.cpp @@ -0,0 +1,540 @@ +/** + * @author Andre Anjos <andre.anjos@idiap.ch> + * @date Wed 6 Nov 21:44:34 2013 + * + * @brief Bindings to bob::io::VideoWriter + */ + +#define XBOB_IO_MODULE +#include <xbob.io/api.h> + +#if WITH_FFMPEG +#include <boost/make_shared.hpp> +#include <numpy/arrayobject.h> +#include <blitz.array/cppapi.h> +#include <stdexcept> +#include <bobskin.h> + +#define VIDEOWRITER_NAME VideoWriter +PyDoc_STRVAR(s_videowriter_str, BOOST_PP_STRINGIZE(XBOB_IO_MODULE_PREFIX) "." BOOST_PP_STRINGIZE(VIDEOWRITER_NAME)); + +/* How to create a new PyBobIoVideoWriterObject */ +static PyObject* PyBobIoVideoWriter_New(PyTypeObject* type, PyObject*, PyObject*) { + + /* Allocates the python object itself */ + PyBobIoVideoWriterObject* self = (PyBobIoVideoWriterObject*)type->tp_alloc(type, 0); + + self->v.reset(); + + return reinterpret_cast<PyObject*>(self); +} + +static void PyBobIoVideoWriter_Delete (PyBobIoVideoWriterObject* o) { + + o->v.reset(); + o->ob_type->tp_free((PyObject*)o); + +} + +/* The __init__(self) method */ +static int PyBobIoVideoWriter_Init(PyBobIoVideoWriterObject* self, + PyObject *args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = { + "filename", "height", "width", //mandatory + "framerate", "bitrate", "gop", "codec", "format", "check", //optional + 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + char* filename = 0; + Py_ssize_t height = 0; + Py_ssize_t width = 0; + + double framerate = 25.; + double bitrate = 1500000.; + Py_ssize_t gop = 12; + char* codec = 0; + char* format = 0; + PyObject* pycheck = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "snn|ddnssO", kwlist, + &filename, &height, &width, &framerate, &bitrate, &gop, &codec, + &format, &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::VideoWriter>(filename, height, width, + framerate, bitrate, gop, codec, format, check); + } + catch (std::exception& e) { + PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for writing: %s", filename, e.what()); + return -1; + } + catch (...) { + PyErr_Format(PyExc_RuntimeError, "cannot open video file `%s' for writing: unknown exception caught", filename); + return -1; + } + + return 0; ///< SUCCESS +} + +PyDoc_STRVAR(s_videowriter_doc, +"VideoWriter(filename, height, width, [framerate=25., [bitrate=1500000., [gop=12, [codec='', [format='', [check=True]) -> new bob::io::VideoWriter\n\ +\n\ +Use this object to write frames to 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\ +height\n\ + [int] The height of the video (must be a multiple of 2)\n\ +\n\ +width\n\ + [int] The width of the video (must be a multiple of 2)\n\ +\n\ +framerate\n\ + [float, optional] The number of frames per second\n\ +\n\ +bitrate\n\ + [float, optional] The estimated bitrate of the output video\n\ +\n\ +gop\n\ + [int, optional] Group-of-Pictures (emit one intra frame\n\ + every `gop' frames at most).\n\ +\n\ +codec\n\ + [str, optional] If you must, specify a valid FFmpeg codec\n\ + name here and that will be used to encode the video stream\n\ + on the output file.\n\ +\n\ +format\n\ + [str, optional] If you must, specify a valid FFmpeg output\n\ + format name and that will be used to encode the video on the\n\ + output file. Leave it empty to guess from the filename extension.\n\ +\n\ +check\n\ + [bool, optional] The video will be created if the combination\n\ + of format and codec are known to work and have been tested,\n\ + otherwise an exception is raised. If you set this parameter to\n\ + ``False``, though, we will ignore this check.\n\ +\n\ +VideoWriter objects can write data to 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. Videos are objects composed\n\ +of RGB colored frames. Each frame inserted should be a 3D\n\ +:py:class:`numpy.ndarray` composed of unsigned integers of 8 bits.\n\ +Each frame should have a shape equivalent to\n\ +``(plane, height, width)``.\n\ +"); + +PyObject* PyBobIoVideoWriter_Filename(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("s", self->v->filename().c_str()); +} + +PyDoc_STRVAR(s_filename_str, "filename"); +PyDoc_STRVAR(s_filename_doc, +"[str] The full path to the file that will be decoded by this object"); + +PyObject* PyBobIoVideoWriter_Height(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("n", self->v->height()); +} + +PyDoc_STRVAR(s_height_str, "height"); +PyDoc_STRVAR(s_height_doc, +"[int] The height of each frame in the video (a multiple of 2)"); + +PyObject* PyBobIoVideoWriter_Width(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("n", self->v->width()); +} + +PyDoc_STRVAR(s_width_str, "width"); +PyDoc_STRVAR(s_width_doc, +"[int] The width of each frame in the video (a multiple of 2)"); + +Py_ssize_t PyBobIoVideoWriter_Len(PyBobIoVideoWriterObject* self) { + return self->v->numberOfFrames(); +} + +PyDoc_STRVAR(s_number_of_frames_str, "number_of_frames"); +PyDoc_STRVAR(s_number_of_frames_doc, +"[int] The number of frames in this video file"); + +PyObject* PyBobIoVideoWriter_Duration(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("n", self->v->duration()); +} + +PyDoc_STRVAR(s_duration_str, "duration"); +PyDoc_STRVAR(s_duration_doc, +"[int] Total duration of this video file in microseconds (long)"); + +PyObject* PyBobIoVideoWriter_FormatName(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("s", self->v->formatName().c_str()); +} + +PyDoc_STRVAR(s_format_name_str, "format_name"); +PyDoc_STRVAR(s_format_name_doc, +"[str] Short name of the format in which this video file was recorded in"); + +PyObject* PyBobIoVideoWriter_FormatLongName(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("s", self->v->formatLongName().c_str()); +} + +PyDoc_STRVAR(s_format_long_name_str, "format_long_name"); +PyDoc_STRVAR(s_format_long_name_doc, +"[str] Full name of the format in which this video file was recorded in"); + +PyObject* PyBobIoVideoWriter_CodecName(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("s", self->v->codecName().c_str()); +} + +PyDoc_STRVAR(s_codec_name_str, "codec_name"); +PyDoc_STRVAR(s_codec_name_doc, +"[str] Short name of the codec in which this video file was recorded in"); + +PyObject* PyBobIoVideoWriter_CodecLongName(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("s", self->v->codecLongName().c_str()); +} + +PyDoc_STRVAR(s_codec_long_name_str, "codec_long_name"); +PyDoc_STRVAR(s_codec_long_name_doc, +"[str] Full name of the codec in which this video file was recorded in"); + +PyObject* PyBobIoVideoWriter_FrameRate(PyBobIoVideoWriterObject* self) { + return PyFloat_FromDouble(self->v->frameRate()); +} + +PyDoc_STRVAR(s_frame_rate_str, "frame_rate"); +PyDoc_STRVAR(s_frame_rate_doc, +"[float] Video's announced frame rate (note there are video formats\n\ +with variable frame rates)"); + +PyObject* PyBobIoVideoWriter_BitRate(PyBobIoVideoWriterObject* self) { + return PyFloat_FromDouble(self->v->bitRate()); +} + +PyDoc_STRVAR(s_bit_rate_str, "bit_rate"); +PyDoc_STRVAR(s_bit_rate_doc, +"[float] The indicative bit rate for this video file, given as a\n\ +hint to `FFmpeg` (compression levels are subject to the picture\n\ +textures)"); + +PyObject* PyBobIoVideoWriter_GOP(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("n", self->v->gop()); +} + +PyDoc_STRVAR(s_gop_str, "gop"); +PyDoc_STRVAR(s_gop_doc, +"[int] Group of pictures setting (see the `Wikipedia entry\n\ +<http://en.wikipedia.org/wiki/Group_of_pictures>`_ for details\n\ +on this setting)"); + +PyObject* PyBobIoVideoWriter_VideoType(PyBobIoVideoWriterObject* self) { + return PyBobIo_TypeInfoAsTuple(self->v->video_type()); +} + +PyDoc_STRVAR(s_video_type_str, "video_type"); +PyDoc_STRVAR(s_video_type_doc, +"[tuple] Typing information to load all of the file at once"); + +PyObject* PyBobIoVideoWriter_FrameType(PyBobIoVideoWriterObject* self) { + return PyBobIo_TypeInfoAsTuple(self->v->frame_type()); +} + +PyDoc_STRVAR(s_frame_type_str, "frame_type"); +PyDoc_STRVAR(s_frame_type_doc, +"[tuple] Typing information to load each frame separatedly"); + +static PyObject* PyBobIoVideoWriter_Print(PyBobIoVideoWriterObject* self) { + return Py_BuildValue("s", self->v->info().c_str()); +} + +PyDoc_STRVAR(s_info_str, "info"); +PyDoc_STRVAR(s_info_doc, +"[str] A string with lots of video information (same as ``str(x)``)"); + +static PyObject* PyBobIoVideoWriter_IsOpened(PyBobIoVideoWriterObject* self) { + if (self->v->is_opened()) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(s_is_opened_str, "is_opened"); +PyDoc_STRVAR(s_is_opened_doc, +"[bool] A flag, indicating if the video is still opened for writing\n\ +(or has already been closed by the user using ``close()``)"); + +static PyGetSetDef PyBobIoVideoWriter_getseters[] = { + { + s_filename_str, + (getter)PyBobIoVideoWriter_Filename, + 0, + s_filename_doc, + 0, + }, + { + s_height_str, + (getter)PyBobIoVideoWriter_Height, + 0, + s_height_doc, + 0, + }, + { + s_width_str, + (getter)PyBobIoVideoWriter_Width, + 0, + s_width_doc, + 0, + }, + { + s_number_of_frames_str, + (getter)PyBobIoVideoWriter_Len, + 0, + s_number_of_frames_doc, + 0, + }, + { + s_duration_str, + (getter)PyBobIoVideoWriter_Duration, + 0, + s_duration_doc, + 0, + }, + { + s_format_name_str, + (getter)PyBobIoVideoWriter_FormatName, + 0, + s_format_name_doc, + 0, + }, + { + s_format_long_name_str, + (getter)PyBobIoVideoWriter_FormatLongName, + 0, + s_format_long_name_doc, + 0, + }, + { + s_codec_name_str, + (getter)PyBobIoVideoWriter_CodecName, + 0, + s_codec_name_doc, + 0, + }, + { + s_codec_long_name_str, + (getter)PyBobIoVideoWriter_CodecLongName, + 0, + s_codec_long_name_doc, + 0, + }, + { + s_frame_rate_str, + (getter)PyBobIoVideoWriter_FrameRate, + 0, + s_frame_rate_doc, + 0, + }, + { + s_bit_rate_str, + (getter)PyBobIoVideoWriter_BitRate, + 0, + s_bit_rate_doc, + 0, + }, + { + s_gop_str, + (getter)PyBobIoVideoWriter_GOP, + 0, + s_gop_doc, + 0, + }, + { + s_video_type_str, + (getter)PyBobIoVideoWriter_VideoType, + 0, + s_video_type_doc, + 0, + }, + { + s_frame_type_str, + (getter)PyBobIoVideoWriter_FrameType, + 0, + s_frame_type_doc, + 0, + }, + { + s_info_str, + (getter)PyBobIoVideoWriter_Print, + 0, + s_info_doc, + 0, + }, + { + s_is_opened_str, + (getter)PyBobIoVideoWriter_IsOpened, + 0, + s_is_opened_doc, + 0, + }, + {0} /* Sentinel */ +}; + +static PyObject* PyBobIoVideoWriter_Repr(PyBobIoVideoWriterObject* self) { + return +# if PY_VERSION_HEX >= 0x03000000 + PyUnicode_FromFormat +# else + PyString_FromFormat +# endif + ("%s(filename='%s', height=%" PY_FORMAT_SIZE_T "d, width=%" PY_FORMAT_SIZE_T "d, framerate=%g, bitrate=%g, gop=%" PY_FORMAT_SIZE_T "d, codec='%s', format='%s')", s_videowriter_str, self->v->filename().c_str(), self->v->height(), self->v->width(), self->v->frameRate(), self->v->bitRate(), self->v->gop(), self->v->codecName().c_str(), self->v->formatName().c_str()); +} + +static PyObject* PyBobIoVideoWriter_Append(PyBobIoVideoWriterObject* self, PyObject *args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = {"frame", 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + PyBlitzArrayObject* frame = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&", kwlist, &PyBlitzArray_Converter, &frame)) return 0; + + if (frame->ndim != 3 && frame->ndim != 4) { + PyErr_Format(PyExc_ValueError, "input array should have 3 or 4 dimensions, but you passed an array with %" PY_FORMAT_SIZE_T "d dimensions", frame->ndim); + return 0; + } + + if (frame->type_num != NPY_UINT8) { + PyErr_Format(PyExc_TypeError, "input array should have dtype `uint8', but you passed an array with dtype == `%s'", PyBlitzArray_TypenumAsString(frame->type_num)); + return 0; + } + + try { + if (frame->ndim == 3) { + self->v->append(*PyBlitzArrayCxx_AsBlitz<uint8_t,3>(frame)); + } + else { + self->v->append(*PyBlitzArrayCxx_AsBlitz<uint8_t,4>(frame)); + } + } + catch (std::exception& e) { + if (!PyErr_Occurred()) PyErr_Format(PyExc_RuntimeError, "caught std::exception while writing frame #%" PY_FORMAT_SIZE_T "d to file `%s': %s", self->v->numberOfFrames(), self->v->filename().c_str(), e.what()); + return 0; + } + catch (...) { + if (!PyErr_Occurred()) PyErr_Format(PyExc_RuntimeError, "caught unknown exception while writing frame #%" PY_FORMAT_SIZE_T "d to file `%s'", self->v->numberOfFrames(), self->v->filename().c_str()); + return 0; + } + + Py_RETURN_NONE; + +} + +PyDoc_STRVAR(s_append_str, "append"); +PyDoc_STRVAR(s_append_doc, +"x.append(frame) -> None\n\ +\n\ +Writes a new frame or set of frames to the file.\n\ +\n\ +The frame should be setup as a array with 3 dimensions organized\n\ +in this way (RGB color-bands, height, width). Sets of frames should\n\ +be setup as a 4D array in this way: (frame-number, RGB color-bands,\n\ +height, width). Arrays should contain only unsigned integers of 8\n\ +bits.\n\ +\n\ +.. note::\n\ + At present time we only support arrays that have C-style storages\n\ + (if you pass reversed arrays or arrays with Fortran-style storage,\n\ + the result is undefined).\n\ +\n\ +"); + +static PyObject* PyBobIoVideoWriter_Close(PyBobIoVideoWriterObject* self) { + self->v->close(); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(s_close_str, "close"); +PyDoc_STRVAR(s_close_doc, +"x.close() -> None\n\ +\n\ +Closes the current video stream and forces writing the trailer.\n\ +After this point the video is finalized and cannot be written to\n\ +anymore.\n\ +"); + +static PyMethodDef PyBobIoVideoWriter_Methods[] = { + { + s_append_str, + (PyCFunction)PyBobIoVideoWriter_Append, + METH_VARARGS|METH_KEYWORDS, + s_append_doc, + }, + { + s_close_str, + (PyCFunction)PyBobIoVideoWriter_Close, + METH_NOARGS, + s_close_doc, + }, + {0} /* Sentinel */ +}; + +static PyMappingMethods PyBobIoVideoWriter_Mapping = { + (lenfunc)PyBobIoVideoWriter_Len, //mp_lenght + 0, /* (binaryfunc)PyBobIoVideoWriter_GetItem, //mp_subscript */ + 0 /* (objobjargproc)PyBobIoVideoWriter_SetItem //mp_ass_subscript */ +}; + +PyTypeObject PyBobIoVideoWriter_Type = { + PyObject_HEAD_INIT(0) + 0, /*ob_size*/ + s_videowriter_str, /*tp_name*/ + sizeof(PyBobIoVideoWriterObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyBobIoVideoWriter_Delete, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)PyBobIoVideoWriter_Repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + &PyBobIoVideoWriter_Mapping, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + (reprfunc)PyBobIoVideoWriter_Print, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + s_videowriter_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyBobIoVideoWriter_Methods, /* tp_methods */ + 0, /* tp_members */ + PyBobIoVideoWriter_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)PyBobIoVideoWriter_Init, /* tp_init */ + 0, /* tp_alloc */ + PyBobIoVideoWriter_New, /* tp_new */ +}; + +#endif /* WITH_FFMPEG */ -- GitLab