19

I am trying to save a simple matplotlib animation from Jake Vanderplas, but I keep getting OSError: [Errno 13] Permission denied.

I should note that I made two small modifications to Jake Vanderplas's example. I installed ffmpeg from MacPorts so I added the line plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin' and I ran into the problem discussed in (Using FFmpeg and IPython), so I added FFwriter = animation.FFMpegWriter().

Here is the code:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin'

fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

def init():
    line.set_data([], [])
    return line,

def animate(i):
    x = np.linspace(0, 2, 1000)
    y = np.sin(2 * np.pi * (x - 0.01 * i))
    line.set_data(x, y)
    return line,

anim = animation.FuncAnimation(fig, animate, init_func=init,
                           frames=200, interval=20, blit=True)

FFwriter = animation.FFMpegWriter()
anim.save('basic_animation.mp4', writer = FFwriter, fps=30, extra_args=['-vcodec', 'libx264'])

Here is the traceback:

File "ani_debug.py", line 34, in <module>
  anim.save('basic_animation.mp4', writer = FFwriter, fps=30, extra_args=['-vcodec', 'libx264'])
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site- packages/matplotlib/animation.py", line 712, in save
  with writer.saving(self._fig, filename, dpi):
File "/Applications/Canopy.app/appdata/canopy-1.3.0.1715.macosx-x86_64/Canopy.app/Contents/lib/python2.7/contextlib.py", line 17, in __enter__
  return self.gen.next()
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.py", line 169, in saving
  self.setup(*args)
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.py", line 159, in setup
  self._run()
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.py", line 186, in _run
  stdin=subprocess.PIPE)
File "/Applications/Canopy.app/appdata/canopy-1.3.0.1715.macosx-x86_64/Canopy.app/Contents/lib/python2.7/subprocess.py", line 709, in __init__
  errread, errwrite)
File "/Applications/Canopy.app/appdata/canopy-1.3.0.1715.macosx-x86_64/Canopy.app/Contents/lib/python2.7/subprocess.py", line 1326, in _execute_child
  raise child_exception
OSError: [Errno 13] Permission denied

I have also tried using Spyder's built-in python and received a similar traceback. Any suggestions?


EDIT: I realized that I did not give the proper path to ffmpeg. Apparently, plt.rcParams['animation.ffmpeg_path'] does not work similar to PYTHONPATH. You must tell the animation module exactly where ffmpeg is with plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin/ffmpeg'.

Now, I get a movie file that will play, but the content is completely garbled. I can't tell what I am looking at.

Here is the traceback:

Exception in Tkinter callback
Traceback (most recent call last):
  File "Tkinter.pyc", line 1470, in __call__
  File "Tkinter.pyc", line 531, in callit
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/backends/backend_tkagg.py", line 141, in _on_timer
    TimerBase._on_timer(self)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/backend_bases.py", line 1203, in _on_timer
    ret = func(*args, **kwargs)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 876, in _step
    still_going = Animation._step(self, *args)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 735, in _step
    self._draw_next_frame(framedata, self._blit)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 753, in _draw_next_frame
    self._pre_draw(framedata, blit)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 766, in _pre_draw
    self._blit_clear(self._drawn_artists, self._blit_cache)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 806, in _blit_clear
    a.figure.canvas.restore_region(bg_cache[a])
KeyError: <matplotlib.axes.AxesSubplot object at 0x104cfb150>

EDIT: For some reason, everything is working fine now. I have tried things on my home computer and my work computer, and neither one can recreate the garbled video file I got after I fixed the ffmpeg path issue.


EDIT: Aaaahaaa! I tracked this sucker down. Sometimes I would import a module that had plt.rcParams['savefig.bbox'] = 'tight' in it. (I would never use that module, but rcParams persist, until you restart your python interpreter.) That setting causes the video to come out all garbled. I will post my solution below.

Community
  • 1
  • 1
Stretch
  • 1,581
  • 3
  • 17
  • 31
  • Wait.. now you get a traceback but also get output? – KobeJohn Apr 15 '14 at 12:28
  • Well, crud. I just tried my code above (with the proper path to ffmpeg) on my work computer, and it worked fine. All my testing before this has been on my home computer. Maybe I need to reinstall ffmpeg on my home computer. – Stretch Apr 15 '14 at 19:48

4 Answers4

25

So it turns out there were two issues.

Issue #1: The path to ffmpeg was wrong. I thought I needed to provide the path to the directory that ffmpeg resides in, but I needed to provide the path all the way to the ffmpeg binary.

Issue #2: Prior to testing out my code to generate videos, I sometimes would import a module with the setting plt.rcParams['savefig.bbox'] = 'tight'. (I did not think much of it, because I did not use the module, but rcParams persist until you restart the python interpreter.) This plt.rcParams['savefig.bbox'] = 'tight' causes the video file to save without any errors, but the frames are all garbled when you try to play the video. Although it took me all evening to track this down, it turns out this is a known issue.

Here is the updated solution that creates a video file for me with a nice, translating, sine wave.

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin/ffmpeg'

fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

def init():
    line.set_data([], [])
    return line,

def animate(i):
    x = np.linspace(0, 2, 1000)
    y = np.sin(2 * np.pi * (x - 0.01 * i))
    line.set_data(x, y)
    return line,

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)

FFwriter = animation.FFMpegWriter(fps=30, extra_args=['-vcodec', 'libx264'])
anim.save('basic_animation.mp4', writer=FFwriter)
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Stretch
  • 1,581
  • 3
  • 17
  • 31
1

Thank you Stretch for your valuable answer. I found that mentioning extra args inside anim.save() results in error. Hence the code is updated as shown below,

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = r'I:\FFmpeg\bin\ffmpeg' #make sure you download FFmpeg files for windows 10 from https://ffmpeg.zeranoe.com/builds/

fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

def init():
    line.set_data([], [])
    return line,

def animate(i):
    x = np.linspace(0, 2, 1000)
    y = np.sin(2 * np.pi * (x - 0.01 * i))
    line.set_data(x, y)
    return line,

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)

FFwriter=animation.FFMpegWriter(fps=30, extra_args=['-vcodec', 'libx264'])
anim.save(r'I:\Understanding_objective functions\test\basic_animation.mp4', writer=FFwriter)

plt.show()

Hope this might helps someone trying to save animation plots to .mp4 format.

Paul Thomas
  • 477
  • 1
  • 7
  • 15
0

I had garbling issues when I first (naively) tried to modify the working example of Stretch's answer to show the graph in realtime (as well as keep the movie).

Not quite right mods of Stretch's answer (which worked for me)

  1. plt.ion() interaction on
  2. plt.draw() and plt.show() inside animate function, before return statent
  3. frames=20, interval=200 to slow graph creation a bit, but still make a 4 second movie

Now plot shows up in a window as it is being created, but the output movie is garbled.

Correct step 2:

  • 2a: plt.draw() inside animate function
  • 2b: plt.show() just after the animate function

Now movie plays back ungarbled.

nik7
  • 806
  • 3
  • 12
  • 20
Erik Kruus
  • 161
  • 1
  • 3
  • It's good to remember that in any situation, not just animation, you likely need to `.show()` your figure only once. – Guimoute Apr 27 '21 at 08:17
0

Further to Stretch's answer, I found that some of the parameters being passed to anim.save() do not appear to achieve the desired effect. Specifically fps was 5 (default) and not the 30 being set. By passing fps=30 to animation.FFMpegWriter it does work.

So:

FFwriter = animation.FFMpegWriter(fps=30)
anim.save('basic_animation.mp4', writer=FFwriter, extra_args=['-vcodec', 'libx264'])

Note that the video is now 7 seconds long (200 frames @ 30 fps) rather than 40 seconds (200 frames @ 5 fps). Note also that a default of 5 fps corresponds to the default 200 ms/frame interval in FuncAnimation, and strictly speaking the 20 ms animation interval used here corresponds to 50 fps.

For those struggling to achieve better video quality, it is also possible to pass a bitrate (integer in kbps) to animation.FFMpegWriter, eg:

FFwriter = animation.FFMpegWriter(fps=30, bitrate=2000)

I tried various extra_args in an effort to achieve better quality, without much success.

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
wheeled
  • 36
  • 3