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,20 @@ def output_args(self):
560
573
return args
561
574
562
575
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
+
564
582
@writers .register ('mencoder' )
565
583
class 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
+
566
590
def _args (self ):
567
591
# Returns the command line parameters for subprocess to use
568
592
# mencoder to create a movie
@@ -577,6 +601,11 @@ def _args(self):
577
601
class MencoderFileWriter (FileMovieWriter , MencoderBase ):
578
602
supported_formats = ['png' , 'jpeg' , 'tga' , 'sgi' ]
579
603
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
+
580
609
def _args (self ):
581
610
# Returns the command line parameters for subprocess to use
582
611
# mencoder to create a movie
@@ -788,7 +817,7 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
788
817
elif (not is_string_like (writer ) and
789
818
any (arg is not None
790
819
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 '
792
821
'fps, codec, bitrate, extra_args, or metadata '
793
822
'is not supported when writer is an existing '
794
823
'MovieWriter instance. These should instead be '
@@ -844,26 +873,16 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
844
873
metadata = metadata )
845
874
except IndexError :
846
875
raise ValueError ("Cannot save animation: no writers are "
847
- "available. Please install mencoder or "
876
+ "available. Please install "
848
877
"ffmpeg to save animations." )
849
878
850
879
verbose .report ('Animation.save using %s' % type (writer ),
851
880
level = 'helpful' )
852
881
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 :
864
883
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." )
867
886
savefig_kwargs .pop ('bbox_inches' )
868
887
869
888
# 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,
873
892
# since GUI widgets are gone. Either need to remove extra code to
874
893
# allow for this non-existent use case or find a way to make it work.
875
894
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." ,
882
899
level = 'helpful' )
883
900
rcParams ['savefig.bbox' ] = None
884
901
with writer .saving (self ._fig , filename , dpi ):
0 commit comments