Skip to content

Animation Module — Save / Embed Helpers for Any FuncAnimation#

The cleopatra.glyphs.base.animation module exposes cleopatra's animation save / inline-embed machinery as glyph-independent helpers. They operate on any matplotlib.animation.FuncAnimation — a sine wave, stock prices, or a map — not only on a Glyph's internal self.anim:

  • save_animation(anim, path, fps=2, ...) writes an animation to a file, choosing the writer from the extension: gif and webp via Pillow, mov/avi/mp4 via FFmpeg. The extension is matched case-insensitively. Quality controls are keyword-only: crf / bitrate (mutually exclusive), codec, preset, pix_fmt, and dpi for the FFmpeg formats; optimize and loop for the Pillow (GIF/WebP) formats; plus extra_args passed straight through to the writer.
  • to_bytes(anim, fmt="gif", fps=2, ...) renders to in-memory bytes in any supported format (temp file cleaned up afterwards); to_gif(...) and to_mp4(...) are thin wrappers for the two common formats.
  • embed_gif(anim, fps=2) returns an IPython.display.Image for inline notebook display. IPython is imported lazily (and is bundled with Jupyter, so any notebook already has it); if it is absent, embed_gif raises a clear ModuleNotFoundError with a pip install ipython hint — or use to_gif for raw bytes with no IPython dependency.

SUPPORTED_VIDEO_FORMAT is ["gif", "mov", "avi", "mp4", "webp"]. Glyph.save_animation delegates to save_animation, so the writer/format logic has a single source of truth. Downstream packages that build their own FuncAnimation can reuse these helpers instead of re-rolling temp-file + writer + IPython.display glue.

Usage#

import matplotlib
matplotlib.use("Agg")  # any backend; Agg shown for headless rendering
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

from cleopatra.glyphs.base.animation import embed_gif, save_animation, to_gif

# Build any FuncAnimation — no Glyph required.
fig, ax = plt.subplots()
(line,) = ax.plot([0, 1], [0, 0])


def update(i):
    line.set_ydata([0, i])
    return (line,)


anim = FuncAnimation(fig, update, frames=3, blit=True)

save_animation(anim, "wave.gif", fps=3)   # write to a file (gif/webp/mov/avi/mp4)
save_animation(anim, "wave.mp4", fps=3, crf=18)  # quality-controlled MP4
gif_bytes = to_gif(anim, fps=3)           # in-memory GIF bytes
embed_gif(anim, fps=3)                     # inline in a notebook cell

Note

The output format is taken from the file extension and is matched case-insensitively (out.GIF works). gif / webp are written with Pillow (no FFmpeg needed). Video formats (mov/avi/mp4) use FFmpeg: cleopatra depends on imageio-ffmpeg, which bundles a static FFmpeg binary, so video export works out of the box — a system FFmpeg on the PATH is used in preference when present. A FileNotFoundError (pointing at https://ffmpeg.org/) is raised only if no FFmpeg — bundled or system — is available; an unsupported extension raises a ValueError. embed_gif imports IPython only when called; if IPython is absent it raises a ModuleNotFoundError with a pip install ipython hint (use to_gif to avoid IPython).

Module Documentation#

cleopatra.glyphs.base.animation #

Save/embed helpers for matplotlib animations (glyph-independent).

These helpers operate on any matplotlib.animation.FuncAnimation, not only on a Glyph's internal self.anim. Saving or embedding an animation is generic matplotlib machinery — it works on a sine wave, stock prices, or a map — so it lives here alongside the glyph classes that produce animations. Downstream packages that build their own FuncAnimation can reuse cleopatra's writer/format handling instead of re-rolling temp-file + writer + IPython.display glue.

Glyph.save_animation delegates to save_animation below, so the writer/format logic has a single source of truth.

embed_gif(anim, fps=2) #

Return an IPython.display.Image of the animation for inline display.

IPython is imported lazily, so importing cleopatra never requires it. IPython ships with Jupyter, so any notebook already has it; outside a notebook the returned Image is not renderable anyway — use to_gif for raw bytes with no IPython dependency.

Parameters:

Name Type Description Default
anim FuncAnimation

The animation to embed.

required
fps int

Frames per second. Default is 2.

2

Returns:

Type Description
Image

An IPython.display.Image wrapping the rendered GIF, ready to be

Image

returned as the last expression of a notebook cell.

Raises:

Type Description
ModuleNotFoundError

If IPython is not installed, with a hint to pip install ipython (or to use to_gif instead).

Examples:

  • Wrap an animation as an inline image and read back its payload:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import embed_gif
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> img = embed_gif(anim)
    >>> img.format
    'gif'
    >>> img.data[:6] in (b"GIF87a", b"GIF89a")
    True
    >>> plt.close(fig)
    
  • Returning the image as a cell's last expression renders it inline; a custom fps controls playback speed:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import embed_gif
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> img = embed_gif(anim, fps=3)
    >>> len(img.data) > 0
    True
    >>> plt.close(fig)
    
See Also

to_gif: Produce the underlying GIF bytes without IPython. save_animation: Write the animation to a file path instead.

Source code in src/cleopatra/glyphs/base/animation.py
def embed_gif(anim: FuncAnimation, fps: int = 2) -> Image:
    """Return an `IPython.display.Image` of the animation for inline display.

    IPython is imported lazily, so importing cleopatra never requires it.
    IPython ships with Jupyter, so any notebook already has it; outside a
    notebook the returned `Image` is not renderable anyway — use `to_gif`
    for raw bytes with no IPython dependency.

    Args:
        anim: The animation to embed.
        fps: Frames per second. Default is 2.

    Returns:
        An `IPython.display.Image` wrapping the rendered GIF, ready to be
        returned as the last expression of a notebook cell.

    Raises:
        ModuleNotFoundError: If IPython is not installed, with a hint to
            `pip install ipython` (or to use `to_gif` instead).

    Examples:
        - Wrap an animation as an inline image and read back its payload:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import embed_gif
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> img = embed_gif(anim)
            >>> img.format
            'gif'
            >>> img.data[:6] in (b"GIF87a", b"GIF89a")
            True
            >>> plt.close(fig)

            ```
        - Returning the image as a cell's last expression renders it inline;
          a custom `fps` controls playback speed:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import embed_gif
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> img = embed_gif(anim, fps=3)
            >>> len(img.data) > 0
            True
            >>> plt.close(fig)

            ```

    See Also:
        to_gif: Produce the underlying GIF bytes without IPython.
        save_animation: Write the animation to a file path instead.
    """
    try:
        from IPython.display import Image
    except ModuleNotFoundError as e:
        if e.name and e.name.split(".")[0] != "IPython":
            raise
        raise ModuleNotFoundError(
            "embed_gif requires IPython for inline display. Install it with "
            "`pip install ipython` (already present in any Jupyter/IPython "
            "environment). For raw GIF bytes without IPython, use to_gif()."
        ) from e

    return Image(data=to_gif(anim, fps=fps), format="gif")

save_animation(anim, path, fps=2, *, crf=None, bitrate=None, codec=None, preset=None, pix_fmt='yuv420p', dpi=None, optimize=True, loop=0, extra_args=None) #

Save any FuncAnimation to a file.

The output format is determined by the file extension. GIF and animated WebP use an optimising Pillow writer; mov/avi/mp4 use FFmpeg. FFmpeg is located on PATH when present and otherwise falls back to the binary bundled with imageio-ffmpeg, so video export works with no separate install. WebP is typically 3-5x smaller than GIF for photographic frames.

Note: when no system FFmpeg is found, the first video export sets matplotlib's global rcParams["animation.ffmpeg_path"] to the bundled binary — a process-wide side effect that then applies to any later matplotlib animation in the same process.

For the FFmpeg formats the frame is automatically padded up to an even width/height (libx264 rejects odd dimensions) and encoded with pix_fmt=yuv420p for universal playback. By default no fixed bitrate is requested (unlike older versions, which forced 1800 kbit/s), so libx264 uses its constant-quality default of roughly CRF 23 — pass crf or bitrate to trade size against quality. GIF output is written with Pillow's optimize pass enabled; both GIF and WebP loop forever by default.

Parameters:

Name Type Description Default
anim FuncAnimation

The animation to save.

required
path str | PathLike

Output file path, as a str or os.PathLike (e.g. a pathlib.Path). Extension determines format. Supported: gif, mov, avi, mp4, webp.

required
fps int

Frames per second. Default is 2.

2
crf int | None

Constant Rate Factor for the ffmpeg formats (lower is higher quality/larger; ~18-28 is typical). Assumes an x264/x265-family codec. Mutually exclusive with bitrate. Ignored for GIF/WebP. None uses the encoder default.

None
bitrate int | None

Target bitrate in kbit/s for the ffmpeg formats. Mutually exclusive with crf. Ignored for GIF/WebP. None lets the encoder choose.

None
codec str | None

ffmpeg codec (e.g. "libx264"). None uses matplotlib's default. Ignored for GIF/WebP.

None
preset str | None

libx264/libx265 speed/size preset (e.g. "slow"); ignored by codecs that don't accept it. Ignored for GIF/WebP.

None
pix_fmt str

Pixel format for the ffmpeg formats. Defaults to "yuv420p" for universal playback. Ignored for GIF/WebP.

'yuv420p'
dpi int | None

Resolution in dots per inch. None uses the figure's dpi.

None
optimize bool

GIF only — run Pillow's palette optimisation pass (a no-op for WebP, whose encoder ignores it). Default True.

True
loop int

GIF/WebP only — number of times to loop; 0 loops forever.

0
extra_args list[str] | None

Extra ffmpeg flags. A -vf filter here is merged with the automatic even-dimension pad and a -pix_fmt overrides pix_fmt. Note these flags bypass the crf/bitrate exclusivity check, so don't smuggle a conflicting -b:v/-crf through here. Ignored for GIF/WebP.

None

Returns:

Type Description
str

The output path as a str (the os.fspath of path),

str

convenient for chaining. Note a pathlib.Path argument comes

str

back as its string form, not the original object.

Raises:

Type Description
ValueError

If the file format is not supported, or if both crf and bitrate are given (competing rate-control modes).

FileNotFoundError

If a video format is requested but neither a system FFmpeg nor imageio-ffmpeg's bundled binary can be found.

Examples:

  • Save a tiny animation to a GIF; the call returns the path it wrote:
    >>> import os, shutil, tempfile, matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from pathlib import Path
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import save_animation
    >>> tmp = tempfile.mkdtemp()
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> path = os.path.join(tmp, "wave.gif")
    >>> save_animation(anim, path) == path
    True
    >>> Path(path).read_bytes()[:6] in (b"GIF87a", b"GIF89a")
    True
    >>> plt.close(fig)
    >>> shutil.rmtree(tmp)
    
  • The extension is matched case-insensitively, so .GIF also works:
    >>> import os, shutil, tempfile, matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import save_animation
    >>> tmp = tempfile.mkdtemp()
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> save_animation(anim, os.path.join(tmp, "WAVE.GIF")).endswith("WAVE.GIF")
    True
    >>> plt.close(fig)
    >>> shutil.rmtree(tmp)
    
  • An unsupported extension raises ValueError before writing (here the animation is rendered once first, so nothing is left dangling):
    >>> import os, shutil, tempfile, matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import save_animation
    >>> tmp = tempfile.mkdtemp()
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> _ = save_animation(anim, os.path.join(tmp, "ok.gif"))
    >>> save_animation(anim, "movie.webm")  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    ValueError: ...not supported...
    >>> plt.close(fig)
    >>> shutil.rmtree(tmp)
    
See Also

to_gif: Render an animation to in-memory GIF bytes instead of a file. embed_gif: Wrap an animation as an IPython.display.Image.

Source code in src/cleopatra/glyphs/base/animation.py
def save_animation(
    anim: FuncAnimation,
    path: str | os.PathLike,
    fps: int = 2,
    *,
    crf: int | None = None,
    bitrate: int | None = None,
    codec: str | None = None,
    preset: str | None = None,
    pix_fmt: str = "yuv420p",
    dpi: int | None = None,
    optimize: bool = True,
    loop: int = 0,
    extra_args: list[str] | None = None,
) -> str:
    """Save any `FuncAnimation` to a file.

    The output format is determined by the file extension. GIF and animated
    WebP use an optimising Pillow writer; mov/avi/mp4 use FFmpeg. FFmpeg is
    located on `PATH` when present and otherwise falls back to the binary
    bundled with `imageio-ffmpeg`, so video export works with no separate
    install. WebP is typically 3-5x smaller than GIF for photographic frames.

    Note: when no system FFmpeg is found, the first video export sets
    matplotlib's global `rcParams["animation.ffmpeg_path"]` to the bundled
    binary — a process-wide side effect that then applies to any later
    matplotlib animation in the same process.

    For the FFmpeg formats the frame is automatically padded up to an even
    width/height (libx264 rejects odd dimensions) and encoded with
    `pix_fmt=yuv420p` for universal playback. By default no fixed bitrate is
    requested (unlike older versions, which forced 1800 kbit/s), so libx264
    uses its constant-quality default of roughly CRF 23 — pass `crf` or
    `bitrate` to trade size against quality. GIF output is written with
    Pillow's `optimize` pass enabled; both GIF and WebP loop forever by
    default.

    Args:
        anim: The animation to save.
        path: Output file path, as a `str` or `os.PathLike` (e.g. a
            `pathlib.Path`). Extension determines format.
            Supported: gif, mov, avi, mp4, webp.
        fps: Frames per second. Default is 2.
        crf: Constant Rate Factor for the ffmpeg formats (lower is higher
            quality/larger; ~18-28 is typical). Assumes an x264/x265-family
            `codec`. Mutually exclusive with `bitrate`. Ignored for
            GIF/WebP. `None` uses the encoder default.
        bitrate: Target bitrate in kbit/s for the ffmpeg formats. Mutually
            exclusive with `crf`. Ignored for GIF/WebP. `None` lets the
            encoder choose.
        codec: ffmpeg codec (e.g. `"libx264"`). `None` uses matplotlib's
            default. Ignored for GIF/WebP.
        preset: libx264/libx265 speed/size preset (e.g. `"slow"`); ignored by
            codecs that don't accept it. Ignored for GIF/WebP.
        pix_fmt: Pixel format for the ffmpeg formats. Defaults to
            `"yuv420p"` for universal playback. Ignored for GIF/WebP.
        dpi: Resolution in dots per inch. `None` uses the figure's dpi.
        optimize: GIF only — run Pillow's palette optimisation pass (a no-op
            for WebP, whose encoder ignores it). Default `True`.
        loop: GIF/WebP only — number of times to loop; `0` loops forever.
        extra_args: Extra ffmpeg flags. A `-vf` filter here is merged with
            the automatic even-dimension pad and a `-pix_fmt` overrides
            `pix_fmt`. Note these flags bypass the `crf`/`bitrate`
            exclusivity check, so don't smuggle a conflicting `-b:v`/`-crf`
            through here. Ignored for GIF/WebP.

    Returns:
        The output path as a `str` (the `os.fspath` of `path`),
        convenient for chaining. Note a `pathlib.Path` argument comes
        back as its string form, not the original object.

    Raises:
        ValueError: If the file format is not supported, or if both `crf`
            and `bitrate` are given (competing rate-control modes).
        FileNotFoundError: If a video format is requested but neither a system
            FFmpeg nor imageio-ffmpeg's bundled binary can be found.

    Examples:
        - Save a tiny animation to a GIF; the call returns the path it wrote:
            ```python
            >>> import os, shutil, tempfile, matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from pathlib import Path
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import save_animation
            >>> tmp = tempfile.mkdtemp()
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> path = os.path.join(tmp, "wave.gif")
            >>> save_animation(anim, path) == path
            True
            >>> Path(path).read_bytes()[:6] in (b"GIF87a", b"GIF89a")
            True
            >>> plt.close(fig)
            >>> shutil.rmtree(tmp)

            ```
        - The extension is matched case-insensitively, so `.GIF` also works:
            ```python
            >>> import os, shutil, tempfile, matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import save_animation
            >>> tmp = tempfile.mkdtemp()
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> save_animation(anim, os.path.join(tmp, "WAVE.GIF")).endswith("WAVE.GIF")
            True
            >>> plt.close(fig)
            >>> shutil.rmtree(tmp)

            ```
        - An unsupported extension raises `ValueError` before writing (here
          the animation is rendered once first, so nothing is left dangling):
            ```python
            >>> import os, shutil, tempfile, matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import save_animation
            >>> tmp = tempfile.mkdtemp()
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> _ = save_animation(anim, os.path.join(tmp, "ok.gif"))
            >>> save_animation(anim, "movie.webm")  # doctest: +ELLIPSIS
            Traceback (most recent call last):
                ...
            ValueError: ...not supported...
            >>> plt.close(fig)
            >>> shutil.rmtree(tmp)

            ```

    See Also:
        to_gif: Render an animation to in-memory GIF bytes instead of a file.
        embed_gif: Wrap an animation as an `IPython.display.Image`.
    """
    path = os.fspath(path)
    video_format = os.path.splitext(path)[1].lstrip(".").lower()
    if not video_format:
        raise ValueError(
            f"The output path {path!r} has no file extension; the output "
            f"format is taken from the extension, so use one of "
            f"{SUPPORTED_VIDEO_FORMAT}."
        )
    if video_format not in SUPPORTED_VIDEO_FORMAT:
        raise ValueError(
            f"The given extension {video_format} implies a format that is "
            f"not supported, only {SUPPORTED_VIDEO_FORMAT} are supported"
        )

    if crf is not None and bitrate is not None:
        raise ValueError(
            "Pass either crf or bitrate, not both: they are competing "
            "rate-control modes for the encoder."
        )

    save_kwargs: dict[str, Any] = {} if dpi is None else {"dpi": dpi}

    if video_format in _PILLOW_FORMATS:
        anim.save(
            path,
            writer=_OptimizedPillowWriter(fps=fps, optimize=optimize, loop=loop),
            **save_kwargs,
        )
    else:
        _ensure_ffmpeg_available()
        writer_kwargs: dict[str, Any] = {
            "fps": fps,
            "extra_args": _build_ffmpeg_extra_args(pix_fmt, crf, preset, extra_args),
        }
        if bitrate is not None:
            writer_kwargs["bitrate"] = bitrate
        if codec is not None:
            writer_kwargs["codec"] = codec
        try:
            anim.save(path, writer=FFMpegWriter(**writer_kwargs), **save_kwargs)
        except FileNotFoundError as e:
            raise FileNotFoundError(
                "FFmpeg could not be run. imageio-ffmpeg's bundled binary "
                "normally makes this work out of the box; if you pinned a custom "
                "ffmpeg via matplotlib's animation.ffmpeg_path, make sure it still "
                "exists, or install a system ffmpeg from https://ffmpeg.org/."
            ) from e
    return path

to_bytes(anim, fmt='gif', fps=2, **kwargs) #

Render a FuncAnimation to in-memory bytes in any supported format.

Renders to a temporary file (the writers need a real path) and reads it back, leaving nothing on disk. Handy for embedding in a notebook or serving over HTTP.

Parameters:

Name Type Description Default
anim FuncAnimation

The animation to render.

required
fmt str

Output format — any member of SUPPORTED_VIDEO_FORMAT (e.g. "gif", "mp4", "webp"). A leading dot is tolerated.

'gif'
fps int

Frames per second. Default is 2.

2
**kwargs

Extra keyword arguments forwarded to save_animation (e.g. crf, codec, loop).

{}

Returns:

Type Description
bytes

The encoded bytes of the animation in the requested format.

Raises:

Type Description
ValueError

If fmt is not a supported format.

Examples:

  • Render to GIF bytes and inspect the payload:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_bytes
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> data = to_bytes(anim, fmt="gif")
    >>> data[:6] in (b"GIF87a", b"GIF89a")
    True
    >>> plt.close(fig)
    
  • Render to animated WebP and confirm the container magic bytes:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_bytes
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> data = to_bytes(anim, fmt="webp")
    >>> data[:4] == b"RIFF" and data[8:12] == b"WEBP"
    True
    >>> plt.close(fig)
    
  • An unsupported format raises ValueError:
    >>> from unittest.mock import MagicMock
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_bytes
    >>> to_bytes(MagicMock(spec=FuncAnimation), fmt="webm")  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    ValueError: ...not supported...
    
See Also

to_gif: Convenience wrapper for GIF bytes. to_mp4: Convenience wrapper for MP4 bytes. save_animation: Write an animation directly to a file path.

Source code in src/cleopatra/glyphs/base/animation.py
def to_bytes(anim: FuncAnimation, fmt: str = "gif", fps: int = 2, **kwargs) -> bytes:
    """Render a `FuncAnimation` to in-memory bytes in any supported format.

    Renders to a temporary file (the writers need a real path) and reads it
    back, leaving nothing on disk. Handy for embedding in a notebook or
    serving over HTTP.

    Args:
        anim: The animation to render.
        fmt: Output format — any member of `SUPPORTED_VIDEO_FORMAT` (e.g.
            `"gif"`, `"mp4"`, `"webp"`). A leading dot is tolerated.
        fps: Frames per second. Default is 2.
        **kwargs: Extra keyword arguments forwarded to `save_animation`
            (e.g. `crf`, `codec`, `loop`).

    Returns:
        The encoded bytes of the animation in the requested format.

    Raises:
        ValueError: If `fmt` is not a supported format.

    Examples:
        - Render to GIF bytes and inspect the payload:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_bytes
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> data = to_bytes(anim, fmt="gif")
            >>> data[:6] in (b"GIF87a", b"GIF89a")
            True
            >>> plt.close(fig)

            ```
        - Render to animated WebP and confirm the container magic bytes:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_bytes
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> data = to_bytes(anim, fmt="webp")
            >>> data[:4] == b"RIFF" and data[8:12] == b"WEBP"
            True
            >>> plt.close(fig)

            ```
        - An unsupported format raises `ValueError`:
            ```python
            >>> from unittest.mock import MagicMock
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_bytes
            >>> to_bytes(MagicMock(spec=FuncAnimation), fmt="webm")  # doctest: +ELLIPSIS
            Traceback (most recent call last):
                ...
            ValueError: ...not supported...

            ```

    See Also:
        to_gif: Convenience wrapper for GIF bytes.
        to_mp4: Convenience wrapper for MP4 bytes.
        save_animation: Write an animation directly to a file path.
    """
    fmt = fmt.lstrip(".").lower()
    if fmt not in SUPPORTED_VIDEO_FORMAT:
        raise ValueError(
            f"The format {fmt!r} is not supported, only "
            f"{SUPPORTED_VIDEO_FORMAT} are supported"
        )
    fd, tmp = tempfile.mkstemp(suffix=f".{fmt}")
    os.close(fd)
    try:
        save_animation(anim, tmp, fps=fps, **kwargs)
        with open(tmp, "rb") as fh:
            return fh.read()
    finally:
        os.remove(tmp)

to_gif(anim, fps=2, **kwargs) #

Render a FuncAnimation to in-memory GIF bytes.

Handy for embedding in a notebook or serving over HTTP without leaving a file on disk. Thin wrapper around to_bytes with fmt="gif".

Parameters:

Name Type Description Default
anim FuncAnimation

The animation to render.

required
fps int

Frames per second. Default is 2.

2
**kwargs

Extra keyword arguments forwarded to save_animation (e.g. optimize, loop).

{}

Returns:

Type Description
bytes

The GIF-encoded bytes of the animation.

Examples:

  • Render an animation to GIF bytes and inspect the payload:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_gif
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> data = to_gif(anim)
    >>> data[:6] in (b"GIF87a", b"GIF89a")
    True
    >>> len(data) > 0
    True
    >>> plt.close(fig)
    
  • A higher fps still yields self-contained bytes you can serve over HTTP or write yourself, without leaving a temp file behind:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_gif
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=3)
    >>> payload = to_gif(anim, fps=5)
    >>> payload.startswith((b"GIF87a", b"GIF89a"))
    True
    >>> plt.close(fig)
    
See Also

to_bytes: Render to bytes in any supported format. save_animation: Write an animation directly to a file path. embed_gif: Wrap these bytes as an IPython.display.Image.

Source code in src/cleopatra/glyphs/base/animation.py
def to_gif(anim: FuncAnimation, fps: int = 2, **kwargs) -> bytes:
    """Render a `FuncAnimation` to in-memory GIF bytes.

    Handy for embedding in a notebook or serving over HTTP without leaving
    a file on disk. Thin wrapper around `to_bytes` with `fmt="gif"`.

    Args:
        anim: The animation to render.
        fps: Frames per second. Default is 2.
        **kwargs: Extra keyword arguments forwarded to `save_animation`
            (e.g. `optimize`, `loop`).

    Returns:
        The GIF-encoded bytes of the animation.

    Examples:
        - Render an animation to GIF bytes and inspect the payload:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_gif
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> data = to_gif(anim)
            >>> data[:6] in (b"GIF87a", b"GIF89a")
            True
            >>> len(data) > 0
            True
            >>> plt.close(fig)

            ```
        - A higher `fps` still yields self-contained bytes you can serve over
          HTTP or write yourself, without leaving a temp file behind:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_gif
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=3)
            >>> payload = to_gif(anim, fps=5)
            >>> payload.startswith((b"GIF87a", b"GIF89a"))
            True
            >>> plt.close(fig)

            ```

    See Also:
        to_bytes: Render to bytes in any supported format.
        save_animation: Write an animation directly to a file path.
        embed_gif: Wrap these bytes as an `IPython.display.Image`.
    """
    return to_bytes(anim, fmt="gif", fps=fps, **kwargs)

to_mp4(anim, fps=2, **kwargs) #

Render a FuncAnimation to in-memory MP4 (H.264) bytes.

Handy for embedding a compact, universally-playable clip or serving it over HTTP without leaving a file on disk. Thin wrapper around to_bytes with fmt="mp4"; the frame is auto-padded to even dimensions and encoded yuv420p like every other MP4 export.

Parameters:

Name Type Description Default
anim FuncAnimation

The animation to render.

required
fps int

Frames per second. Default is 2.

2
**kwargs

Extra keyword arguments forwarded to save_animation (e.g. crf, bitrate, codec, preset).

{}

Returns:

Type Description
bytes

The MP4-encoded bytes of the animation.

Raises:

Type Description
FileNotFoundError

If neither a system FFmpeg nor imageio-ffmpeg's bundled binary can be found.

Examples:

  • Render to MP4 bytes and confirm the ISO base-media ftyp box:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_mp4
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> data = to_mp4(anim)
    >>> data[4:8] == b"ftyp"
    True
    >>> plt.close(fig)
    
  • Trade size for quality with a CRF and confirm non-empty output:
    >>> import matplotlib
    >>> matplotlib.use("Agg")
    >>> import matplotlib.pyplot as plt
    >>> from matplotlib.animation import FuncAnimation
    >>> from cleopatra.glyphs.base.animation import to_mp4
    >>> fig, ax = plt.subplots()
    >>> (line,) = ax.plot([0, 1], [0, 0])
    >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
    >>> data = to_mp4(anim, crf=30, preset="veryfast")
    >>> len(data) > 0
    True
    >>> plt.close(fig)
    
See Also

to_bytes: Render to bytes in any supported format. to_gif: Render an animation to in-memory GIF bytes. save_animation: Write an animation directly to a file path.

Source code in src/cleopatra/glyphs/base/animation.py
def to_mp4(anim: FuncAnimation, fps: int = 2, **kwargs) -> bytes:
    """Render a `FuncAnimation` to in-memory MP4 (H.264) bytes.

    Handy for embedding a compact, universally-playable clip or serving it
    over HTTP without leaving a file on disk. Thin wrapper around `to_bytes`
    with `fmt="mp4"`; the frame is auto-padded to even dimensions and
    encoded `yuv420p` like every other MP4 export.

    Args:
        anim: The animation to render.
        fps: Frames per second. Default is 2.
        **kwargs: Extra keyword arguments forwarded to `save_animation`
            (e.g. `crf`, `bitrate`, `codec`, `preset`).

    Returns:
        The MP4-encoded bytes of the animation.

    Raises:
        FileNotFoundError: If neither a system FFmpeg nor imageio-ffmpeg's
            bundled binary can be found.

    Examples:
        - Render to MP4 bytes and confirm the ISO base-media `ftyp` box:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_mp4
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> data = to_mp4(anim)
            >>> data[4:8] == b"ftyp"
            True
            >>> plt.close(fig)

            ```
        - Trade size for quality with a CRF and confirm non-empty output:
            ```python
            >>> import matplotlib
            >>> matplotlib.use("Agg")
            >>> import matplotlib.pyplot as plt
            >>> from matplotlib.animation import FuncAnimation
            >>> from cleopatra.glyphs.base.animation import to_mp4
            >>> fig, ax = plt.subplots()
            >>> (line,) = ax.plot([0, 1], [0, 0])
            >>> anim = FuncAnimation(fig, lambda i: (line,), frames=2)
            >>> data = to_mp4(anim, crf=30, preset="veryfast")
            >>> len(data) > 0
            True
            >>> plt.close(fig)

            ```

    See Also:
        to_bytes: Render to bytes in any supported format.
        to_gif: Render an animation to in-memory GIF bytes.
        save_animation: Write an animation directly to a file path.
    """
    return to_bytes(anim, fmt="mp4", fps=fps, **kwargs)