36
36
import contextlib
37
37
import tempfile
38
38
import warnings
39
- from matplotlib .cbook import iterable , is_string_like
39
+ from matplotlib .cbook import iterable , is_string_like , deprecated
40
40
from matplotlib .compat import subprocess
41
41
from matplotlib import verbose
42
42
from matplotlib import rcParams , rcParamsDefault , rc_context
60
60
# how-to-encode-series-of-images-into-h264-using-x264-api-c-c )
61
61
62
62
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
+
63
69
# A registry for available MovieWriter classes
64
70
class MovieWriterRegistry (object ):
65
71
def __init__ (self ):
@@ -134,10 +140,6 @@ class MovieWriter(object):
134
140
The format used in writing frame data, defaults to 'rgba'
135
141
'''
136
142
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
-
141
143
def __init__ (self , fps = 5 , codec = None , bitrate = None , extra_args = None ,
142
144
metadata = None ):
143
145
'''
@@ -189,8 +191,20 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
189
191
@property
190
192
def frame_size (self ):
191
193
'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' )
194
208
195
209
def setup (self , fig , outfile , dpi ):
196
210
'''
@@ -207,6 +221,7 @@ def setup(self, fig, outfile, dpi):
207
221
self .outfile = outfile
208
222
self .fig = fig
209
223
self .dpi = dpi
224
+ self ._adjust_frame_size ()
210
225
211
226
# Run here so that grab_frame() can write the data to a pipe. This
212
227
# eliminates the need for temp files.
@@ -316,10 +331,6 @@ def isAvailable(cls):
316
331
class FileMovieWriter (MovieWriter ):
317
332
'`MovieWriter` subclass that handles writing to a file.'
318
333
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
-
323
334
def __init__ (self , * args , ** kwargs ):
324
335
MovieWriter .__init__ (self , * args , ** kwargs )
325
336
self .frame_format = rcParams ['animation.frame_format' ]
@@ -345,6 +356,8 @@ def setup(self, fig, outfile, dpi, frame_prefix='_tmp', clear_temp=True):
345
356
self .fig = fig
346
357
self .outfile = outfile
347
358
self .dpi = dpi
359
+ self ._adjust_frame_size ()
360
+
348
361
self .clear_temp = clear_temp
349
362
self .temp_prefix = frame_prefix
350
363
self ._frame_counter = 0 # used for generating sequential file names
@@ -500,7 +513,7 @@ class FFMpegFileWriter(FileMovieWriter, FFMpegBase):
500
513
def _args (self ):
501
514
# Returns the command line parameters for subprocess to use
502
515
# 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
504
517
'-i' , self ._base_temp_name (),
505
518
'-vframes' , str (self ._frame_counter ),
506
519
'-r' , str (self .fps )] + self .output_args
@@ -560,9 +573,18 @@ def output_args(self):
560
573
return args
561
574
562
575
563
- # Combine Mencoder options with pipe-based writing
576
+ mencoder_dep = """Support for mencoder is only partially functional,
577
+ and will be removed entirely in 2.2. Please use ffmpeg instead."""
578
+
579
+
564
580
@writers .register ('mencoder' )
565
581
class MencoderWriter (MovieWriter , MencoderBase ):
582
+
583
+ @deprecated ('2.0' , message = mencoder_dep )
584
+ def __init__ (self , * args , ** kwargs ):
585
+ with rc_context (rc = {'animation.codec' : 'mpeg4' }):
586
+ super (MencoderWriter , self ).__init__ (* args , ** kwargs )
587
+
566
588
def _args (self ):
567
589
# Returns the command line parameters for subprocess to use
568
590
# mencoder to create a movie
@@ -577,6 +599,11 @@ def _args(self):
577
599
class MencoderFileWriter (FileMovieWriter , MencoderBase ):
578
600
supported_formats = ['png' , 'jpeg' , 'tga' , 'sgi' ]
579
601
602
+ @deprecated ('2.0' , message = mencoder_dep )
603
+ def __init__ (self , * args , ** kwargs ):
604
+ with rc_context (rc = {'animation.codec' : 'mpeg4' }):
605
+ super (MencoderFileWriter , self ).__init__ (* args , ** kwargs )
606
+
580
607
def _args (self ):
581
608
# Returns the command line parameters for subprocess to use
582
609
# mencoder to create a movie
@@ -788,7 +815,7 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
788
815
elif (not is_string_like (writer ) and
789
816
any (arg is not None
790
817
for arg in (fps , codec , bitrate , extra_args , metadata ))):
791
- raise RuntimeError ('Passing in values for arguments for arguments '
818
+ raise RuntimeError ('Passing in values for arguments '
792
819
'fps, codec, bitrate, extra_args, or metadata '
793
820
'is not supported when writer is an existing '
794
821
'MovieWriter instance. These should instead be '
@@ -844,26 +871,16 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
844
871
metadata = metadata )
845
872
except IndexError :
846
873
raise ValueError ("Cannot save animation: no writers are "
847
- "available. Please install mencoder or "
874
+ "available. Please install "
848
875
"ffmpeg to save animations." )
849
876
850
877
verbose .report ('Animation.save using %s' % type (writer ),
851
878
level = 'helpful' )
852
879
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 :
880
+ if 'bbox_inches' in savefig_kwargs :
864
881
warnings .warn ("Warning: discarding the 'bbox_inches' argument in "
865
- "'savefig_kwargs' as it not supported by "
866
- "{0})." . format ( writer . __class__ . __name__ ) )
882
+ "'savefig_kwargs' as it may cause frame size "
883
+ "to vary, which is inappropriate for animation." )
867
884
savefig_kwargs .pop ('bbox_inches' )
868
885
869
886
# Create a new sequence of frames for saved data. This is different
@@ -873,12 +890,10 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
873
890
# since GUI widgets are gone. Either need to remove extra code to
874
891
# allow for this non-existent use case or find a way to make it work.
875
892
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__ ),
893
+ if (rcParams ['savefig.bbox' ] == 'tight' ):
894
+ verbose .report ("Disabling savefig.bbox = 'tight', as it "
895
+ "may cause frame size to vary, which "
896
+ "is inappropriate for animation." ,
882
897
level = 'helpful' )
883
898
rcParams ['savefig.bbox' ] = None
884
899
with writer .saving (self ._fig , filename , dpi ):
0 commit comments