Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 424556b

Browse filesBrowse files
committed
Merge pull request #5628 from JanSchulz/ani_writer
Reset the available animation movie writer on rcParam change
2 parents 600464d + e783867 commit 424556b
Copy full SHA for 424556b

File tree

Expand file treeCollapse file tree

3 files changed

+107
-11
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+107
-11
lines changed

‎lib/matplotlib/animation.py

Copy file name to clipboardExpand all lines: lib/matplotlib/animation.py
+61-7Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@
6464
class MovieWriterRegistry(object):
6565
def __init__(self):
6666
self.avail = dict()
67+
self._registered = dict()
68+
self._dirty = False
69+
70+
def set_dirty(self):
71+
"""Sets a flag to re-setup the writers"""
72+
self._dirty = True
6773

6874
# Returns a decorator that can be used on classes to register them under
6975
# a name. As in:
@@ -72,19 +78,36 @@ def __init__(self):
7278
# pass
7379
def register(self, name):
7480
def wrapper(writerClass):
81+
self._registered[name] = writerClass
7582
if writerClass.isAvailable():
7683
self.avail[name] = writerClass
7784
return writerClass
7885
return wrapper
7986

87+
def ensure_not_dirty(self):
88+
"""If dirty, reasks the writers if they are available"""
89+
if self._dirty:
90+
self.reset_available_writers()
91+
92+
def reset_available_writers(self):
93+
"""Reset the available state of all registered writers"""
94+
self.avail = {}
95+
for name, writerClass in self._registered.items():
96+
if writerClass.isAvailable():
97+
self.avail[name] = writerClass
98+
self._dirty = False
99+
80100
def list(self):
81101
''' Get a list of available MovieWriters.'''
102+
self.ensure_not_dirty()
82103
return list(self.avail.keys())
83104

84105
def is_available(self, name):
106+
self.ensure_not_dirty()
85107
return name in self.avail
86108

87109
def __getitem__(self, name):
110+
self.ensure_not_dirty()
88111
if not self.avail:
89112
raise RuntimeError("No MovieWriters available!")
90113
return self.avail[name]
@@ -315,10 +338,11 @@ def isAvailable(cls):
315338
Check to see if a MovieWriter subclass is actually available by
316339
running the commandline tool.
317340
'''
318-
if not cls.bin_path():
341+
bin_path = cls.bin_path()
342+
if not bin_path:
319343
return False
320344
try:
321-
p = subprocess.Popen(cls.bin_path(),
345+
p = subprocess.Popen(bin_path,
322346
shell=False,
323347
stdout=subprocess.PIPE,
324348
stderr=subprocess.PIPE,
@@ -432,9 +456,19 @@ def finish(self):
432456
# Check error code for creating file here, since we just run
433457
# the process here, rather than having an open pipe.
434458
if self._proc.returncode:
435-
raise RuntimeError('Error creating movie, return code: '
436-
+ str(self._proc.returncode)
437-
+ ' Try running with --verbose-debug')
459+
try:
460+
stdout = [s.decode() for s in self._proc._stdout_buff]
461+
stderr = [s.decode() for s in self._proc._stderr_buff]
462+
verbose.report("MovieWriter.finish: stdout: %s" % stdout,
463+
level='helpful')
464+
verbose.report("MovieWriter.finish: stderr: %s" % stderr,
465+
level='helpful')
466+
except Exception as e:
467+
pass
468+
msg = ('Error creating movie, return code: ' +
469+
str(self._proc.returncode) +
470+
' Try setting mpl.verbose.set_level("helpful")')
471+
raise RuntimeError(msg)
438472

439473
def cleanup(self):
440474
MovieWriter.cleanup(self)
@@ -619,12 +653,28 @@ def _init_from_registry(cls):
619653
binpath = ''
620654
rcParams[cls.exec_key] = rcParamsDefault[cls.exec_key] = binpath
621655

656+
@classmethod
657+
def isAvailable(cls):
658+
'''
659+
Check to see if a ImageMagickWriter is actually available
660+
661+
Done by first checking the windows registry (if applicable) and then
662+
running the commandline tool.
663+
'''
664+
bin_path = cls.bin_path()
665+
if bin_path == "convert":
666+
cls._init_from_registry()
667+
return super(ImageMagickBase, cls).isAvailable()
622668

623669
ImageMagickBase._init_from_registry()
624670

625671

672+
# Note: the base classes need to be in that order to get
673+
# isAvailable() from ImageMagickBase called and not the
674+
# one from MovieWriter. The latter is then called by the
675+
# former.
626676
@writers.register('imagemagick')
627-
class ImageMagickWriter(MovieWriter, ImageMagickBase):
677+
class ImageMagickWriter(ImageMagickBase, MovieWriter):
628678
def _args(self):
629679
return ([self.bin_path(),
630680
'-size', '%ix%i' % self.frame_size, '-depth', '8',
@@ -633,8 +683,12 @@ def _args(self):
633683
+ self.output_args)
634684

635685

686+
# Note: the base classes need to be in that order to get
687+
# isAvailable() from ImageMagickBase called and not the
688+
# one from MovieWriter. The latter is then called by the
689+
# former.
636690
@writers.register('imagemagick_file')
637-
class ImageMagickFileWriter(FileMovieWriter, ImageMagickBase):
691+
class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter):
638692
supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp',
639693
'pbm', 'raw', 'rgba']
640694

‎lib/matplotlib/rcsetup.py

Copy file name to clipboardExpand all lines: lib/matplotlib/rcsetup.py
+19-4Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,21 @@ def validate_hist_bins(s):
802802
raise ValueError("'hist.bins' must be 'auto', an int or " +
803803
"a sequence of floats")
804804

805+
def validate_animation_writer_path(p):
806+
# Make sure it's a string and then figure out if the animations
807+
# are already loaded and reset the writers (which will validate
808+
# the path on next call)
809+
if not isinstance(p, six.text_type):
810+
raise ValueError("path must be a (unicode) string")
811+
from sys import modules
812+
# set dirty, so that the next call to the registry will re-evaluate
813+
# the state.
814+
# only set dirty if already loaded. If not loaded, the load will
815+
# trigger the checks.
816+
if "matplotlib.animation" in modules:
817+
modules["matplotlib.animation"].writers.set_dirty()
818+
return p
819+
805820

806821
# a map from key -> value, converter
807822
defaultParams = {
@@ -1222,20 +1237,20 @@ def validate_hist_bins(s):
12221237
# Controls image format when frames are written to disk
12231238
'animation.frame_format': ['png', validate_movie_frame_fmt],
12241239
# Path to FFMPEG binary. If just binary name, subprocess uses $PATH.
1225-
'animation.ffmpeg_path': ['ffmpeg', six.text_type],
1240+
'animation.ffmpeg_path': ['ffmpeg', validate_animation_writer_path],
12261241

12271242
# Additional arguments for ffmpeg movie writer (using pipes)
12281243
'animation.ffmpeg_args': [[], validate_stringlist],
12291244
# Path to AVConv binary. If just binary name, subprocess uses $PATH.
1230-
'animation.avconv_path': ['avconv', six.text_type],
1245+
'animation.avconv_path': ['avconv', validate_animation_writer_path],
12311246
# Additional arguments for avconv movie writer (using pipes)
12321247
'animation.avconv_args': [[], validate_stringlist],
12331248
# Path to MENCODER binary. If just binary name, subprocess uses $PATH.
1234-
'animation.mencoder_path': ['mencoder', six.text_type],
1249+
'animation.mencoder_path': ['mencoder', validate_animation_writer_path],
12351250
# Additional arguments for mencoder movie writer (using pipes)
12361251
'animation.mencoder_args': [[], validate_stringlist],
12371252
# Path to convert binary. If just binary name, subprocess uses $PATH
1238-
'animation.convert_path': ['convert', six.text_type],
1253+
'animation.convert_path': ['convert', validate_animation_writer_path],
12391254
# Additional arguments for mencoder movie writer (using pipes)
12401255

12411256
'animation.convert_args': [[], validate_stringlist],

‎lib/matplotlib/tests/test_animation.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_animation.py
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
from matplotlib.externals import six
55

66
import os
7+
import sys
78
import tempfile
89
import numpy as np
910
from numpy.testing import assert_equal
1011
from nose import with_setup
12+
from nose.tools import assert_false, assert_true
13+
import matplotlib as mpl
1114
from matplotlib import pyplot as plt
1215
from matplotlib import animation
1316
from matplotlib.testing.noseclasses import KnownFailureTest
@@ -163,6 +166,30 @@ def animate(i):
163166
frames=iter(range(5)))
164167

165168

169+
def test_movie_writer_registry():
170+
ffmpeg_path = mpl.rcParams['animation.ffmpeg_path']
171+
# Not sure about the first state as there could be some writer
172+
# which set rcparams
173+
#assert_false(animation.writers._dirty)
174+
assert_true(len(animation.writers._registered) > 0)
175+
animation.writers.list() # resets dirty state
176+
assert_false(animation.writers._dirty)
177+
mpl.rcParams['animation.ffmpeg_path'] = u"not_available_ever_xxxx"
178+
assert_true(animation.writers._dirty)
179+
animation.writers.list() # resets
180+
assert_false(animation.writers._dirty)
181+
assert_false(animation.writers.is_available("ffmpeg"))
182+
# something which is guaranteed to be available in path
183+
# and exits immediately
184+
bin = u"true" if sys.platform != 'win32' else u"where"
185+
mpl.rcParams['animation.ffmpeg_path'] = bin
186+
assert_true(animation.writers._dirty)
187+
animation.writers.list() # resets
188+
assert_false(animation.writers._dirty)
189+
assert_true(animation.writers.is_available("ffmpeg"))
190+
mpl.rcParams['animation.ffmpeg_path'] = ffmpeg_path
191+
192+
166193
if __name__ == "__main__":
167194
import nose
168195
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.