3737import contextlib
3838import tempfile
3939import warnings
40- from matplotlib .cbook import iterable , is_string_like
40+ from matplotlib .cbook import iterable , is_string_like , deprecated
4141from matplotlib .compat import subprocess
4242from matplotlib import verbose
4343from matplotlib import rcParams , rcParamsDefault , rc_context
6161# how-to-encode-series-of-images-into-h264-using-x264-api-c-c )
6262
6363
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+
6470# A registry for available MovieWriter classes
6571class MovieWriterRegistry (object ):
6672 def __init__ (self ):
@@ -194,10 +200,6 @@ class MovieWriter(AbstractMovieWriter):
194200 The format used in writing frame data, defaults to 'rgba'
195201 '''
196202
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-
201203 def __init__ (self , fps = 5 , codec = None , bitrate = None , extra_args = None ,
202204 metadata = None ):
203205 '''
@@ -249,8 +251,20 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
249251 @property
250252 def frame_size (self ):
251253 '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' )
254268
255269 def setup (self , fig , outfile , dpi ):
256270 '''
@@ -267,6 +281,7 @@ def setup(self, fig, outfile, dpi):
267281 self .outfile = outfile
268282 self .fig = fig
269283 self .dpi = dpi
284+ self ._adjust_frame_size ()
270285
271286 # Run here so that grab_frame() can write the data to a pipe. This
272287 # eliminates the need for temp files.
@@ -364,10 +379,6 @@ def isAvailable(cls):
364379class FileMovieWriter (MovieWriter ):
365380 '`MovieWriter` subclass that handles writing to a file.'
366381
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-
371382 def __init__ (self , * args , ** kwargs ):
372383 MovieWriter .__init__ (self , * args , ** kwargs )
373384 self .frame_format = rcParams ['animation.frame_format' ]
@@ -393,6 +404,8 @@ def setup(self, fig, outfile, dpi, frame_prefix='_tmp', clear_temp=True):
393404 self .fig = fig
394405 self .outfile = outfile
395406 self .dpi = dpi
407+ self ._adjust_frame_size ()
408+
396409 self .clear_temp = clear_temp
397410 self .temp_prefix = frame_prefix
398411 self ._frame_counter = 0 # used for generating sequential file names
@@ -448,10 +461,9 @@ def grab_frame(self, **savefig_kwargs):
448461 try :
449462 # Tell the figure to save its data to the sink, using the
450463 # 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 )
455467
456468 except RuntimeError :
457469 out , err = self ._proc .communicate ()
@@ -548,7 +560,7 @@ class FFMpegFileWriter(FileMovieWriter, FFMpegBase):
548560 def _args (self ):
549561 # Returns the command line parameters for subprocess to use
550562 # 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
552564 '-i' , self ._base_temp_name (),
553565 '-vframes' , str (self ._frame_counter ),
554566 '-r' , str (self .fps )] + self .output_args
@@ -608,9 +620,20 @@ def output_args(self):
608620 return args
609621
610622
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+
612629@writers .register ('mencoder' )
613630class 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+
614637 def _args (self ):
615638 # Returns the command line parameters for subprocess to use
616639 # mencoder to create a movie
@@ -625,6 +648,11 @@ def _args(self):
625648class MencoderFileWriter (FileMovieWriter , MencoderBase ):
626649 supported_formats = ['png' , 'jpeg' , 'tga' , 'sgi' ]
627650
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+
628656 def _args (self ):
629657 # Returns the command line parameters for subprocess to use
630658 # mencoder to create a movie
@@ -836,7 +864,7 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
836864 elif (not is_string_like (writer ) and
837865 any (arg is not None
838866 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 '
840868 'fps, codec, bitrate, extra_args, or metadata '
841869 'is not supported when writer is an existing '
842870 'MovieWriter instance. These should instead be '
@@ -892,26 +920,16 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
892920 metadata = metadata )
893921 except IndexError :
894922 raise ValueError ("Cannot save animation: no writers are "
895- "available. Please install mencoder or "
923+ "available. Please install "
896924 "ffmpeg to save animations." )
897925
898926 verbose .report ('Animation.save using %s' % type (writer ),
899927 level = 'helpful' )
900928
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 :
912930 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." )
915933 savefig_kwargs .pop ('bbox_inches' )
916934
917935 # 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,
921939 # since GUI widgets are gone. Either need to remove extra code to
922940 # allow for this non-existent use case or find a way to make it work.
923941 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." ,
930946 level = 'helpful' )
931947 rcParams ['savefig.bbox' ] = None
932948 with writer .saving (self ._fig , filename , dpi ):
0 commit comments