From 755fc9e6e2546730e52fdac1e36f6d22ee17171c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 1 Dec 2020 18:45:30 -0500 Subject: [PATCH 1/4] Copy logging setup from FFMpegWriter to FFMpegFileWriter. --- lib/matplotlib/animation.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 48a3ae41ed09..c8cc43faf19c 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -634,9 +634,15 @@ class FFMpegFileWriter(FFMpegBase, FileMovieWriter): def _args(self): # Returns the command line parameters for subprocess to use # ffmpeg to create a movie using a collection of temp images - return [self.bin_path(), '-r', str(self.fps), + args = [self.bin_path(), '-r', str(self.fps), '-i', self._base_temp_name(), - '-vframes', str(self._frame_counter)] + self.output_args + '-vframes', str(self._frame_counter)] + # Logging is quieted because subprocess.PIPE has limited buffer size. + # If you have a lot of frames in your animation and set logging to + # DEBUG, you will have a buffer overrun. + if _log.getEffectiveLevel() > logging.DEBUG: + args += ['-loglevel', 'error'] + return [*args, *self.output_args] # Base class of avconv information. AVConv has identical arguments to FFMpeg. From 97a2b369be9beac3a9c62df8f48ab52da9f4a9ea Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 1 Dec 2020 19:20:44 -0500 Subject: [PATCH 2/4] Support raw/rgba frame format in FFMpegFileWriter. --- lib/matplotlib/animation.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index c8cc43faf19c..f7b54861f27d 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -634,15 +634,23 @@ class FFMpegFileWriter(FFMpegBase, FileMovieWriter): def _args(self): # Returns the command line parameters for subprocess to use # ffmpeg to create a movie using a collection of temp images - args = [self.bin_path(), '-r', str(self.fps), - '-i', self._base_temp_name(), - '-vframes', str(self._frame_counter)] + args = [] + # For raw frames, we need to explicitly tell ffmpeg the metadata. + if self.frame_format in {'raw', 'rgba'}: + args += [ + '-f', 'image2', '-vcodec', 'rawvideo', + '-video_size', '%dx%d' % self.frame_size, + '-pixel_format', 'rgba', + '-framerate', str(self.fps), + ] + args += ['-r', str(self.fps), '-i', self._base_temp_name(), + '-vframes', str(self._frame_counter)] # Logging is quieted because subprocess.PIPE has limited buffer size. # If you have a lot of frames in your animation and set logging to # DEBUG, you will have a buffer overrun. if _log.getEffectiveLevel() > logging.DEBUG: args += ['-loglevel', 'error'] - return [*args, *self.output_args] + return [self.bin_path(), *args, *self.output_args] # Base class of avconv information. AVConv has identical arguments to FFMpeg. From 892273340c3393be93c0d0ac72646e0b1283bbd9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Dec 2020 18:40:43 -0500 Subject: [PATCH 3/4] Remove unsupported animation frame formats. Since `supported_formats` determines the valid `frame_format` values, and the latter is passed to `Figure.savefig`, the former must be a subset of the file formats to which Matplotlib supports saving things. --- lib/matplotlib/animation.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index f7b54861f27d..063648403646 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -628,8 +628,7 @@ class FFMpegFileWriter(FFMpegBase, FileMovieWriter): Frames are written to temporary files on disk and then stitched together at the end. """ - supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp', - 'pbm', 'raw', 'rgba'] + supported_formats = ['png', 'jpeg', 'tiff', 'raw', 'rgba'] def _args(self): # Returns the command line parameters for subprocess to use @@ -759,8 +758,7 @@ class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter): together at the end. """ - supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp', - 'pbm', 'raw', 'rgba'] + supported_formats = ['png', 'jpeg', 'tiff', 'raw', 'rgba'] def _args(self): # Force format: ImageMagick does not recognize 'raw'. From 6e3864afc1842cee5dfd9c8313a6508a9945323b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Dec 2020 18:54:58 -0500 Subject: [PATCH 4/4] TST: Check all supported animation frame formats. --- lib/matplotlib/tests/test_animation.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index b241b916cc0b..8b35d0767d78 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -141,19 +141,31 @@ def isAvailable(cls): ('html', 'movie.html'), ('null', 'movie.null') ] -WRITER_OUTPUT += [ - (writer, Path(output)) for writer, output in WRITER_OUTPUT] + + +def gen_writers(): + for writer, output in WRITER_OUTPUT: + if not animation.writers.is_available(writer): + mark = pytest.mark.skip( + f"writer '{writer}' not available on this system") + yield pytest.param(writer, None, output, marks=[mark]) + yield pytest.param(writer, None, Path(output), marks=[mark]) + continue + + writer_class = animation.writers[writer] + for frame_format in getattr(writer_class, 'supported_formats', [None]): + yield writer, frame_format, output + yield writer, frame_format, Path(output) # Smoke test for saving animations. In the future, we should probably # design more sophisticated tests which compare resulting frames a-la # matplotlib.testing.image_comparison -@pytest.mark.parametrize('writer, output', WRITER_OUTPUT) +@pytest.mark.parametrize('writer, frame_format, output', gen_writers()) @pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim']) -def test_save_animation_smoketest(tmpdir, writer, output, anim): - if not animation.writers.is_available(writer): - pytest.skip("writer '%s' not available on this system" % writer) - +def test_save_animation_smoketest(tmpdir, writer, frame_format, output, anim): + if frame_format is not None: + plt.rcParams["animation.frame_format"] = frame_format anim = animation.FuncAnimation(**anim) dpi = None codec = None