From 949ecdd4857ccfb259b13d4ccdcea1c3b288ff23 Mon Sep 17 00:00:00 2001 From: Ryan May <rmay31@gmail.com> Date: Fri, 13 May 2016 16:36:46 -0600 Subject: [PATCH 1/4] MNT: Refactor animation handling of 'bbox_inches'. Instead of a hard-coded look for either instance types or strings, make it a property of the MovieWriter instances. Set the flag to true by default for the MovieFileWriters (which all seem to work fine) and make it false by default for MovieWriters (which are currently pipe-based and break). We can also eliminate looking at strings by doing the check after we create the instance of MovieWriter (if necessary). --- lib/matplotlib/animation.py | 52 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 93eed7b84b98..b85d7011e268 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -35,6 +35,7 @@ from base64 import encodestring as encodebytes import contextlib import tempfile +import warnings from matplotlib.cbook import iterable, is_string_like from matplotlib.compat import subprocess from matplotlib import verbose @@ -109,6 +110,11 @@ class MovieWriter(object): frame_format: string The format used in writing frame data, defaults to 'rgba' ''' + + # Specifies whether the size of all frames need to be identical + # i.e. whether we can use savefig.bbox = 'tight' + frame_size_can_vary = False + def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None, metadata=None): ''' @@ -283,6 +289,11 @@ def isAvailable(cls): class FileMovieWriter(MovieWriter): '`MovieWriter` subclass that handles writing to a file.' + + # In general, if frames are writen to files on disk, it's not important + # that they all be identically sized + frame_size_can_vary = True + def __init__(self, *args, **kwargs): MovieWriter.__init__(self, *args, **kwargs) self.frame_format = rcParams['animation.frame_format'] @@ -712,29 +723,6 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, if savefig_kwargs is None: savefig_kwargs = {} - # FIXME: Using 'bbox_inches' doesn't currently work with - # writers that pipe the data to the command because this - # requires a fixed frame size (see Ryan May's reply in this - # thread: [1]). Thus we drop the 'bbox_inches' argument if it - # exists in savefig_kwargs. - # - # [1] (http://matplotlib.1069221.n5.nabble.com/ - # Animation-class-let-save-accept-kwargs-which- - # are-passed-on-to-savefig-td39627.html) - # - if 'bbox_inches' in savefig_kwargs: - if not (writer in ['ffmpeg_file', 'mencoder_file'] or - isinstance(writer, - (FFMpegFileWriter, MencoderFileWriter))): - print("Warning: discarding the 'bbox_inches' argument in " - "'savefig_kwargs' as it is only currently supported " - "with the writers 'ffmpeg_file' and 'mencoder_file' " - "(writer used: " - "'{0}').".format( - writer if isinstance(writer, six.string_types) - else writer.__class__.__name__)) - savefig_kwargs.pop('bbox_inches') - # Need to disconnect the first draw callback, since we'll be doing # draws. Otherwise, we'll end up starting the animation. if self._first_draw_id is not None: @@ -778,7 +766,6 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, extra_args=extra_args, metadata=metadata) else: - import warnings warnings.warn("MovieWriter %s unavailable" % writer) try: @@ -792,6 +779,23 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, verbose.report('Animation.save using %s' % type(writer), level='helpful') + + # FIXME: Using 'bbox_inches' doesn't currently work with + # writers that pipe the data to the command because this + # requires a fixed frame size (see Ryan May's reply in this + # thread: [1]). Thus we drop the 'bbox_inches' argument if it + # exists in savefig_kwargs. + # + # [1] (http://matplotlib.1069221.n5.nabble.com/ + # Animation-class-let-save-accept-kwargs-which- + # are-passed-on-to-savefig-td39627.html) + # + if 'bbox_inches' in savefig_kwargs and not writer.frame_size_can_vary: + warnings.warn("Warning: discarding the 'bbox_inches' argument in " + "'savefig_kwargs' as it not supported by " + "{0}).".format(writer.__class__.__name__)) + savefig_kwargs.pop('bbox_inches') + # Create a new sequence of frames for saved data. This is different # from new_frame_seq() to give the ability to save 'live' generated # frame information to be saved later. From 885b6fdb849e273ebda8c84d84f5bc7fb914fd29 Mon Sep 17 00:00:00 2001 From: Ryan May <rmay31@gmail.com> Date: Fri, 13 May 2016 16:40:06 -0600 Subject: [PATCH 2/4] FIX: Animations should disable savefig.bbox as necessary. This fixes part of #6416 by resetting the 'savefig.bbox' rcParam if it is set to 'tight'. --- lib/matplotlib/animation.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index b85d7011e268..3cd24875f10a 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -39,7 +39,7 @@ from matplotlib.cbook import iterable, is_string_like from matplotlib.compat import subprocess from matplotlib import verbose -from matplotlib import rcParams, rcParamsDefault +from matplotlib import rcParams, rcParamsDefault, rc_context # Process creation flag for subprocess to prevent it raising a terminal # window. See for example: @@ -801,17 +801,26 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, # frame information to be saved later. # TODO: Right now, after closing the figure, saving a movie won't work # since GUI widgets are gone. Either need to remove extra code to - # allow for this non-existant use case or find a way to make it work. - with writer.saving(self._fig, filename, dpi): - for anim in all_anim: - # Clear the initial frame - anim._init_draw() - for data in zip(*[a.new_saved_frame_seq() - for a in all_anim]): - for anim, d in zip(all_anim, data): - # TODO: Need to see if turning off blit is really necessary - anim._draw_next_frame(d, blit=False) - writer.grab_frame(**savefig_kwargs) + # allow for this non-existent use case or find a way to make it work. + with rc_context(): + # See above about bbox_inches savefig kwarg + if (not writer.frame_size_can_vary and + rcParams['savefig.bbox'] == 'tight'): + verbose.report("Disabling savefig.bbox = 'tight', as it is " + "not supported by " + "{0}.".format(writer.__class__.__name__), + level='helpful') + rcParams['savefig.bbox'] = None + with writer.saving(self._fig, filename, dpi): + for anim in all_anim: + # Clear the initial frame + anim._init_draw() + for data in zip(*[a.new_saved_frame_seq() + for a in all_anim]): + for anim, d in zip(all_anim, data): + # TODO: See if turning off blit is really necessary + anim._draw_next_frame(d, blit=False) + writer.grab_frame(**savefig_kwargs) # Reconnect signal for first draw if necessary if reconnect_first_draw: From d28e85302d17a14bfab3ad51bc7cc4f9ee380092 Mon Sep 17 00:00:00 2001 From: Ryan May <rmay31@gmail.com> Date: Fri, 13 May 2016 16:44:37 -0600 Subject: [PATCH 3/4] MNT: Move comment next to code it applies to: --- lib/matplotlib/animation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 3cd24875f10a..d43d7e97c3e4 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -420,8 +420,6 @@ class FFMpegBase(object): @property def output_args(self): - # The %dk adds 'k' as a suffix so that ffmpeg treats our bitrate as in - # kbps args = ['-vcodec', self.codec] # For h264, the default format is yuv444p, which is not compatible # with quicktime (and others). Specifying yuv420p fixes playback on @@ -429,6 +427,8 @@ def output_args(self): # OSX). Also fixes internet explorer. This is as of 2015/10/29. if self.codec == 'h264' and '-pix_fmt' not in self.extra_args: args.extend(['-pix_fmt', 'yuv420p']) + # The %dk adds 'k' as a suffix so that ffmpeg treats our bitrate as in + # kbps if self.bitrate > 0: args.extend(['-b', '%dk' % self.bitrate]) if self.extra_args: From 229ddf64bac1cb11c30cd530d77b793f6df8c2d1 Mon Sep 17 00:00:00 2001 From: Ryan May <rmay31@gmail.com> Date: Fri, 13 May 2016 16:45:31 -0600 Subject: [PATCH 4/4] MNT: Fix some misspellings. --- lib/matplotlib/animation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index d43d7e97c3e4..b45d3ca3561a 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -133,8 +133,8 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None, automatically by the underlying utility. extra_args: list of strings or None A list of extra string arguments to be passed to the underlying - movie utiltiy. The default is None, which passes the additional - argurments in the 'animation.extra_args' rcParam. + movie utility. The default is None, which passes the additional + arguments in the 'animation.extra_args' rcParam. metadata: dict of string:string or None A dictionary of keys and values for metadata to include in the output file. Some keys that may be of use include: @@ -702,8 +702,8 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, `animation.bitrate`. *extra_args* is a list of extra string arguments to be passed to the - underlying movie utiltiy. The default is None, which passes the - additional argurments in the 'animation.extra_args' rcParam. + underlying movie utility. The default is None, which passes the + additional arguments in the 'animation.extra_args' rcParam. *metadata* is a dictionary of keys and values for metadata to include in the output file. Some keys that may be of use include: @@ -947,7 +947,7 @@ def to_html5_video(self): directly into the HTML5 video tag. This respects the rc parameters for the writer as well as the bitrate. This also makes use of the ``interval`` to control the speed, and uses the ``repeat`` - paramter to decide whether to loop. + parameter to decide whether to loop. ''' VIDEO_TAG = r'''<video {size} {options}> <source type="video/mp4" src="data:video/mp4;base64,{video}">