3636import contextlib
3737import tempfile
3838import warnings
39- from matplotlib .cbook import iterable , is_string_like
39+ from matplotlib .cbook import iterable , is_string_like , deprecated
4040from matplotlib .compat import subprocess
4141from matplotlib import verbose
4242from matplotlib import rcParams , rcParamsDefault , rc_context
6060# how-to-encode-series-of-images-into-h264-using-x264-api-c-c )
6161
6262
63+ def adjusted_figsize (w , h , dpi , n ):
64+ wnew = int (w * dpi / n ) * n / dpi
65+ hnew = int (h * dpi / n ) * n / dpi
66+ return wnew , hnew
67+
68+
6369# A registry for available MovieWriter classes
6470class MovieWriterRegistry (object ):
6571 def __init__ (self ):
@@ -134,10 +140,6 @@ class MovieWriter(object):
134140 The format used in writing frame data, defaults to 'rgba'
135141 '''
136142
137- # Specifies whether the size of all frames need to be identical
138- # i.e. whether we can use savefig.bbox = 'tight'
139- frame_size_can_vary = False
140-
141143 def __init__ (self , fps = 5 , codec = None , bitrate = None , extra_args = None ,
142144 metadata = None ):
143145 '''
@@ -189,8 +191,20 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
189191 @property
190192 def frame_size (self ):
191193 'A tuple (width,height) in pixels of a movie frame.'
192- width_inches , height_inches = self .fig .get_size_inches ()
193- return width_inches * self .dpi , height_inches * self .dpi
194+ w , h = self .fig .get_size_inches ()
195+ return int (w * self .dpi ), int (h * self .dpi )
196+
197+ def _adjust_frame_size (self ):
198+ if self .codec == 'h264' :
199+ wo , ho = self .fig .get_size_inches ()
200+ w , h = adjusted_figsize (wo , ho , self .dpi , 2 )
201+ if not (wo , ho ) == (w , h ):
202+ self .fig .set_size_inches (w , h , forward = True )
203+ verbose .report ('figure size (inches) has been adjusted '
204+ 'from %s x %s to %s x %s' % (wo , ho , w , h ),
205+ level = 'helpful' )
206+ verbose .report ('frame size in pixels is %s x %s' % self .frame_size ,
207+ level = 'debug' )
194208
195209 def setup (self , fig , outfile , dpi ):
196210 '''
@@ -207,6 +221,7 @@ def setup(self, fig, outfile, dpi):
207221 self .outfile = outfile
208222 self .fig = fig
209223 self .dpi = dpi
224+ self ._adjust_frame_size ()
210225
211226 # Run here so that grab_frame() can write the data to a pipe. This
212227 # eliminates the need for temp files.
@@ -316,10 +331,6 @@ def isAvailable(cls):
316331class FileMovieWriter (MovieWriter ):
317332 '`MovieWriter` subclass that handles writing to a file.'
318333
319- # In general, if frames are writen to files on disk, it's not important
320- # that they all be identically sized
321- frame_size_can_vary = True
322-
323334 def __init__ (self , * args , ** kwargs ):
324335 MovieWriter .__init__ (self , * args , ** kwargs )
325336 self .frame_format = rcParams ['animation.frame_format' ]
@@ -345,6 +356,8 @@ def setup(self, fig, outfile, dpi, frame_prefix='_tmp', clear_temp=True):
345356 self .fig = fig
346357 self .outfile = outfile
347358 self .dpi = dpi
359+ self ._adjust_frame_size ()
360+
348361 self .clear_temp = clear_temp
349362 self .temp_prefix = frame_prefix
350363 self ._frame_counter = 0 # used for generating sequential file names
@@ -500,7 +513,7 @@ class FFMpegFileWriter(FileMovieWriter, FFMpegBase):
500513 def _args (self ):
501514 # Returns the command line parameters for subprocess to use
502515 # ffmpeg to create a movie using a collection of temp images
503- return [self .bin_path (), '-r' , str ( self . fps ),
516+ return [self .bin_path (), # -r option is not needed before -i option
504517 '-i' , self ._base_temp_name (),
505518 '-vframes' , str (self ._frame_counter ),
506519 '-r' , str (self .fps )] + self .output_args
@@ -560,9 +573,20 @@ def output_args(self):
560573 return args
561574
562575
563- # Combine Mencoder options with pipe-based writing
576+ # The message must be a single line; internal newlines cause sphinx failure.
577+ mencoder_dep = ("Support for mencoder is only partially functional, "
578+ "and will be removed entirely in 2.2. "
579+ "Please use ffmpeg instead." )
580+
581+
564582@writers .register ('mencoder' )
565583class MencoderWriter (MovieWriter , MencoderBase ):
584+
585+ @deprecated ('2.0' , message = mencoder_dep )
586+ def __init__ (self , * args , ** kwargs ):
587+ with rc_context (rc = {'animation.codec' : 'mpeg4' }):
588+ super (MencoderWriter , self ).__init__ (* args , ** kwargs )
589+
566590 def _args (self ):
567591 # Returns the command line parameters for subprocess to use
568592 # mencoder to create a movie
@@ -577,6 +601,11 @@ def _args(self):
577601class MencoderFileWriter (FileMovieWriter , MencoderBase ):
578602 supported_formats = ['png' , 'jpeg' , 'tga' , 'sgi' ]
579603
604+ @deprecated ('2.0' , message = mencoder_dep )
605+ def __init__ (self , * args , ** kwargs ):
606+ with rc_context (rc = {'animation.codec' : 'mpeg4' }):
607+ super (MencoderFileWriter , self ).__init__ (* args , ** kwargs )
608+
580609 def _args (self ):
581610 # Returns the command line parameters for subprocess to use
582611 # mencoder to create a movie
@@ -788,7 +817,7 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
788817 elif (not is_string_like (writer ) and
789818 any (arg is not None
790819 for arg in (fps , codec , bitrate , extra_args , metadata ))):
791- raise RuntimeError ('Passing in values for arguments for arguments '
820+ raise RuntimeError ('Passing in values for arguments '
792821 'fps, codec, bitrate, extra_args, or metadata '
793822 'is not supported when writer is an existing '
794823 'MovieWriter instance. These should instead be '
@@ -844,26 +873,16 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
844873 metadata = metadata )
845874 except IndexError :
846875 raise ValueError ("Cannot save animation: no writers are "
847- "available. Please install mencoder or "
876+ "available. Please install "
848877 "ffmpeg to save animations." )
849878
850879 verbose .report ('Animation.save using %s' % type (writer ),
851880 level = 'helpful' )
852881
853- # FIXME: Using 'bbox_inches' doesn't currently work with
854- # writers that pipe the data to the command because this
855- # requires a fixed frame size (see Ryan May's reply in this
856- # thread: [1]). Thus we drop the 'bbox_inches' argument if it
857- # exists in savefig_kwargs.
858- #
859- # [1] (http://matplotlib.1069221.n5.nabble.com/
860- # Animation-class-let-save-accept-kwargs-which-
861- # are-passed-on-to-savefig-td39627.html)
862- #
863- if 'bbox_inches' in savefig_kwargs and not writer .frame_size_can_vary :
882+ if 'bbox_inches' in savefig_kwargs :
864883 warnings .warn ("Warning: discarding the 'bbox_inches' argument in "
865- "'savefig_kwargs' as it not supported by "
866- "{0})." . format ( writer . __class__ . __name__ ) )
884+ "'savefig_kwargs' as it may cause frame size "
885+ "to vary, which is inappropriate for animation." )
867886 savefig_kwargs .pop ('bbox_inches' )
868887
869888 # Create a new sequence of frames for saved data. This is different
@@ -873,12 +892,10 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
873892 # since GUI widgets are gone. Either need to remove extra code to
874893 # allow for this non-existent use case or find a way to make it work.
875894 with rc_context ():
876- # See above about bbox_inches savefig kwarg
877- if (not writer .frame_size_can_vary and
878- rcParams ['savefig.bbox' ] == 'tight' ):
879- verbose .report ("Disabling savefig.bbox = 'tight', as it is "
880- "not supported by "
881- "{0}." .format (writer .__class__ .__name__ ),
895+ if (rcParams ['savefig.bbox' ] == 'tight' ):
896+ verbose .report ("Disabling savefig.bbox = 'tight', as it "
897+ "may cause frame size to vary, which "
898+ "is inappropriate for animation." ,
882899 level = 'helpful' )
883900 rcParams ['savefig.bbox' ] = None
884901 with writer .saving (self ._fig , filename , dpi ):
0 commit comments