|
15 | 15 | # * Movies |
16 | 16 | # * Can blit be enabled for movies? |
17 | 17 | # * Need to consider event sources to allow clicking through multiple figures |
18 | | -from __future__ import (absolute_import, division, print_function, |
19 | | - unicode_literals) |
20 | 18 |
|
21 | 19 | import six |
22 | | -from six.moves import zip |
23 | 20 |
|
24 | 21 | import abc |
| 22 | +import base64 |
25 | 23 | import contextlib |
26 | 24 | from io import BytesIO |
27 | 25 | import itertools |
28 | 26 | import logging |
29 | 27 | import os |
| 28 | +from pathlib import Path |
30 | 29 | import platform |
31 | 30 | import subprocess |
32 | 31 | import sys |
33 | | -import tempfile |
| 32 | +from tempfile import TemporaryDirectory |
34 | 33 | import uuid |
35 | 34 |
|
36 | 35 | import numpy as np |
|
39 | 38 | JS_INCLUDE) |
40 | 39 | from matplotlib import cbook, rcParams, rcParamsDefault, rc_context |
41 | 40 |
|
42 | | -if six.PY2: |
43 | | - from base64 import encodestring as encodebytes |
44 | | -else: |
45 | | - from base64 import encodebytes |
46 | | - |
47 | 41 |
|
48 | 42 | _log = logging.getLogger(__name__) |
49 | 43 |
|
@@ -383,8 +377,7 @@ def grab_frame(self, **savefig_kwargs): |
383 | 377 | dpi=self.dpi, **savefig_kwargs) |
384 | 378 | except (RuntimeError, IOError) as e: |
385 | 379 | out, err = self._proc.communicate() |
386 | | - _log.info('MovieWriter -- Error ' |
387 | | - 'running proc:\n%s\n%s' % (out, err)) |
| 380 | + _log.info('MovieWriter -- Error running proc:\n%s\n%s', out, err) |
388 | 381 | raise IOError('Error saving animation to file (cause: {0}) ' |
389 | 382 | 'Stdout: {1} StdError: {2}. It may help to re-run ' |
390 | 383 | 'with logging level set to ' |
@@ -537,8 +530,7 @@ def grab_frame(self, **savefig_kwargs): |
537 | 530 |
|
538 | 531 | except RuntimeError: |
539 | 532 | out, err = self._proc.communicate() |
540 | | - _log.info('MovieWriter -- Error ' |
541 | | - 'running proc:\n%s\n%s' % (out, err)) |
| 533 | + _log.info('MovieWriter -- Error running proc:\n%s\n%s', out, err) |
542 | 534 | raise |
543 | 535 |
|
544 | 536 | def finish(self): |
@@ -669,7 +661,7 @@ def _args(self): |
669 | 661 | # Logging is quieted because subprocess.PIPE has limited buffer size. |
670 | 662 | # If you have a lot of frames in your animation and set logging to |
671 | 663 | # DEBUG, you will have a buffer overrun. |
672 | | - if (_log.getEffectiveLevel() > logging.DEBUG): |
| 664 | + if _log.getEffectiveLevel() > logging.DEBUG: |
673 | 665 | args += ['-loglevel', 'quiet'] |
674 | 666 | args += ['-i', 'pipe:'] + self.output_args |
675 | 667 | return args |
@@ -903,7 +895,7 @@ def grab_frame(self, **savefig_kwargs): |
903 | 895 | f = BytesIO() |
904 | 896 | self.fig.savefig(f, format=self.frame_format, |
905 | 897 | dpi=self.dpi, **savefig_kwargs) |
906 | | - imgdata64 = encodebytes(f.getvalue()).decode('ascii') |
| 898 | + imgdata64 = base64.encodebytes(f.getvalue()).decode('ascii') |
907 | 899 | self._total_bytes += len(imgdata64) |
908 | 900 | if self._total_bytes >= self._bytes_limit: |
909 | 901 | _log.warning( |
@@ -1336,35 +1328,30 @@ def to_html5_video(self, embed_limit=None): |
1336 | 1328 | # Convert from MB to bytes |
1337 | 1329 | embed_limit *= 1024 * 1024 |
1338 | 1330 |
|
1339 | | - # First write the video to a tempfile. Set delete to False |
1340 | | - # so we can re-open to read binary data. |
1341 | | - with tempfile.NamedTemporaryFile(suffix='.m4v', |
1342 | | - delete=False) as f: |
| 1331 | + # Can't open a NamedTemporaryFile twice on Windows, so use a |
| 1332 | + # TemporaryDirectory instead. |
| 1333 | + with TemporaryDirectory() as tmpdir: |
| 1334 | + path = Path(tmpdir, "temp.m4v") |
1343 | 1335 | # We create a writer manually so that we can get the |
1344 | 1336 | # appropriate size for the tag |
1345 | 1337 | Writer = writers[rcParams['animation.writer']] |
1346 | 1338 | writer = Writer(codec='h264', |
1347 | 1339 | bitrate=rcParams['animation.bitrate'], |
1348 | 1340 | fps=1000. / self._interval) |
1349 | | - self.save(f.name, writer=writer) |
1350 | | - |
1351 | | - # Now open and base64 encode |
1352 | | - with open(f.name, 'rb') as video: |
1353 | | - vid64 = encodebytes(video.read()) |
1354 | | - vid_len = len(vid64) |
1355 | | - if vid_len >= embed_limit: |
1356 | | - _log.warning( |
1357 | | - "Animation movie is %s bytes, exceeding the limit of " |
1358 | | - "%s. If you're sure you want a large animation " |
1359 | | - "embedded, set the animation.embed_limit rc parameter " |
1360 | | - "to a larger value (in MB).", vid_len, embed_limit) |
1361 | | - else: |
1362 | | - self._base64_video = vid64.decode('ascii') |
1363 | | - self._video_size = 'width="{}" height="{}"'.format( |
1364 | | - *writer.frame_size) |
1365 | | - |
1366 | | - # Now we can remove |
1367 | | - os.remove(f.name) |
| 1341 | + self.save(str(path), writer=writer) |
| 1342 | + # Now open and base64 encode. |
| 1343 | + vid64 = base64.encodebytes(path.read_bytes()) |
| 1344 | + |
| 1345 | + if len(vid64) >= embed_limit: |
| 1346 | + _log.warning( |
| 1347 | + "Animation movie is %s bytes, exceeding the limit of %s. " |
| 1348 | + "If you're sure you want a large animation embedded, set " |
| 1349 | + "the animation.embed_limit rc parameter to a larger value " |
| 1350 | + "(in MB).", vid_len, embed_limit) |
| 1351 | + else: |
| 1352 | + self._base64_video = vid64.decode('ascii') |
| 1353 | + self._video_size = 'width="{}" height="{}"'.format( |
| 1354 | + *writer.frame_size) |
1368 | 1355 |
|
1369 | 1356 | # If we exceeded the size, this attribute won't exist |
1370 | 1357 | if hasattr(self, '_base64_video'): |
@@ -1392,25 +1379,18 @@ def to_jshtml(self, fps=None, embed_frames=True, default_mode=None): |
1392 | 1379 | if default_mode is None: |
1393 | 1380 | default_mode = 'loop' if self.repeat else 'once' |
1394 | 1381 |
|
1395 | | - if hasattr(self, "_html_representation"): |
1396 | | - return self._html_representation |
1397 | | - else: |
1398 | | - # Can't open a second time while opened on windows. So we avoid |
1399 | | - # deleting when closed, and delete manually later. |
1400 | | - with tempfile.NamedTemporaryFile(suffix='.html', |
1401 | | - delete=False) as f: |
1402 | | - self.save(f.name, writer=HTMLWriter(fps=fps, |
1403 | | - embed_frames=embed_frames, |
1404 | | - default_mode=default_mode)) |
1405 | | - # Re-open and get content |
1406 | | - with open(f.name) as fobj: |
1407 | | - html = fobj.read() |
1408 | | - |
1409 | | - # Now we can delete |
1410 | | - os.remove(f.name) |
1411 | | - |
1412 | | - self._html_representation = html |
1413 | | - return html |
| 1382 | + if not hasattr(self, "_html_representation"): |
| 1383 | + # Can't open a NamedTemporaryFile twice on Windows, so use a |
| 1384 | + # TemporaryDirectory instead. |
| 1385 | + with TemporaryDirectory() as tmpdir: |
| 1386 | + path = Path(tmpdir, "temp.html") |
| 1387 | + writer = HTMLWriter(fps=fps, |
| 1388 | + embed_frames=embed_frames, |
| 1389 | + default_mode=default_mode) |
| 1390 | + self.save(str(path), writer=writer) |
| 1391 | + self._html_representation = path.read_text() |
| 1392 | + |
| 1393 | + return self._html_representation |
1414 | 1394 |
|
1415 | 1395 | def _repr_html_(self): |
1416 | 1396 | '''IPython display hook for rendering.''' |
|
0 commit comments