Skip to content

Python API

What an episode imports, and the tools an agent framework calls. Everything here is also reachable from the command line.

Episodes

manimvox.kit.episode.Episode

Bases: Scene

narrate

narrate(text, tts=None, pad=PAD)

Play the voice line; animations inside the block run over it. On exit, waits until the line (plus pad) has finished.

say

say(text, *anims, run_time=1.0, tts=None, pad=PAD, lag_ratio=0.0)

Voice line + animations played together at its start.

steps

steps(text, anims, run_time=0.7, tts=None, pad=PAD, lead=0.0)

Voice line with anims spread evenly across it, the last one finishing a moment before the line ends. Each item is an Animation or a list of Animations played together.

beat

beat(seconds=1.0)

A silent hold after a key reveal, so the picture lands before the next line.

stage

stage()

Everything on screen except the header and progress bar.

clear

clear(shift=LEFT * 0.7, run_time=0.55)

Transition: sweep the stage away.

intro

intro(narration, tts=None, question=None)

Title card, then it settles into the persistent header. Anything drawn before intro() (a cold open) is swept away first. question replaces the tagline on the card and is what outro(answer=...) answers.

motif

motif()

The title card's decoration, bottom right. The project's own mark when series.json gives one (theme.mark_svg, drawn in the accent), else two rings.

chapter

chapter(title, narration=None, tts=None, bridge=None, bridge_tts=None)

Transition into a new chapter: big title, then it docks top-right. bridge is a "So... / But..." line spoken over the finished picture before it is cleared.

outro

outro(recap, narration, next_title=None, tts=None, next_label='NEXT', answer=None, answer_tts=None)

Recap bullets and a pointer to the next episode. The last episode can point elsewhere instead: next_label="READ", next_title="docs/design.md". answer is spoken first, on screen under the question given to intro(). An empty recap skips the Recap heading and bullets, keeps the answer on screen, and narration is still spoken over it.

swap

swap(old, new, run_time=0.7)

Cross-fade one mobject into another at the same place.

pulse

pulse(path_mobs, color=ACCENT, run_time=1.6)

Send a glowing dot along a chain of mobjects (centre to centre).

manimvox.kit.episode.Episode3D

Bases: Episode, ThreeDScene

An Episode with Manim's 3D camera: tilt and turn it with move_camera(), set_camera_orientation() and begin_ambient_camera_rotation(). The header, chapter label and progress bar stay put on screen. intro(), chapter() and outro() clear the stage, then put the camera back to the flat view, so their cards are never tilted. Text placed in the 3D scene turns with it; pin() a label to keep it flat.

pin

pin(*mobs)

Keep mobs where they are on screen while the camera moves. Call it before revealing them.

camera_home

camera_home(run_time=1.0)

Turn the camera back to the flat view, as an animation (to end a 3D scene on a flat picture).

Components

Diagram components. Each returns a VGroup the episode positions.

chip

chip(text, color=ACCENT, size=16, filled=False, weight=BOLD)

Small pill label.

card

card(title, body=None, w=3.8, color=BLUE, title_size=24, body_size=17, pad=0.26, fill=PANEL, icon=None)

Titled panel with an accent bar. body is a string or list of strings.

node

node(label, sub=None, w=2.6, h=None, color=BLUE, size=21, sub_size=14, fill=PANEL, label_color=INK)

Compact labelled box for diagrams.

arrow

arrow(a, b, color=MUTED, label=None, buff=0.12, sw=3.0, label_size=14, curve=0.0, dashed=False, label_color=None, label_above=False)

Arrow from the edge of a to the edge of b (mobjects or points). The label sits on the line; label_above=True lifts it clear, for arrows too short to show both.

gnode

gnode(name, color=BLUE, shape='circle', r=0.32, size=17, mono=True)

A node drawn the way a product's graph view draws one: a filled shape with the name beneath. shape is "circle" or "square". .shape is the shape, .label the name; edges should meet .shape, not the group.

gedge

gedge(a, b, label=None, color=LINE, size=13)

Directed edge between two gnodes, rim to rim, label above its middle.

bullets

bullets(items, size=24, color=INK, gap=0.26, max_w=9.5, dot=ACCENT)

Bulleted list. Returns a VGroup of rows (reveal them one by one).

stat

stat(value, label, color=ACCENT, w=3.0, vsize=52, lsize=16, fill=PANEL)

Big number over a caption.

table

table(rows, col_w, size=18, row_h=0.6, header=True, header_color=ACCENT, colors=None, mono_cols=())

Simple table. Returns VGroup of rows; each row = VGroup(bg, *cells).

code

code(src, size=17, title=None, w=None, colors=None, line_spacing=-1)

Monospace panel. colors maps substrings to colours (t2c). line_spacing above 0 opens the lines up (try 0.8) for code read aloud line by line.

flow

flow(labels, w=2.2, color=BLUE, gap=0.55, size=19, subs=None, max_w=SAFE_W)

Horizontal chain of nodes joined by arrows. Returns (nodes, arrows).

heading

heading(text, size=38, color=INK, sub=None, sub_size=20)

Scene heading for the top of the stage (already positioned).

note

note(text, color=MUTED, size=15)

Small source / caveat line pinned to the bottom of the stage.

lifeline

lifeline(label, x, top=2.2, bottom=-3.2, color=BLUE, w=2.3, size=18)

Sequence-diagram column: a header box at top and a dashed line down to bottom. Returns VGroup(head, line); .x is the column's x.

message

message(a, b, y, label=None, color=MUTED, dashed=False, size=15)

Horizontal sequence-diagram message from lifeline a to lifeline b at height y. A reply is usually dashed=True. Label sits above the arrow.

3D meshes

manimvox.kit.mesh

3D models as triangles: a glTF, GLB, OBJ, STL or PLY file, or arrays, read into a Mesh the episode can draw.

Mesh dataclass

Triangles with Manim's z up: vertices (n, 3), faces (m, 3) indices into them, and per face its colors as #RRGGBB (None when the source has none) and the name of the parts it came from.

load_mesh

load_mesh(source, max_faces: int = 1500, up: str = 'y') -> Mesh

Read a 3D model: a .glb, .gltf, .obj, .stl or .ply file (export Blender scenes as glTF), or (vertices, faces) arrays with optional per-face colours. up names the source's up axis, "y" (glTF, OBJ) or "z" (STL, CAD); it becomes Manim's z, which points up once the camera tilts. A model with more than max_faces triangles (at most 5000) is simplified to that many. Files and simplifying need the trimesh extra. Raises ValueError for input it cannot draw.

Clips: frames from other libraries

manimvox.scenes.clip.Clip

Bases: ImageMobject

Frames on the stage. Move and scale it like any mobject; self.play(clip.play(), run_time=...) plays it. Its width is set in scene units, so a draft and a final render place it the same.

Positions on a clip are pixels of its source, counted from the top left. space is the source's size when the frames were scaled down from it, so positions read off the original screenshot or recording still hold.

shown property

shown: int

The index of the frame on screen.

show

show(i: int) -> Clip

Display frame i (a negative index counts from the end).

at

at(x: float, y: float) -> np.ndarray

The scene point over pixel (x, y) of the source, counted from the top left: where to put a label or an arrow tip on what the clip shows. It follows the clip when it moves, scales or zooms.

visible

visible(x: float, y: float, w: float, h: float) -> tuple[float, float, float, float] | None

The part of region (x, y, w, h) inside the current view, or None when none of it is.

framing

framing(x: float, y: float, w: float, h: float, pad: float = ZOOM_PAD) -> tuple[float, ...]

The view that shows region (x, y, w, h) with a margin, in the clip's own shape and inside the source.

zoom

zoom(x: float, y: float, w: float, h: float, pad: float = ZOOM_PAD, **kwargs) -> Zoom

The animation that zooms into region (x, y, w, h) of the source inside the clip's own frame, so the clip never grows past its place on the stage. Make overlays after it: they point at the zoomed picture.

zoom_out

zoom_out(**kwargs) -> Zoom

The animation back to the whole picture.

loop

loop() -> Clip

Keep playing the frames, over and over, under whatever the scene plays or waits next (a model turning while the narration goes on). clip.clear_updaters() stops it on the frame it shows.

play

play(run_time: float | None = None, start: float = 0.0, end: float | None = None, **kwargs) -> PlayClip

The animation that runs through the frames from start to end seconds of the clip (default: all of it) over run_time seconds (default: that stretch's own length, so a recording plays at real speed). Play one recording in parts, one part per narrated line.

manimvox.scenes.raster

Frames from Pillow or numpy (from_frames), from pycairo (from_cairo), one image (still), a screenshot file (screenshot) and a screen recording (from_video).

from_frames

from_frames(draw: Callable[[float], object], seconds: float, fps: float = 15, width: float = DEFAULT_WIDTH) -> Clip

draw(t) returns the frame at t seconds as a PIL image or an (h, w, 3|4) uint8 array, every frame one size.

from_cairo

from_cairo(draw: Callable, seconds: float, fps: float = 15, size: tuple[int, int] = (1280, 720), width: float = DEFAULT_WIDTH) -> Clip

draw(ctx, t, w, h) paints the frame at t seconds on a fresh, transparent canvas of size pixels.

still

still(image, width: float = DEFAULT_WIDTH) -> Clip

One image (PIL or array) as a one-frame Clip, sized in scene units like every clip.

screenshot

screenshot(path, width: float = DEFAULT_WIDTH) -> Clip

A screenshot file (PNG, JPEG, WebP) as a one-frame Clip. Positions on it (at, zoom, the overlays in manimvox.scenes) are the image's own pixels, so read them off the file.

from_video

from_video(path, fps: float = 15, start: float = 0.0, end: float | None = None, width: float = DEFAULT_WIDTH, max_width: int = 1280) -> Clip

A screen recording (MP4, WebM, MOV) from start to end seconds as a Clip of fps frames a second. Frames wider than max_width pixels are scaled down to save memory, but positions stay in the recording's own pixels. Play it with clip.play(), which runs at the recording's speed by default.

manimvox.scenes.charts

Charts in the theme: Matplotlib and Plotly figures as Clips, coloured with the project's roles.

palette

palette(theme=None) -> dict[str, str]

Every theme role and named colour, by lower-case name.

series_colours

series_colours(theme=None) -> list[str]

The colours a chart gives its first, second, … series.

mpl_style

mpl_style(theme=None) -> dict

Matplotlib rcParams in the theme. Build the figure inside with plt.rc_context(mpl_style()):.

from_matplotlib

from_matplotlib(obj, fps: float = 15, width: float = DEFAULT_WIDTH, dpi: int = 150) -> Clip

A Matplotlib Figure as one frame, or a FuncAnimation as all of its frames at fps.

plotly_template

plotly_template(theme=None) -> dict

A Plotly template in the theme: fig.update_layout(template=plotly_template()).

from_plotly

from_plotly(fig, seconds: float | None = None, fps: float = 10, size: tuple[int, int] = (1280, 720), width: float = DEFAULT_WIDTH) -> Clip

A Plotly figure as one frame, or fig(t) (a function returning a figure) as seconds of frames at fps. Kaleido exports the frames, and it needs Chrome on the machine.

manimvox.scenes.model

A 3D model rendered with real lighting and materials (PyVista, on VTK) as a Clip, the camera orbiting it.

Each frame is drawn from its own time: the camera angle and the model's animation are set for t, then the frame is rendered. Nothing runs on a clock, so a draft and a final render show the same frames.

from_pyvista

from_pyvista(draw, seconds: float, fps: float = 15, size: tuple[int, int] = (1280, 720), width: float = DEFAULT_WIDTH, setup=None) -> Clip

Frames of a PyVista scene on a transparent background: setup(plotter) once (add meshes, lights), then draw(plotter, t) before each frame at t seconds (move the camera, swap a clipped mesh, recolour). Set the camera's position, focal_point and up in draw: PyVista's default up is z. The same plotter serves every frame, so meshes added in setup stay. Needs the pyvista extra and OpenGL.

screen_xy

screen_xy(plotter, point) -> tuple[float, float]

Where the 3D point shows in the plotter's frame, in pixels from the top left: give it to clip.at(x, y) to place a label or a path on the rendered picture. Call it inside draw, after the camera is set for that frame.

from_model

from_model(path, seconds: float = 4, fps: float = 15, size: tuple[int, int] = (1280, 720), width: float = DEFAULT_WIDTH, orbit: float = 60, azimuth: float = -30, elevation: float = 20, zoom: float = 1.0, animation: int | str | None = 0, loop: bool = False, up: str = 'y', color: str | None = None) -> Clip

The model in path (.glb or .gltf with its materials and animations; .obj, .stl or .ply in color, default the theme accent) rendered with lighting on a transparent background, seconds long at fps. The camera starts azimuth degrees round the model and elevation degrees above it, and turns orbit degrees over the clip (0 holds still; 360 loops seamlessly); zoom above 1 moves it closer. up is the model's up axis, "y" for glTF and OBJ, "z" for STL and CAD. A glTF animation clip (an index or a name; None for none) plays from its start in step with the clip, holding its last pose, or repeating if loop. Needs the pyvista extra and OpenGL.

Screenshots and screen recordings

manimvox.scenes.screen

Overlays for screenshots and screen recordings: a spotlight on a region, a callout, a cursor that points and clicks, and window chrome. Regions are (x, y, w, h) in the source's pixels, as Clip.at takes them, and every overlay is placed for the clip's current position and zoom: make it after the clip has moved or zoomed.

box_on

box_on(clip: Clip, region, color=ACCENT, sw: float = 4, r: float = 0.08, pad: float = 0.06) -> RoundedRectangle

An outline around region, a little outside it so it doesn't cover the region's own edge.

spotlight

spotlight(clip: Clip, region, opacity: float = 0.72, color=ACCENT, sw: float = 4) -> VGroup

Dims the clip except region, and outlines the region. Returns a VGroup with .mask and .ring: FadeIn it to focus, FadeOut it before the next spotlight or zoom.

callout

callout(clip: Clip, region, text: str, side: str = 'right', color=ACCENT, size: int = 18, gap: float = 1.0) -> VGroup

A label gap scene units off side ("right", "left", "up", "down") of region, with an arrow to it, kept on the stage. Returns a VGroup with .label, .arrow and .ring (the region's outline).

cursor

cursor(color=INK, outline=BG, size: float = 0.42) -> VGroup

A mouse pointer whose tip is the point it points at (.tip). Place it with point.

point

point(pointer: VGroup, target, **kwargs)

The animation that moves pointer (from cursor) so its tip lands on target, a scene point or a mobject's centre; for a spot on a clip, pass clip.at(x, y).

click

click(pointer: VGroup, color=ACCENT, run_time: float = 0.6) -> AnimationGroup

The pointer presses where its tip is now, with a short flash around the spot: play it after the point that moved it there, not alongside.

window

window(clip: Clip, url: str | None = None, title: str | None = None, bar: float = 0.42) -> VGroup

Browser or app chrome around clip: a title bar above it with three dots and the url (or title), and a border. Make it after placing the clip; group them to move both (Group(window(clip), clip)). Returns a VGroup with .bar, .border and .label (None without a url or title).

Tools for agents

Typed functions that mirror the manimvox command, for agents with no shell.

Every tool takes the project folder (or any folder inside it) as project, where the command uses the current folder. It returns a dataclass of plain values, so dataclasses.asdict gives JSON. A failed check is a result with ok=False and its findings, as the command exits 1. A tool that cannot do what was asked raises ToolError, whose message says what to do first. Nothing here prompts: a download goes ahead only with allow_download=True.

ToolError

Bases: Exception

A tool could not do what was asked. The message says why, and what to run first.

add_block

add_block(project: str, names: list[str], force: bool = False) -> AddResult

Copy blocks into the project's blocks/ folder; an episode then imports them with each block's import_line.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required
names list[str]

Block names from list_blocks, e.g. ["bar-ranking"].

required
force bool

Replace a copy already in the project, losing any edits made to it.

False

check

check(project: str, what: str, episode: str | None = None) -> CheckResult

Run one check: theme (colour contrast and fonts), layout (the last draft) or audio (every line audible).

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required
what str

theme, layout or audio.

required
episode str | None

The episode, for layout and audio: ep01, 1, or its file stem.

None

contact_sheet

contact_sheet(project: str, episode: str, every: float = 5.0) -> SheetsResult

Write contact sheets (3 by 3 grids of frames) of an episode, from out/ if rendered, else the last draft.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required
episode str

The episode as listed in series.json: ep01, 1, or its file stem.

required
every float

Seconds between sampled frames; above 0.

5.0

doctor

doctor() -> DoctorResult

Check Python, cairo, Pango and its fonts, Manim, the chart extras and every voice engine; a FAIL row names its fix.

draft

draft(project: str, episode: str, every: float = 5.0) -> DraftResult

Render a fast 480p draft with no voice, write contact sheets and run the layout check.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required
episode str

The episode as listed in series.json: ep01, 1, or its file stem.

required
every float

Seconds between the frames sampled into the contact sheets; above 0.

5.0

index

index(project: str) -> IndexResult

Write the playlist page out/index.html from the rendered episodes.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required

join

join(project: str, episodes: list[str] | None = None, output: str | None = None, cards: bool = True) -> JoinResult

Join rendered episodes into one MP4 behind a title card, with the subtitles shifted to match.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required
episodes list[str] | None

The episodes in order; by default every rendered episode in series.json order.

None
output str | None

The joined MP4, relative to the project folder or absolute; by default out/.mp4.

None
cards bool

False for no title card. Each episode keeps its own end card either way.

True

list_blocks

list_blocks() -> BlocksResult

List the blocks: ready-made charts and diagrams that add_block copies into a project.

new_project

new_project(directory: str, series: str, tagline: str = '', voice: str = 'kokoro:af_heart') -> ProjectResult

Create a project folder with series.json, FACTS.md, SERIES.md, episodes/, out/ and research/.

Parameters:

Name Type Description Default
directory str

The folder to create, best given as an absolute path; it must not already hold a project.

required
series str

The series name shown on every title card, e.g. "Hash Maps, Explained".

required
tagline str

A short line shown under the series name.

''
voice str

The narration voice as engine:voice.

'kokoro:af_heart'

package

package(project: str) -> PackageResult

Write share// and share/-720p/ with a zip of each, from the rendered episodes.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required

prepare_voice

prepare_voice(voice: str, project: str | None = None, allow_download: bool = False) -> PrepareResult

Get a voice engine, or one of its voices, ready to render: checks the voice and fetches a missing model.

Parameters:

Name Type Description Default
voice str

An engine (kokoro) or engine:voice (kokoro:af_heart).

required
project str | None

The project folder, where a cloning voice's reference recording is found.

None
allow_download bool

True only when the user has agreed to download the model.

False

render

render(project: str, episode: str, voice: str | None = None, allow_download: bool = False) -> RenderResult

Render the final 1080p episode with the voice, apply series.json post, and run the audio check; takes minutes.

Parameters:

Name Type Description Default
project str

The project folder, or any folder inside it.

required
episode str

The episode as listed in series.json: ep01, 1, or its file stem.

required
voice str | None

engine:voice for this render; by default series.json's voice, then MANIMVOX_VOICE, then kokoro:af_heart.

None
allow_download bool

True only when the user has agreed to download the voice model if it is missing.

False

voices

voices(engine: str | None = None) -> VoicesResult

List the voice engines (ready or not, licence, whether narration text leaves the machine), or one engine's voices.

Parameters:

Name Type Description Default
engine str | None

An engine name such as kokoro, to list its voices; leave it out to list the engines.

None