Skip to main content

praxinoscope

Storyboard-driven animated explainer videos in pure Python.

A storyboard is a plain YAML file: ordered scenes, each with a scene type, typed fields, captions in one or more languages, a duration and source references. praxinoscope renders it deterministically, with Cairo drawing, Pango text and ffmpeg encoding, into MP4, WebM or GIF at 16:9, 9:16 or 1:1. The same storyboard and theme always produce the same frames.

Status: milestones 1 and 2 of the project brief are done, plus the rest of the starter scene types and six chart scenes. That covers the engine core, seventeen scene types (title, end, counter, merge/split, timeline, journey, grid, compare, stages, quote, place, ranking, line, donut, seats, waterfall, diverging), the Bauhaus and Muted themes, the storyboard schema and loader, an end-to-end render, and a small CLI. Ingest, outline and the AI adapter come later.

AI agents: start with llms.txt, or run praxinoscope guide.

Install

System libraries (not on PyPI): Pango with GObject introspection, and ffmpeg.

# Debian/Ubuntu
sudo apt install ffmpeg gir1.2-pango-1.0 python3-gi python3-gi-cairo
# macOS
brew install ffmpeg pango pygobject3
pip install praxinoscope    # pycairo, PyGObject, numpy, pydantic, PyYAML
python -c "import praxinoscope; praxinoscope.fetch_fonts()"   # Noto Sans SC for Chinese (~17 MB)

PyGObject and pycairo build against the system libraries. If the build fails, create the virtual environment with --system-site-packages so it uses the distribution's python3-gi and python3-cairo instead.

Jost ships in the package. See FONTS.md for font licenses.

Quick start

praxinoscope example board.yaml       # starter storyboard
praxinoscope check board.yaml         # every problem at once, with fix hints
praxinoscope preview board.yaml       # sheet.png: one still per scene
praxinoscope render board.yaml out.mp4 --aspect 9:16

From Python, each step is one call and takes a path, YAML text, a dict or a list:

import praxinoscope as px

px.check("board.yaml")            # [] when it will render
px.render("board.yaml", "out.mp4", aspect="9:16", scale=0.5)

Project gives the same with more control:

from praxinoscope import Project

p = Project.load("examples/sail/storyboard.yaml")
p.check()                         # field validation, caption overflow, glyph coverage
p.preview("sheet.png")            # contact sheet: one still per scene
p.render("sail.mp4")              # 1080p, with music and sound cues
p.render("sail-vertical.mp4", aspect="9:16")
p.render("sail.gif", scale=0.5)

Storyboard

version: 1
meta:
  title: "SAIL"
  languages: [en]          # caption languages; with two, e.g. [en, fr], *_alt fields use the second
  aspect: "16:9"            # 16:9 | 9:16 | 1:1
  theme: bauhaus
  fps: 30
  eyebrow: "Chapter 1"
scenes:
  - id: red-storm           # stable slug
    type: merge
    fields: {count: 27, unit: nations, result: RED STORM}
    captions:
      en: "2021: 27 nations launch Red Storm, the first crewed Mars mission."
    year: "2021"            # shown in the year chip; omit to hide it
    sources: [p3.s1, p3.s2] # traceability back to the input text
    duration: auto          # reading-time based, or seconds
    pinned: false           # protect from regeneration (used by later stages)

Validation reports every problem at once, with paths such as scenes[1] (red-storm).fields.count, and suggests fixes for typos (unknown field 'valeu' (did you mean 'value'?)). praxinoscope.json_schema() returns the JSON Schema, with each scene type's fields spelled out, for editors and structured AI output.

The same storyboard in the compact form, which loads identically:

title: "SAIL"
scenes:
  - {type: merge, id: red-storm, count: 27, unit: nations, result: RED STORM, year: "2021",
     caption: "2021: 27 nations launch Red Storm, the first crewed Mars mission."}

Scene fields sit on the scene, id defaults to <type>-<n>, caption: "text" is the first-language caption, and meta keys may sit at the top level. praxinoscope expand prints the canonical form.

Scene types

praxinoscope scenes prints the full reference, generated from the code.

type fields
title title, title_alt, subtitle, kicker
end title, title_alt, lines (up to 6)
counter value, start, decimals, prefix, suffix, unit, unit_alt, label, label_alt
merge count, unit, unit_alt, result, result_alt, mode (merge or split)
timeline events (2 to 8 of date, label, label_alt), highlight
journey origin, origin_alt, destination, destination_alt, via (up to 3), distance, distance_alt
grid count or items (up to 12 names), highlight, label, label_alt
compare left, right (each title, title_alt, value, prefix, suffix, decimals, note, note_alt), mode (versus or before-after)
stages steps (2 to 6 of title, title_alt, note), current
quote text, text_alt, attribution, attribution_alt, marks
place name, name_alt, detail, detail_alt, coordinates, nearby (up to 4)
ranking items (2 to 10 of label, label_alt, value), order, highlight
line values, start_label, end_label, callout (at, text, text_alt), from_zero
donut parts (2 to 6 of label, label_alt, value), center, center_alt, show, highlight
seats parties (1 to 8 of label, label_alt, seats), majority, majority_label
waterfall steps (2 to 9 of label, label_alt, value, total)
diverging items (2 to 10, values may be negative), baseline, baseline_alt, sort

The chart scenes (ranking to diverging) also take label, label_alt (a heading) and prefix, suffix, decimals (how numbers print), except seats, which takes only the heading. grid takes shape: square, person, house or circle. Their designs follow the FT Visual Vocabulary.

examples/tour/storyboard.yaml uses every scene type once, and examples/charts/storyboard.yaml shows the chart scenes with invented data; render either with theme="bauhaus" or theme="muted" to compare themes.

Architecture

praxinoscope/
  schema.py        storyboard model (pydantic v2), versioned
  loader.py        YAML/JSON load + save, scene field resolution, error collection
  timing.py        auto durations (reading time per script), frame-exact timeline
  render.py        composition: background, scene, chrome, transition; video, stills, contact sheet, audio
  project.py       high-level Project API
  api.py           one-call check/render/preview, generated scene reference
  cli.py           praxinoscope command
  plugins.py       scene/theme registries + entry points
  engine/          tween, layout (Frame/Rect/anchors/grids), Pango text, color, ffmpeg encoder, numpy audio
  scenes/          SceneType contract + built-in scenes
  themes/          Theme contract + Bauhaus

Scenes draw only through the theme (ctx.theme.entity(...), ctx.text(..., role), ctx.ease(t, start, dur, "emphasis")), and they position things with the stage rect and frame units, never with pixels. A theme supplies the palette, text roles, shape vocabulary, easings, the chrome (header, eyebrow, year chip, caption band), the transition and the sound palette.

The shape vocabulary every theme provides (the base Theme has plain fallbacks for all of them):

shape is used by
entity something that acts; emphasis=True for "the one" title, end, merge, compare
item a thing in a collection; emphasis / dim for highlighting grid
figure a pictogram: person, house or circle grid
place a location journey, place
connector a link from A toward B stages, compare
marker a node on a line, active or still ahead; also a seat or a legend swatch timeline, stages, compare, seats, donut
rule a plain line: axis, divider, attribution dash timeline, quote, place
route a travelled path (an engine.path.Polyline) journey
traveller what moves along a route, pointing along its heading journey
pin a map pin journey, place
halo a ring for pulses and "you are here" timeline, grid, stages, place
panel a card grouping content compare
bar a quantity bar; emphasis for a total or the one pointed at compare, ranking, waterfall, diverging
slice a ring or pie segment donut
series a data line (a Polyline) line
axis a chart baseline or zero line line, waterfall, diverging
callout an annotation box with a pointer line
quote_mark an opening quotation mark quote
on_accent(i) the text color that reads on accent(i) stages, compare

Themes may also define the text roles quote, quote_alt and value; a theme without them falls back to display_alt, subtitle and display.

Writing a scene type

from praxinoscope import register_scene
from praxinoscope.scenes import Fields, SceneType

class DotsFields(Fields):
    n: int = 3

@register_scene
class Dots(SceneType[DotsFields]):
    name = "dots"
    Fields = DotsFields
    min_duration = 3.0

    def draw(self, ctx, t):
        for i, cell in enumerate(ctx.stage.cols(*[1] * self.f.n)):
            k = ctx.ease(t, 0.2 * i, 0.6, "emphasis")
            ctx.theme.entity(ctx.cr, cell.center, cell.short * 0.5 * k, i)

Writing a theme

Subclass praxinoscope.themes.Theme (or an existing theme), set name, and override palette, roles, regions, chrome, transition, shapes and sounds as needed. Register it with @register_theme, or in another package with an entry point:

[project.entry-points."praxinoscope.themes"]
swiss = "mypkg.themes:Swiss"

Development

git clone https://github.com/emptymalei/praxinoscope && cd praxinoscope
pip install -e '.[dev]'
pytest

Documentation

The docs site is built with Zensical from docs/ and zensical.toml; the API reference is generated from docstrings.

pip install -e '.[docs]'
zensical serve            # http://localhost:8000, rebuilds on save
zensical build            # static site in site/

Releasing

Set __version__ in src/praxinoscope/__init__.py, merge, then push a matching tag (git tag v0.1.0 && git push origin v0.1.0). The Release workflow builds the sdist and wheel, renders a test video from the installed wheel, and publishes to PyPI through trusted publishing. Running the workflow by hand publishes to TestPyPI instead.

Decisions taken as defaults

These are open questions from the brief, settled for now and easy to revisit:

  • Name: praxinoscope, after Émile Reynaud's 1877 animation device. It was free on PyPI on 2026-10-03.
  • License: Apache-2.0.
  • Pango binding: PyGObject PangoCairo.
  • Schema: pydantic v2, version: 1.

License

Apache-2.0. Bundled fonts keep their own licenses (see FONTS.md).

Metadata

Release files for praxinoscope 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for praxinoscope 0.1.0
File Size Uploaded
praxinoscope-0.1.0.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for praxinoscope 0.1.0
File Interpreter ABI Platform
praxinoscope-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / praxinoscope-0.1.0.tar.gz

Download URL praxinoscope-0.1.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
6457a8b5240bb8d562ba836524de789a148c5539fd733391297595299621af7b
BLAKE2b-256 checksum
How to use checksums
c5bf89e68c894c1efe1cbcc5e62c66b77db24242928d11cd15e1f936cc09b5db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / praxinoscope-0.1.0-py3-none-any.whl

Download URL praxinoscope-0.1.0-py3-none-any.whl
Size 169.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
af0fed317300b778c3711698859045bdfee397d439ee28c3121fd96281febb2a
BLAKE2b-256 checksum
How to use checksums
8f3187efc84a874c5be3c47da392095fcb635e692b6374c84da35c9d1b254e97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page