Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f91dff4

Browse files
authored
Merge pull request #7306 from tacaswell/backport_win_animation_reg
Merge pull request #5628 from JanSchulz/ani_writer
2 parents 954d662 + 70418b9 commit f91dff4

File tree

3 files changed

+107
-11
lines changed

3 files changed

+107
-11
lines changed

lib/matplotlib/animation.py

Lines 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]
@@ -275,10 +298,11 @@ def isAvailable(cls):
275298
Check to see if a MovieWriter subclass is actually available by
276299
running the commandline tool.
277300
'''
278-
if not cls.bin_path():
301+
bin_path = cls.bin_path()
302+
if not bin_path:
279303
return False
280304
try:
281-
p = subprocess.Popen(cls.bin_path(),
305+
p = subprocess.Popen(bin_path,
282306
shell=False,
283307
stdout=subprocess.PIPE,
284308
stderr=subprocess.PIPE,
@@ -397,9 +421,19 @@ def finish(self):
397421
# Check error code for creating file here, since we just run
398422
# the process here, rather than having an open pipe.
399423
if self._proc.returncode:
400-
raise RuntimeError('Error creating movie, return code: '
401-
+ str(self._proc.returncode)
402-
+ ' Try running with --verbose-debug')
424+
try:
425+
stdout = [s.decode() for s in self._proc._stdout_buff]
426+
stderr = [s.decode() for s in self._proc._stderr_buff]
427+
verbose.report("MovieWriter.finish: stdout: %s" % stdout,
428+
level='helpful')
429+
verbose.report("MovieWriter.finish: stderr: %s" % stderr,
430+
level='helpful')
431+
except Exception as e:
432+
pass
433+
msg = ('Error creating movie, return code: ' +
434+
str(self._proc.returncode) +
435+
' Try setting mpl.verbose.set_level("helpful")')
436+
raise RuntimeError(msg)
403437

404438
def cleanup(self):
405439
MovieWriter.cleanup(self)
@@ -584,12 +618,28 @@ def _init_from_registry(cls):
584618
binpath = ''
585619
rcParams[cls.exec_key] = rcParamsDefault[cls.exec_key] = binpath
586620

621+
@classmethod
622+
def isAvailable(cls):
623+
'''
624+
Check to see if a ImageMagickWriter is actually available
625+
626+
Done by first checking the windows registry (if applicable) and then
627+
running the commandline tool.
628+
'''
629+
bin_path = cls.bin_path()
630+
if bin_path == "convert":
631+
cls._init_from_registry()
632+
return super(ImageMagickBase, cls).isAvailable()
587633

588634
ImageMagickBase._init_from_registry()
589635

590636

637+
# Note: the base classes need to be in that order to get
638+
# isAvailable() from ImageMagickBase called and not the
639+
# one from MovieWriter. The latter is then called by the
640+
# former.
591641
@writers.register('imagemagick')
592-
class ImageMagickWriter(MovieWriter, ImageMagickBase):
642+
class ImageMagickWriter(ImageMagickBase, MovieWriter):
593643
def _args(self):
594644
return ([self.bin_path(),
595645
'-size', '%ix%i' % self.frame_size, '-depth', '8',
@@ -598,8 +648,12 @@ def _args(self):
598648
+ self.output_args)
599649

600650

651+
# Note: the base classes need to be in that order to get
652+
# isAvailable() from ImageMagickBase called and not the
653+
# one from MovieWriter. The latter is then called by the
654+
# former.
601655
@writers.register('imagemagick_file')
602-
class ImageMagickFileWriter(FileMovieWriter, ImageMagickBase):
656+
class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter):
603657
supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp',
604658
'pbm', 'raw', 'rgba']
605659

lib/matplotlib/rcsetup.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,21 @@ def validate_hist_bins(s):
865865
raise ValueError("'hist.bins' must be 'auto', an int or " +
866866
"a sequence of floats")
867867

868+
def validate_animation_writer_path(p):
869+
# Make sure it's a string and then figure out if the animations
870+
# are already loaded and reset the writers (which will validate
871+
# the path on next call)
872+
if not isinstance(p, six.text_type):
873+
raise ValueError("path must be a (unicode) string")
874+
from sys import modules
875+
# set dirty, so that the next call to the registry will re-evaluate
876+
# the state.
877+
# only set dirty if already loaded. If not loaded, the load will
878+
# trigger the checks.
879+
if "matplotlib.animation" in modules:
880+
modules["matplotlib.animation"].writers.set_dirty()
881+
return p
882+
868883

869884
# a map from key -> value, converter
870885
defaultParams = {
@@ -1314,20 +1329,20 @@ def validate_hist_bins(s):
13141329
# Controls image format when frames are written to disk
13151330
'animation.frame_format': ['png', validate_movie_frame_fmt],
13161331
# Path to FFMPEG binary. If just binary name, subprocess uses $PATH.
1317-
'animation.ffmpeg_path': ['ffmpeg', six.text_type],
1332+
'animation.ffmpeg_path': ['ffmpeg', validate_animation_writer_path],
13181333

13191334
# Additional arguments for ffmpeg movie writer (using pipes)
13201335
'animation.ffmpeg_args': [[], validate_stringlist],
13211336
# Path to AVConv binary. If just binary name, subprocess uses $PATH.
1322-
'animation.avconv_path': ['avconv', six.text_type],
1337+
'animation.avconv_path': ['avconv', validate_animation_writer_path],
13231338
# Additional arguments for avconv movie writer (using pipes)
13241339
'animation.avconv_args': [[], validate_stringlist],
13251340
# Path to MENCODER binary. If just binary name, subprocess uses $PATH.
1326-
'animation.mencoder_path': ['mencoder', six.text_type],
1341+
'animation.mencoder_path': ['mencoder', validate_animation_writer_path],
13271342
# Additional arguments for mencoder movie writer (using pipes)
13281343
'animation.mencoder_args': [[], validate_stringlist],
13291344
# Path to convert binary. If just binary name, subprocess uses $PATH
1330-
'animation.convert_path': ['convert', six.text_type],
1345+
'animation.convert_path': ['convert', validate_animation_writer_path],
13311346
# Additional arguments for mencoder movie writer (using pipes)
13321347

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

lib/matplotlib/tests/test_animation.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
import six
55

66
import os
7+
import sys
78
import tempfile
89
import numpy as np
910
from nose import with_setup
11+
from nose.tools import assert_false, assert_true
12+
import matplotlib as mpl
1013
from matplotlib import pyplot as plt
1114
from matplotlib import animation
1215
from matplotlib.testing.noseclasses import KnownFailureTest
@@ -92,6 +95,30 @@ def animate(i):
9295
frames=iter(range(5)))
9396

9497

98+
def test_movie_writer_registry():
99+
ffmpeg_path = mpl.rcParams['animation.ffmpeg_path']
100+
# Not sure about the first state as there could be some writer
101+
# which set rcparams
102+
#assert_false(animation.writers._dirty)
103+
assert_true(len(animation.writers._registered) > 0)
104+
animation.writers.list() # resets dirty state
105+
assert_false(animation.writers._dirty)
106+
mpl.rcParams['animation.ffmpeg_path'] = u"not_available_ever_xxxx"
107+
assert_true(animation.writers._dirty)
108+
animation.writers.list() # resets
109+
assert_false(animation.writers._dirty)
110+
assert_false(animation.writers.is_available("ffmpeg"))
111+
# something which is guaranteed to be available in path
112+
# and exits immediately
113+
bin = u"true" if sys.platform != 'win32' else u"where"
114+
mpl.rcParams['animation.ffmpeg_path'] = bin
115+
assert_true(animation.writers._dirty)
116+
animation.writers.list() # resets
117+
assert_false(animation.writers._dirty)
118+
assert_true(animation.writers.is_available("ffmpeg"))
119+
mpl.rcParams['animation.ffmpeg_path'] = ffmpeg_path
120+
121+
95122
if __name__ == "__main__":
96123
import nose
97124
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

0 commit comments

Comments
 (0)