37
37
import contextlib
38
38
import tempfile
39
39
import warnings
40
- from matplotlib .cbook import iterable , is_string_like
40
+ from matplotlib .cbook import iterable , is_string_like , deprecated
41
41
from matplotlib .compat import subprocess
42
42
from matplotlib import verbose
43
43
from matplotlib import rcParams , rcParamsDefault , rc_context
61
61
# how-to-encode-series-of-images-into-h264-using-x264-api-c-c )
62
62
63
63
64
+ def adjusted_figsize (w , h , dpi , n ):
65
+ wnew = int (w * dpi / n ) * n / dpi
66
+ hnew = int (h * dpi / n ) * n / dpi
67
+ return wnew , hnew
68
+
69
+
64
70
# A registry for available MovieWriter classes
65
71
class MovieWriterRegistry (object ):
66
72
def __init__ (self ):
@@ -194,10 +200,6 @@ class MovieWriter(AbstractMovieWriter):
194
200
The format used in writing frame data, defaults to 'rgba'
195
201
'''
196
202
197
- # Specifies whether the size of all frames need to be identical
198
- # i.e. whether we can use savefig.bbox = 'tight'
199
- frame_size_can_vary = False
200
-
201
203
def __init__ (self , fps = 5 , codec = None , bitrate = None , extra_args = None ,
202
204
metadata = None ):
203
205
'''
@@ -249,8 +251,20 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
249
251
@property
250
252
def frame_size (self ):
251
253
'A tuple (width,height) in pixels of a movie frame.'
252
- width_inches , height_inches = self .fig .get_size_inches ()
253
- return width_inches * self .dpi , height_inches * self .dpi
254
+ w , h = self .fig .get_size_inches ()
255
+ return int (w * self .dpi ), int (h * self .dpi )
256
+
257
+ def _adjust_frame_size (self ):
258
+ if self .codec == 'h264' :
259
+ wo , ho = self .fig .get_size_inches ()
260
+ w , h = adjusted_figsize (wo , ho , self .dpi , 2 )
261
+ if not (wo , ho ) == (w , h ):
262
+ self .fig .set_size_inches (w , h , forward = True )
263
+ verbose .report ('figure size (inches) has been adjusted '
264
+ 'from %s x %s to %s x %s' % (wo , ho , w , h ),
265
+ level = 'helpful' )
266
+ verbose .report ('frame size in pixels is %s x %s' % self .frame_size ,
267
+ level = 'debug' )
254
268
255
269
def setup (self , fig , outfile , dpi ):
256
270
'''
@@ -267,6 +281,7 @@ def setup(self, fig, outfile, dpi):
267
281
self .outfile = outfile
268
282
self .fig = fig
269
283
self .dpi = dpi
284
+ self ._adjust_frame_size ()
270
285
271
286
# Run here so that grab_frame() can write the data to a pipe. This
272
287
# eliminates the need for temp files.
@@ -364,10 +379,6 @@ def isAvailable(cls):
364
379
class FileMovieWriter (MovieWriter ):
365
380
'`MovieWriter` subclass that handles writing to a file.'
366
381
367
- # In general, if frames are writen to files on disk, it's not important
368
- # that they all be identically sized
369
- frame_size_can_vary = True
370
-
371
382
def __init__ (self , * args , ** kwargs ):
372
383
MovieWriter .__init__ (self , * args , ** kwargs )
373
384
self .frame_format = rcParams ['animation.frame_format' ]
@@ -393,6 +404,8 @@ def setup(self, fig, outfile, dpi, frame_prefix='_tmp', clear_temp=True):
393
404
self .fig = fig
394
405
self .outfile = outfile
395
406
self .dpi = dpi
407
+ self ._adjust_frame_size ()
408
+
396
409
self .clear_temp = clear_temp
397
410
self .temp_prefix = frame_prefix
398
411
self ._frame_counter = 0 # used for generating sequential file names
@@ -448,10 +461,9 @@ def grab_frame(self, **savefig_kwargs):
448
461
try :
449
462
# Tell the figure to save its data to the sink, using the
450
463
# frame format and dpi.
451
- myframesink = self ._frame_sink ()
452
- self .fig .savefig (myframesink , format = self .frame_format ,
453
- dpi = self .dpi , ** savefig_kwargs )
454
- myframesink .close ()
464
+ with self ._frame_sink () as myframesink :
465
+ self .fig .savefig (myframesink , format = self .frame_format ,
466
+ dpi = self .dpi , ** savefig_kwargs )
455
467
456
468
except RuntimeError :
457
469
out , err = self ._proc .communicate ()
@@ -548,7 +560,7 @@ class FFMpegFileWriter(FileMovieWriter, FFMpegBase):
548
560
def _args (self ):
549
561
# Returns the command line parameters for subprocess to use
550
562
# ffmpeg to create a movie using a collection of temp images
551
- return [self .bin_path (), '-r' , str ( self . fps ),
563
+ return [self .bin_path (), # -r option is not needed before -i option
552
564
'-i' , self ._base_temp_name (),
553
565
'-vframes' , str (self ._frame_counter ),
554
566
'-r' , str (self .fps )] + self .output_args
@@ -608,9 +620,20 @@ def output_args(self):
608
620
return args
609
621
610
622
611
- # Combine Mencoder options with pipe-based writing
623
+ # The message must be a single line; internal newlines cause sphinx failure.
624
+ mencoder_dep = ("Support for mencoder is only partially functional, "
625
+ "and will be removed entirely in 2.2. "
626
+ "Please use ffmpeg instead." )
627
+
628
+
612
629
@writers .register ('mencoder' )
613
630
class MencoderWriter (MovieWriter , MencoderBase ):
631
+
632
+ @deprecated ('2.0' , message = mencoder_dep )
633
+ def __init__ (self , * args , ** kwargs ):
634
+ with rc_context (rc = {'animation.codec' : 'mpeg4' }):
635
+ super (MencoderWriter , self ).__init__ (* args , ** kwargs )
636
+
614
637
def _args (self ):
615
638
# Returns the command line parameters for subprocess to use
616
639
# mencoder to create a movie
@@ -625,6 +648,11 @@ def _args(self):
625
648
class MencoderFileWriter (FileMovieWriter , MencoderBase ):
626
649
supported_formats = ['png' , 'jpeg' , 'tga' , 'sgi' ]
627
650
651
+ @deprecated ('2.0' , message = mencoder_dep )
652
+ def __init__ (self , * args , ** kwargs ):
653
+ with rc_context (rc = {'animation.codec' : 'mpeg4' }):
654
+ super (MencoderFileWriter , self ).__init__ (* args , ** kwargs )
655
+
628
656
def _args (self ):
629
657
# Returns the command line parameters for subprocess to use
630
658
# mencoder to create a movie
@@ -836,7 +864,7 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
836
864
elif (not is_string_like (writer ) and
837
865
any (arg is not None
838
866
for arg in (fps , codec , bitrate , extra_args , metadata ))):
839
- raise RuntimeError ('Passing in values for arguments for arguments '
867
+ raise RuntimeError ('Passing in values for arguments '
840
868
'fps, codec, bitrate, extra_args, or metadata '
841
869
'is not supported when writer is an existing '
842
870
'MovieWriter instance. These should instead be '
@@ -892,26 +920,16 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
892
920
metadata = metadata )
893
921
except IndexError :
894
922
raise ValueError ("Cannot save animation: no writers are "
895
- "available. Please install mencoder or "
923
+ "available. Please install "
896
924
"ffmpeg to save animations." )
897
925
898
926
verbose .report ('Animation.save using %s' % type (writer ),
899
927
level = 'helpful' )
900
928
901
- # FIXME: Using 'bbox_inches' doesn't currently work with
902
- # writers that pipe the data to the command because this
903
- # requires a fixed frame size (see Ryan May's reply in this
904
- # thread: [1]). Thus we drop the 'bbox_inches' argument if it
905
- # exists in savefig_kwargs.
906
- #
907
- # [1] (http://matplotlib.1069221.n5.nabble.com/
908
- # Animation-class-let-save-accept-kwargs-which-
909
- # are-passed-on-to-savefig-td39627.html)
910
- #
911
- if 'bbox_inches' in savefig_kwargs and not writer .frame_size_can_vary :
929
+ if 'bbox_inches' in savefig_kwargs :
912
930
warnings .warn ("Warning: discarding the 'bbox_inches' argument in "
913
- "'savefig_kwargs' as it not supported by "
914
- "{0})." . format ( writer . __class__ . __name__ ) )
931
+ "'savefig_kwargs' as it may cause frame size "
932
+ "to vary, which is inappropriate for animation." )
915
933
savefig_kwargs .pop ('bbox_inches' )
916
934
917
935
# Create a new sequence of frames for saved data. This is different
@@ -921,12 +939,10 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
921
939
# since GUI widgets are gone. Either need to remove extra code to
922
940
# allow for this non-existent use case or find a way to make it work.
923
941
with rc_context ():
924
- # See above about bbox_inches savefig kwarg
925
- if (not writer .frame_size_can_vary and
926
- rcParams ['savefig.bbox' ] == 'tight' ):
927
- verbose .report ("Disabling savefig.bbox = 'tight', as it is "
928
- "not supported by "
929
- "{0}." .format (writer .__class__ .__name__ ),
942
+ if (rcParams ['savefig.bbox' ] == 'tight' ):
943
+ verbose .report ("Disabling savefig.bbox = 'tight', as it "
944
+ "may cause frame size to vary, which "
945
+ "is inappropriate for animation." ,
930
946
level = 'helpful' )
931
947
rcParams ['savefig.bbox' ] = None
932
948
with writer .saving (self ._fig , filename , dpi ):
0 commit comments