Skip to content

Commit 0c11fd7

Browse files
committed
Adjust figure size for h264; deprecate mencoder
1 parent 1f74976 commit 0c11fd7

File tree

1 file changed

+49
-34
lines changed

1 file changed

+49
-34
lines changed

lib/matplotlib/animation.py

+49-34
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import contextlib
3737
import tempfile
3838
import warnings
39-
from matplotlib.cbook import iterable, is_string_like
39+
from matplotlib.cbook import iterable, is_string_like, deprecated
4040
from matplotlib.compat import subprocess
4141
from matplotlib import verbose
4242
from matplotlib import rcParams, rcParamsDefault, rc_context
@@ -60,6 +60,12 @@
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
6470
class 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):
316331
class 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,18 @@ def output_args(self):
560573
return args
561574

562575

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+
564580
@writers.register('mencoder')
565581
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+
566588
def _args(self):
567589
# Returns the command line parameters for subprocess to use
568590
# mencoder to create a movie
@@ -577,6 +599,11 @@ def _args(self):
577599
class MencoderFileWriter(FileMovieWriter, MencoderBase):
578600
supported_formats = ['png', 'jpeg', 'tga', 'sgi']
579601

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+
580607
def _args(self):
581608
# Returns the command line parameters for subprocess to use
582609
# mencoder to create a movie
@@ -788,7 +815,7 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
788815
elif (not is_string_like(writer) and
789816
any(arg is not None
790817
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 '
792819
'fps, codec, bitrate, extra_args, or metadata '
793820
'is not supported when writer is an existing '
794821
'MovieWriter instance. These should instead be '
@@ -844,26 +871,16 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
844871
metadata=metadata)
845872
except IndexError:
846873
raise ValueError("Cannot save animation: no writers are "
847-
"available. Please install mencoder or "
874+
"available. Please install "
848875
"ffmpeg to save animations.")
849876

850877
verbose.report('Animation.save using %s' % type(writer),
851878
level='helpful')
852879

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:
864881
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.")
867884
savefig_kwargs.pop('bbox_inches')
868885

869886
# 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,
873890
# since GUI widgets are gone. Either need to remove extra code to
874891
# allow for this non-existent use case or find a way to make it work.
875892
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.",
882897
level='helpful')
883898
rcParams['savefig.bbox'] = None
884899
with writer.saving(self._fig, filename, dpi):

0 commit comments

Comments
 (0)