8000 making it possible to add full command in animation · matplotlib/matplotlib@11df90a · GitHub
[go: up one dir, main page]

Skip to content

Commit 11df90a

Browse files
committed
making it possible to add full command in animation
1 parent b9b02f1 commit 11df90a

File tree

3 files changed

+275
-9
lines changed

3 files changed

+275
-9
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""
2+
==================================
3+
Using the full command line option
4+
==================================
5+
6+
This example show:
7+
- how to get a slow animation work in for example the VLC player
8+
- how to use the full command line option when the default command line is not
9+
general enough
10+
- one way to animate an axes title with `.ArtistAnimation`
11+
- several ways of saving the animation
12+
"""
13+
14+
import numpy as np
15+
import matplotlib.pyplot as plt
16+
import matplotlib.animation as animation
17+
18+
plt.close('animation')
19+
fig, ax = plt.subplots(num='animation')
20+
21+
# There is a problem with animation of the axis title with artist animation.
22+
# The problem is because the title is one object in the axes object that just
23+
# get updated and not changed. This solution use a an ordinary text object with
24+
# some of the title properties so that it behaves like the axes title.
25+
transform = ax.title.get_transform()
26+
title_kwargs = dict(transform=transform, horizontalalignment='center')
27+
28+
def f(x, y, i):
29+
return np.sin(x+i*np.pi/15) + np.cos(y+i*np.pi/20)
30+
31+
x = np.linspace(0, 2*np.pi, 120)
32+
y = np.linspace(0, 2*np.pi, 100).reshape(-1, 1)
33+
34+
ims = []
35+
for i in range(10):
36+
im = ax.imshow(f(x, y, i))
37+
number = ax.text(60, 50, str(i), fontsize=50, ha='center',
38+
transform=ax.transData)
39+
title = ax.text(0.5, 1, 'Frame number: {}'.format(i), **title_kwargs)
40+
ims.append([im, number, title])
41+
42+
anim = animation.ArtistAnimation(fig, ims, interval=2000, repeat_delay=1000)
43+
< 67E6 code>44+
# The animation can be saved to an animated gif with the ImageMagick or Pillow
45+
# writers with for example.
46+
anim.save('anim.gif', writer='imagemagick')
47+
48+
# or to a html file with for example.
49+
writer_html = animation.HTMLWriter(fps=0.5, embed_frames=True)
50+
anim.save('anim.html', writer=writer_html)
51+
52+
# It can also be saved to an .mp4 file with the ffmpeg or avconv writers with
53+
# for example.
54+
55+
writer1 = animation.FFMpegFileWriter(fps=0.5)
56+
anim.save('anim1.mp4', writer=writer1)
57+
58+
"""
59+
``anim1.mp4`` might not work well in all movie players though. The proplems is
60+
that the frame rate of the movie is 0.5 frames per second and all players cant
61+
handle such a low frame rate. This can be solved by having different input
62+
and output frame rates. You can read more here_.
63+
64+
This can be done in matplotlib using the full command line option.
65+
66+
.. _here: rac.ffmpeg.org/wiki/Slideshow
67+
"""
68+
69+
# See how the command used before looks like.
70+
command1 = writer1.get_command()
71+
print(command1)
72+
# ffmpeg -r 0.5 -i _tmp%07d.png -vframes 11 -vcodec h264 -pix_fmt
73+
# yuv420p -y anim1.mp4
74+
#
75+
# We can change that command line to a formated one to be able to save the
76+
# movie with an output framerate of 25 fps.
77+
full_command = ['{path} -framerate {fps} -i _tmp%07d.png -vf fps=25 -vcodec '
78+
'h264 -pix_fmt yuv420p -y {outfile}'][0]
79+
80+
writer2 = animation.FFMpegFileWriter(fps=0.5, extra_args=full_command)
81+
anim.save('anim2.mp4', writer=writer2)
82+
83+
# Another way to achieve a workable movie is the use the extra_args option
84+
# in the FFMpegWriter (the same method doesn't work in the FFMpegFileWriter
85+
# due to the -vframes argument).
86+
writer3 = animation.FFMpegWriter(fps=0.5, extra_args=['-vf', 'fps=25'])
87+
anim.save('anim3.mp4', writer=writer3)
88+
89+
plt.show()
90+
91+
"""
92+
References
93+
----------
94+
95+
The use of the following functions and methods is shown
96+
in this example:
97+
"""
98+
import matplotlib
99+
matplotlib.animation.ArtistAnimation
100+
matplotlib.animation.ArtistAnimation.save
101+
matplotlib.animation.ImageMagickWriter
102+
matplotlib.animation.HTMLWriter
103+
matplotlib.animation.FFMpegFileWriter
104+
matplotlib.animation.FFMpegFileWriter.get_command

lib/matplotlib/animation.py

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,30 +250,56 @@ class MovieWriter(AbstractMovieWriter):
250250

251251
def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
252252
metadata=None):
253-
'''MovieWriter
253+
"""
254+
MovieWriter
254255
255256
Parameters
256257
----------
257258
fps: int
258259
Framerate for movie.
260+
259261
codec: string or None, optional
260262
The codec to use. If ``None`` (the default) the ``animation.codec``
261263
rcParam is used.
264+
262265
bitrate: int or None, optional
263266
The bitrate for the saved movie file, which is one way to control
264267
the output file size and quality. The default value is ``None``,
265268
which uses the ``animation.bitrate`` rcParam. A value of -1
266269
implies that the bitrate should be determined automatically by the
267270
underlying utility.
268-
extra_args: list of strings or None, optional
269-
A list of extra string arguments to be passed to the underlying
270-
movie utility. The default is ``None``, which passes the additional
271-
arguments in the ``animation.extra_args`` rcParam.
271+
272+
extra_args: list of strings, string or None, optional
273+
Either a list of extra string arguments to be passed to the
274+
underlying movie utility or a full command string
275+
explained below. The default is ``None``,which passes the
276+
additional arguments in the ``animation.extra_args`` rcParam.
277+
272278
metadata: Dict[str, str] or None
273279
A dictionary of keys and values for metadata to include in the
274280
output file. Some keys that may be of use include:
275281
title, artist, genre, subject, copyright, srcform, comment.
276-
'''
282+
283+
Notes
284+
-----
285+
*extra_args* can be a string with the full command line argument used
286+
in the ffmpeg, imagemagick and avconv writers. The string is then
287+
formatted with the python method ``format``. Possible keywords for the
288+
pipe based writers are::
289+
290+
path, fps, delay=100/fps, x_size, y_size (in pixels),
291+
frame_format, codec, bitrate, outfile
292+
293+
and for the file bases writers::
294+
295+
path, fps, delay=100/fps, frame_format, temp_name,
296+
N (number of frames ), codec, bitrate, outfile
297+
298+
an example command string is::
299+
300+
'ffmpeg -r {fps} -i _tmp%07d.png -r 25 -y {outfile}'
301+
302+
"""
277303
self.fps = fps
278304
self.frame_format = 'rgba'
279305

@@ -292,6 +318,12 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
292318
else:
293319
self.extra_args = extra_args
294320

321+
if type(self.extra_args) == str:
322+
self.full_command = extra_args
323+
self.extra_args = None
324+
else:
325+
self.full_command = None
326+
295327
if metadata is None:
296328
self.metadata = dict()
297329
else:
@@ -316,7 +348,7 @@ def _adjust_frame_size(self):
316348
_log.debug('frame size in pixels is %s x %s', *self.frame_size)
317349
return w, h
318350

319-
def setup(self, fig, outfile, dpi=None):
351+
def setup(self, fig, outfile, dpi=None, run=True):
320352
'''
321353
Perform setup for writing the movie file.
322354
@@ -329,6 +361,8 @@ def setup(self, fig, outfile, dpi=None):
329361
dpi : int, optional
330362
The DPI (or resolution) for the file. This controls the size
331363
in pixels of the resulting movie file. Default is fig.dpi.
364+
run : bool, optional
365+
The command to save the movie is run if set to True.
332366
'''
333367
self.outfile = outfile
334368
self.fig = fig
@@ -339,7 +373,8 @@ def setup(self, fig, outfile, dpi=None):
339373

340374
# Run here so that grab_frame() can write the data to a pipe. This
341375
# eliminates the need for temp files.
342-
self._run()
376+
if run:
377+
self._run()
343378

344379
def _run(self):
345380
# Uses subprocess to call the program for assembling frames into a
@@ -413,6 +448,44 @@ def isAvailable(cls):
413448
'''
414449
return shutil.which(cls.bin_path()) is not None
415450

451+
def _get_full_command(self):
452+
"""
453+
Make string formating on the *full_command* string.
454+
455+
The string syntax should be '{path} -r {fps}' where possible keywords
456+
are path, fps, delay=100/fps, x_size, y_size (in pixels), frame_format,
457+
codec, bitrate, outfile.
458+
"""
459+
arg_dict = dict(path=self.bin_path(),
460+
fps=str(self.fps),
461+
delay=str(100./self.fps),
462+
x_size=self.frame_size[0],
463+
y_size=self.frame_size[1],
464+
frame_format=self.frame_format,
465+
codec=self.codec,
466+
bitrate=self.bitrate,
467+
outfile=self.outfile)
468+
469+
return self.full_command.format(**arg_dict).split()
470+
471+
def get_command(self):
472+
"""
473+
Returns the command line as a string.
474+
475+
`.setup` should be called before `.get_command` so all variables are
476+
set. This could either be done explicitely or by `.Animation.save`.
477+
The first method results in an incorrect value of number of frames
478+
where applicable.
479+
480+
The empty string is returned if not all variables where set or if the
481+
writer don't use a command line.
482+
"""
483+
try:
484+
return ' '.join(self._args())
485+
except:
486+
_log.warning("The command line could not be show for this writer"
487+
" or all variables where not set with setup.")
488+
return ''
416489

417490
class FileMovieWriter(MovieWriter):
418491
'''`MovieWriter` for writing to individual files and stitching at the end.
@@ -543,6 +616,27 @@ def cleanup(self):
543616
for fname in self._temp_names:
544617
os.remove(fname)
545618

619+
def _get_full_command(self):
620+
"""
621+
Make string formating on the *full_command* input string.
622+
623+
The string syntax should be '{path} -r {fps}' where possible keywords
624+
are path, fps, delay=100/fps, frame_format, temp_name, N (number of
625+
frames ), codec, bitrate, outfile.
626+
"""
627+
628+
arg_dict = dict(path=self.bin_path(),
629+
fps=str(self.fps),
630+
delay=str(100./self.fps),
631+
frame_format=self.frame_format,
632+
temp_name=self._base_temp_name(),
633+
N=str(self._frame_counter),
634+
codec=self.codec,
635+
bitrate=self.bitrate,
636+
outfile=self.outfile)
637+
638+
return self.full_command.format(**arg_dict).split()
639+
546640

547641
@writers.register('pillow')
548642
class PillowWriter(MovieWriter):
@@ -637,6 +731,8 @@ class FFMpegWriter(FFMpegBase, MovieWriter):
637731
def _args(self):
638732
# Returns the command line parameters for subprocess to use
639733
# ffmpeg to create a movie using a pipe.
734+
if self.full_command:
735+
return self._get_full_command()
640736
args = [self.bin_path(), '-f', 'rawvideo', '-vcodec', 'rawvideo',
641737
'-s', '%dx%d' % self.frame_size, '-pix_fmt', self.frame_format,
642738
'-r', str(self.fps)]
@@ -662,6 +758,8 @@ class FFMpegFileWriter(FFMpegBase, FileMovieWriter):
662758
'pbm', 'raw', 'rgba']
663759

664760
def _args(self):
761+
if self.full_command:
762+
return self._get_full_command()
665763
# Returns the command line parameters for subprocess to use
666764
# ffmpeg to create a movie using a collection of temp images
667765
return [self.bin_path(), '-r', str(self.fps),
@@ -768,6 +866,8 @@ class ImageMagickWriter(ImageMagickBase, MovieWriter):
768866
769867
'''
770868
def _args(self):
869+
if self.full_command:
870+
return self._get_full_command()
771871
return ([self.bin_path(),
772872
'-size', '%ix%i' % self.frame_size, '-depth', '8',
773873
'-delay', str(self.delay), '-loop', '0',
@@ -792,8 +892,11 @@ class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter):
792892
'pbm', 'raw', 'rgba']
793893

794894
def _args(self):
895+
self.fname_format_str = '%s*.%s'
896+
if self.full_command:
897+
return self._get_full_command()
795898
return ([self.bin_path(), '-delay', str(self.delay), '-loop', '0',
796-
'%s*.%s' % (self.temp_prefix, self.frame_format)]
899+
self._base_temp_name()]
797900
+ self.output_args)
798901

799902

lib/matplotlib/tests/test_animation.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,62 @@ def test_failing_ffmpeg(tmpdir, monkeypatch):
260260
make_animation().save("test.mpeg")
261261
finally:
262262
animation.writers.reset_available_writers()
263+
264+
def test_command_line():
265+
fig = plt.figure()
266+
267+
command1_ref = ['-f rawvideo -vcodec rawvideo -s 640x480 -pix_fmt '
268+
'rgba -r 2 -loglevel quiet -i pipe: -vcodec h264 '
269+
'-pix_fmt yuv420p -y test.mp4'][0]
270+
271+
writer1 = animation.FFMpegWriter(fps=2, codec='h264', bitrate=-1)
272+
writer1.setup(fig, 'test.mp4')
273+
command1 = writer1.get_command()
274+
ind1 = command1.find(' ')
275+
276+
command2_ref = '-delay 50.0 -loop 0 _tmp*.png test.gif'
277+
278+
writer2 = animation.ImageMagickFileWriter(fps=2)
279+
writer2.setup(fig, 'test.gif')
280+
command2 = writer2.get_command()
281+
ind2 = command2.find(' ')
282+
283+
assert command1[ind1+1:] == command1_ref
284+
assert command2[ind2+1:] == command2_ref
285+
286+
def test_full_command_line():
287+
fig = plt.figure()
288+
289+
com1_ref = ['ffmpeg -f rawvideo -vcodec rawvideo -s 640x480 -pix_fmt '
290+
'rgba -r 2 -loglevel quiet -i pipe: -vcodec h264 '
291+
'-pix_fmt yuv420p -y test.mp4'][0]
292+
293+
com1_in = ['ffmpeg -f rawvideo -vcodec rawvideo -s {x_size}x{y_size} '
294+
'-pix_fmt {frame_format} -r {fps} -loglevel quiet -i pipe: '
295+
'-vcodec h264 -pix_fmt yuv420p -y {outfile}'][0]
296+
297+
writer1 = animation.FFMpegWriter(fps=2, extra_args=com1_in)
298+
writer1.setup(fig, 'test.mp4')
299+
com1 = writer1.get_command()
300+
301+
com2_ref = 'convert -delay 50.0 -loop 0 _tmp*.png test.gif'
302+
com2_in = 'convert -delay {delay} -loop 0 {temp_name} {outfile}'
303+
304+
writer2 = animation.ImageMagickFileWriter(fps=2, extra_args=com2_in)
305+
writer2.setup(fig, 'test.gif')
306+
com2 = writer2.get_command()
307+
308+
com3_ref = ['ffmpeg -r 2 -i _tmp%07d.png -vframes 10 -vcodec h264 '
309+
'-pix_fmt yuv420p -y test.mp4'][0]
310+
311+
com3_in = ['ffmpeg -r {fps} -i {temp_name} -vframes 10 -vcodec {codec} '
312+
'-pix_fmt yuv420p -y test.mp4'][0]
313+
314+
writer3 = animation.FFMpegFileWriter(fps=2, codec='h264',
315+
extra_args=com3_in)
316+
writer3.setup(fig, 'test.mp4')
317+
com3 = writer3.get_command()
318+
319+
assert com1 == com1_ref
320+
assert com2 == com2_ref
321+
assert com3 == com3_ref

0 commit comments

Comments
 (0)
0