Skip to content

Animation conversion to HTML5 video #4785

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ Support for URL string arguments to ``imread``
The ``imread`` function now accepts URL strings that point to remote PNG
files. This circumvents the generation of a HTTPResponse object directly.

Display hook for animations in the IPython notebook
---------------------------------------------------

`matplotlib.animation.Animation` instances gained a ``_repr_html_`` method
to support inline display of animations in the notebook. The method used
to display is controlled by the ``animation.html`` rc parameter, which
currently supports values of ``none`` and ``html5``. ``none`` is the
default, performing no display. ``html5`` converts the animation to an
h264 encoded video, which is embedded directly in the notebook.

Users not wishing to use the ``_repr_html_`` display hook can also manually
call the `to_html5_video` method to get the HTML and display using
IPython's ``HTML`` display class::

from IPython.display import HTML
HTML(anim.to_html5_video())


.. _whats-new-1-4:

Expand Down
57 changes: 56 additions & 1 deletion lib/matplotlib/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
from matplotlib.externals import six
from matplotlib.externals.six.moves import xrange, zip

import os
import platform
import sys
import itertools
import base64
import contextlib
import tempfile
from matplotlib.cbook import iterable, is_string_like
from matplotlib.compat import subprocess
from matplotlib import verbose
Expand Down Expand Up @@ -383,7 +386,6 @@ def cleanup(self):

# Delete temporary files
if self.clear_temp:
import os
verbose.report(
'MovieWriter: clearing temporary fnames=%s' %
str(self._temp_names),
Expand Down Expand Up @@ -885,6 +887,59 @@ def _end_redraw(self, evt):
self._resize_id = self._fig.canvas.mpl_connect('resize_event',
self._handle_resize)

def to_html5_video(self):
r'''Returns animation as an HTML5 video tag.

This saves the animation as an h264 video, encoded in base64
directly into the HTML5 video tag. This respects the rc parameters
for the writer as well as the bitrate. This also makes use of the
``interval`` to control the speed, and uses the ``repeat``
paramter to decide whether to loop.
'''
VIDEO_TAG = r'''<video {size} {options}>
<source type="video/mp4" src="data:video/mp4;base64,{video}">
Your browser does not support the video tag.
</video>'''
# Cache the the rendering of the video as HTML
if not hasattr(self, '_base64_video'):
# First write the video to a tempfile. Set delete to False
# so we can re-open to read binary data.
with tempfile.NamedTemporaryFile(suffix='.m4v',
delete=False) as f:
# We create a writer manually so that we can get the
# appropriate size for the tag
Writer = writers[rcParams['animation.writer']]
writer = Writer(codec='h264',
bitrate=rcParams['animation.bitrate'],
fps=1000. / self._interval)
self.save(f.name, writer=writer)

# Now open and base64 encode
with open(f.name, 'rb') as video:
vid64 = base64.encodebytes(video.read())
self._base64_video = vid64.decode('ascii')
self._video_size = 'width="{0}" height="{1}"'.format(
*writer.frame_size)

# Now we can remove
os.remove(f.name)

# Default HTML5 options are to autoplay and to display video controls
options = ['controls', 'autoplay']

# If we're set to repeat, make it loop
if self.repeat:
options.append('loop')
return VIDEO_TAG.format(video=self._base64_video,
size=self._video_size,
options=' '.join(options))

def _repr_html_(self):
r'IPython display hook for rendering.'
fmt = rcParams['animation.html']
if fmt == 'html5':
return self.to_html5_video()


class TimedAnimation(Animation):
'''
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,9 @@ def validate_hinting(s):

validate_axis_locator = ValidateInStrings('major', ['minor', 'both', 'major'])

validate_movie_html_fmt = ValidateInStrings('animation.html',
['html5', 'none'])

def validate_bbox(s):
if isinstance(s, six.string_types):
s = s.lower()
Expand Down Expand Up @@ -944,6 +947,7 @@ def __call__(self, s):
'examples.directory': ['', six.text_type],

# Animation settings
'animation.html': ['none', validate_movie_html_fmt],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this needs to be added to the rc template too. At least for now? A comment with an explanation about that this controls the repr_html would also be good I think

'animation.writer': ['ffmpeg', validate_movie_writer],
'animation.codec': ['mpeg4', six.text_type],
'animation.bitrate': [-1, validate_int],
Expand Down
3 changes: 3 additions & 0 deletions matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ backend : %(backend)s
#examples.directory : '' # directory to look in for custom installation

###ANIMATION settings
#animation.html : 'none' # How to display the animation as HTML in
# the IPython notebook. 'html5' uses
# HTML5 video tag.
#animation.writer : ffmpeg # MovieWriter 'backend' to use
#animation.codec : mpeg4 # Codec to use for writing movie
#animation.bitrate: -1 # Controls size/quality tradeoff for movie.
Expand Down