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