IPython Notebook animations embedded in Google Chrome

by Jorge Herrera
December 2013

Matplotlib offers an easy way to create animations using matplotlib.animation. Furthermore, there are examples showing how to embed dynamically generated animations in an IPython Notebook (e.g. this example). The problem is that Google Chrome dropped support for H.264 video encoding. Instead if favors Google's WebM standar (open-source and no royalties for browser developers).

I had to fiddle for a few hours to get matplotlib to generate a .WebM video, so I decided to put this notebook together for future reference. The code is largely based on the afore mentioned example.

Note: the WebM format is not supported by every browser. In particular, is not supported by Safari at the moment. I have only tested this notebook on Google Chrome (v 31.0.1650.63, in case you care). That said, according to webmproject.org the format is supported by these browsers:

  • Mozilla Firefox 4 and later
  • Opera 10.60 and later
  • Google Chrome 6 and later
  • Microsoft Internet Explorer 9 and later (requires WebM MF components)
In [13]:
import numpy as np

import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt

#################################################
# SIMPLE OSCILATOR
#################################################

TWO_PI = np.pi*2.0

fs = 1000                              # simulation sample rate (samples/second)
SIM_DURATION = 4.0                     # simulation duration in seconds
t = np.arange(0,SIM_DURATION,1.0/fs)   # simulation time vector
A = np.sqrt(t/SIM_DURATION)            # oscilator amplitude
omega_0 = 5*TWO_PI                     # oscilator natural frequency

# sinosuidal force (driving oscilator)
epsilon = 1.1                          # coupling force
omega = omega_0*0.9                    # forcing frequency
force = epsilon*np.cos(omega*t)    

# drived oscilator
theta = omega_0*t + force
x = A*np.exp(1j*theta)

r = np.abs(x)
phi = np.angle(x)

# plt.plot(t, r, 'b', t, phi, 'r')
In [14]:
#################################################
# UTILS TO GENERATE AND EMBED THE MOVIE
#################################################

from tempfile import NamedTemporaryFile
import shutil

WEBM_VIDEO_TAG = """<video controls>
 <source src="data:video/x-webm;base64,{0}" type="video/webm">
 Your browser does not support the video tag.
</video>"""

M4V_VIDEO_TAG = """<video controls>
 <source src="data:video/x-m4v;base64,{0}" type="video/mp4">
 Your browser does not support the video tag.
</video>"""

FPS = 20         # Frames per second in the generated movie

def anim_to_html(anim, filename=None):
    if not hasattr(anim, '_encoded_video'):
        with NamedTemporaryFile(suffix='.webm') as f:
            webm_writer = animation.FFMpegWriter(fps=FPS, codec="libvpx")  # you'll need libvpx to encode .webm videos
            vpx_args = ["-quality", "good",    # many arguments are not needed in this example, but I left them for reference
                        "-cpu-used", "0",
                        "-b:v", "500k",
                        "-qmin", "10",
                        "-qmax", "42",
                        "-maxrate", "500k",
                        "-bufsize", "1000k",
                        "-threads", "4",
                        "-vf", "scale=-1:240",
                        "-codec:a", "libvorbis",
                        "-b:a", "128k"]
            anim.save(f.name, writer=webm_writer, extra_args=vpx_args)
            if filename is not None:  # in case you want to keep a copy of the generated movie
                shutil.copyfile(f.name, filename)
            video = open(f.name, "rb").read()
        anim._encoded_video = video.encode("base64")
        
    return WEBM_VIDEO_TAG.format(anim._encoded_video)


from IPython.display import HTML

def display_animation(anim, filename):
    plt.close(anim._fig)
    return HTML(anim_to_html(anim, filename))
In [15]:
#################################################
# THE ACTUAL ANIMATION
#################################################

from matplotlib import animation

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.subplot(111, polar=True)
line, point, = ax.plot([], [], 'b-', [], [], 'ro', lw=1)
ax.set_rmax(1.2*np.max(r))
ax.set_yticks([]) 


TIME_SCALE = 0.25       # this allows to generate the movie at differenc "speeds"
                        # use TIME_SCALE = 1.0 for real speed, TIME_SCALE = 2.0 for double speed 
                        # (note that the movie generation time is proportional to 1/TIME_SCALE)

# initialization function
def init():
    line.set_data([], [])
    point.set_data([], [])
    return line,

# animation function.  This is called sequentially
def animate(i):
    tt = np.round(1.0*i*TIME_SCALE*fs/FPS)  # here we link the simulation time with the movie time
    line.set_data(phi[:tt], r[:tt])
    point.set_data(phi[tt], r[tt])
    return line, point,

# call the animator.  blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, 
                               init_func=init,
                               frames=int(FPS*SIM_DURATION/TIME_SCALE), 
                               interval=TIME_SCALE*1000/FPS, blit=True)

# call our new function to display the animation
display_animation(anim, filename='testanim.webm')
Out[15]:
In [15]: