import os import sys import random import string import warnings if sys.version_info < (3, 0): from cStringIO import StringIO as InMemory else: from io import BytesIO as InMemory from matplotlib.animation import writers, FileMovieWriter from base64 import b64encode ICON_DIR = os.path.join(os.path.dirname(__file__), 'icons') class _Icons(object): """This class is a container for base64 representations of the icons""" icons = ['first', 'prev', 'reverse', 'pause', 'play', 'next', 'last'] def __init__(self, icon_dir=ICON_DIR, extension='png'): self.icon_dir = icon_dir self.extension = extension for icon in self.icons: setattr(self, icon, self._load_base64('{0}.{1}'.format(icon, extension))) def _load_base64(self, filename): data = open(os.path.join(self.icon_dir, filename), 'rb').read() return 'data:image/{0};base64,{1}'.format(self.extension, b64encode(data).decode('ascii')) JS_INCLUDE = """ """ DISPLAY_TEMPLATE = """


Once Loop Reflect
""" INCLUDED_FRAMES = """ for (var i=0; i<{Nframes}; i++){{ frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) + ".{frame_format}"; }} """ def _included_frames(frame_list, frame_format): """frame_list should be a list of filenames""" return INCLUDED_FRAMES.format(Nframes=len(frame_list), frame_dir=os.path.dirname(frame_list[0]), frame_format=frame_format) def _embedded_frames(frame_list, frame_format): """frame_list should be a list of base64-encoded png files""" template = ' frames[{0}] = "data:image/{1};base64,{2}"\n' embedded = "\n" for i, frame_data in enumerate(frame_list): embedded += template.format(i, frame_format, frame_data.replace('\n', '\\\n')) return embedded @writers.register('html') class HTMLWriter(FileMovieWriter): # we start the animation id count at a random number: this way, if two # animations are meant to be included on one HTML page, there is a # very small chance of conflict. rng = random.Random() exec_key = 'animation.ffmpeg_path' args_key = 'animation.ffmpeg_args' supported_formats = ['png', 'jpeg', 'tiff', 'svg'] @classmethod def new_id(cls): #return '%16x' % cls.rng.getrandbits(64) return ''.join(cls.rng.choice(string.ascii_uppercase) for x in range(16)) def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None, metadata=None, embed_frames=False, default_mode='loop'): self.embed_frames = embed_frames self.default_mode = default_mode.lower() if self.default_mode not in ['loop', 'once', 'reflect']: self.default_mode = 'loop' warnings.warn("unrecognized default_mode: using 'loop'") self._saved_frames = list() super(HTMLWriter, self).__init__(fps, codec, bitrate, extra_args, metadata) def setup(self, fig, outfile, dpi, frame_dir=None): if os.path.splitext(outfile)[-1] not in ['.html', '.htm']: raise ValueError("outfile must be *.htm or *.html") if not self.embed_frames: if frame_dir is None: frame_dir = outfile.rstrip('.html') + '_frames' if not os.path.exists(frame_dir): os.makedirs(frame_dir) frame_prefix = os.path.join(frame_dir, 'frame') else: frame_prefix = None super(HTMLWriter, self).setup(fig, outfile, dpi, frame_prefix, clear_temp=False) def grab_frame(self, **savefig_kwargs): if self.embed_frames: suffix = '.' + self.frame_format f = InMemory() self.fig.savefig(f, format=self.frame_format, dpi=self.dpi, **savefig_kwargs) f.seek(0) self._saved_frames.append(b64encode(f.read()).decode('ascii')) else: return super(HTMLWriter, self).grab_frame(**savefig_kwargs) def _run(self): # make a ducktyped subprocess standin # this is called by the MovieWriter base class, but not used here. class ProcessStandin(object): returncode = 0 def communicate(self): return ('', '') self._proc = ProcessStandin() # save the frames to an html file if self.embed_frames: fill_frames = _embedded_frames(self._saved_frames, self.frame_format) else: # temp names is filled by FileMovieWriter fill_frames = _included_frames(self._temp_names, self.frame_format) mode_dict = dict(once_checked='', loop_checked='', reflect_checked='') mode_dict[self.default_mode + '_checked'] = 'checked' interval = int(1000. / self.fps) with open(self.outfile, 'w') as of: of.write(JS_INCLUDE) of.write(DISPLAY_TEMPLATE.format(id=self.new_id(), Nframes=len(self._temp_names), fill_frames=fill_frames, interval=interval, icons=_Icons(), **mode_dict))