an.tools#
User-facing utility functions, plus the SSOT list for CLI dispatch.
Each function here is meant to be callable from Python and from the shell
via an <funcname>. Keep their signatures dispatch-friendly: positional args
become required arguments, defaults become optional flags, and the docstring
becomes the command’s help. an/__main__.py projects this list onto typer
without touching these functions, so they stay plain Python.
Functions
|
Render the fixed bench corpus and write a metrics ledger. |
|
Compare two ledger rows — and refuse when they are not comparable. |
|
Break each guard on purpose and check the test that names it goes red. |
|
Print a status report of all backend system + Python deps. |
|
Show what third-party work is in |
|
Create a fresh an project at |
|
Apply a free-text instruction to the scene. |
|
Live-preview the project's scene in a browser; reloads on edit. |
|
Write a shot's frame at chosen instants to a PNG, drawn the way |
The sub-namespaces installed genres added ( |
|
|
Render the project at |
|
Reconcile scene.md and ir/scene.json inside |
|
Validate the scene at |
- an.tools.bench(scenes='', out='', keep_render='', quiet=False, bless='', compare='', mutation='')[source]#
Render the fixed bench corpus and write a metrics ledger.
The instrument, not the verdict: it records numbers whose predicted direction under each deliberate degradation is declared in advance, so a future regression is caught by something other than someone noticing.
scenes: comma-separated corpus scene names (default: all of them) out: ledger path (default: misc/bench/ledger/<date>-<sha>[-dirty].json) keep_render: keep the throwaway render tree here instead of deleting it quiet: print only the ledger path bless: (re)write the golden frames, recording THIS STRING as the reason compare: after the run, compare it against this baseline ledger row mutation: pull one declared lever for this run, and ask –compare the
per-mutation question instead of “is the second row worse”
Rendering knobs are deliberately NOT flags: a bench whose render knobs vary per invocation produces incomparable rows, so they are a module constant recorded verbatim into the ledger.
--mutationis not one of them, and is the exception that states the rule: a lever is the independent variable, it is named in the report, it is exempted by declaration inMUTATION_TOUCHESrather than by widening anything, and the row it produces is never filed under a commit’s name. Without it the--compareartifact is always themutation=Nonepath, which asks “is this worse” of a run that was broken on purpose — the wrong question, answered confidently.--blesstakes the reason as its value rather than pairing with a separate--reason, so a bless with no recorded reason cannot be typed. A re-bless with no recorded reason is the same failure as a silently widened threshold, and it is the failure this wave exists to prevent — so look at the PNG diff (GitHub renders 2-up, swipe and onion-skin) before writing one.- Return type:
- an.tools.bench_compare(before='', after='', mutation='', strict=False, raw=False)[source]#
Compare two ledger rows — and refuse when they are not comparable.
before: baseline ledger row (default: the second-newest committed row) after: the row to judge (default: the newest committed row) mutation: evaluate the per-mutation predictions instead of asking whether
the second row is worse. One of the mutations the rows declare.
- strict: exit nonzero when the answer is bad — a regression without a
mutation, or a direction that reverses on its own metric’s threshold grid (an#140: some cell of it got worse), an unmet criterion with one, a comparison that answered nothing, or a row that could not be read at all. For CI.
raw: print the report as JSON instead of the human digest
Refusing is the feature. Two rows measured on different scenes, at different resolutions, or on different x264 builds are not “one better and one worse” — every number in them is uninterpretable relative to the other, and a number reported across incomparable rows is worse than none.
-dirtyrows are excluded from the defaults: a row measured against uncommitted edits describes no commit. Name one explicitly to compare it.- Return type:
- an.tools.bench_mutants(names='', quiet=False)[source]#
Break each guard on purpose and check the test that names it goes red.
names: comma-separated mutant names (default: every declared one) quiet: print only the tally
“N mutants, all caught” is unfalsifiable after the fact when the mutations lived in a scratch script. These are declared data, so the proof is re-runnable — which matters because Wave 1 shipped three guards that stayed green while the bug they guarded was present.
The WHOLE guard file runs for each mutant, never a
-kfilter: a filter that happens to exclude the catching test reports “not caught” and sends you to write a test that already exists. Takes about forty seconds.- Return type:
- an.tools.credits(project_dir, json_out=None)[source]#
Show what third-party work is in
project_dirand what it obliges.project_dir: the an project json_out: also write the machine-readable record to this path
A licence recorded and never displayed is not compliance, so this is the consumer that makes the provenance field worth having.
- Return type:
- an.tools.init(project_dir, name=None, force=False, id=False, genre='', package='', root='')[source]#
Create a fresh an project at
project_dir— or, with –id, at the default location.project_dir: where to create the project (created if missing); with –id, the project’s id instead (an init <id> –id; –id is a switch and takes no value) name: project display name (defaults to the directory name) force: overwrite an existing scene.md id: a switch: read the positional as a project id, and create the project under its genre’s projects folder (an init alice-and-bob –id –genre cutout_animation -> ~/.local/share/cutan/projects/alice-and-bob) genre: with –id, the genre the video is made in (e.g. cutout_animation); the genre names the package whose root holds the project package: with –id, that package directly, overriding –genre (default: the genre’s, else an) root: with –id, that package’s root (default: its data folder, or <PKG>_HOME)
- Return type:
- an.tools.iterate(project_dir, instruction, apply_changes=True, model='claude-opus-4-7')[source]#
Apply a free-text instruction to the scene. Needs ANTHROPIC_API_KEY.
project_dir: path to an an project instruction: what to change in plain English (e.g. “make Maya’s laugh longer and warmer”) apply_changes: persist the new scene to disk (default True); the next render
re-renders exactly the shots whose content changed
model: Anthropic model id (default claude-opus-4-7)
- Return type:
- an.tools.preview(project_dir, shot='', no_browser=False)[source]#
Live-preview the project’s scene in a browser; reloads on edit.
Spins up a local HTTP server pointed at the runtime canvas. The browser polls
scene.jsonfor changes and re-loads when you savescene.md. Lossy: visuals only, no audio. Blocks until Ctrl-C.project_dir: path to an an project (must contain scene.md / ir/scene.json) shot: shot id to preview (default: first shot in the timeline) no_browser: don’t auto-open the default browser
- Return type:
- an.tools.probe(project_dir, shot='', at=None, out='', columns=0, supersample=1, allow_private_here=False)[source]#
Write a shot’s frame at chosen instants to a PNG, drawn the way
an renderdraws it.The shot is prepared as a render prepares it (captions, style pack, the settled length) and its own renderer draws only the frames asked for; a Manim shot is read from the picture the last render stored. Several –at make one grid. Frames showing private or unknown material are refused at a path git would pick up.
project_dir: path to an an project shot: the shot id at: seconds into the shot, repeatable (–at 1.5 –at 3.8) out: the PNG to write (default: <project>/artifacts/probes/<shot>@<t>.png, gitignored) columns: cells per row of a grid (default: a square grid) supersample: as
an render --supersample, so the frame matches the film’s allow_private_here: write not-publishable frames where git would pick them up- Return type:
- an.tools.registered_namespaces()[source]#
The sub-namespaces installed genres added (
cli.<namespace>services).>>> registered_namespaces() == {} or all(isinstance(v, list) for v in registered_namespaces().values()) True
- an.tools.render(project_dir, output_name='main', tts='', lipsync='offline', parallel='', strict_assets=False, supersample=1, pix_fmt='', step_hz=0.0, language='en', capture='', force_render=False, no_cache=False, cache_frames=False)[source]#
Render the project at
project_dirto a single mp4.Incremental: a shot whose inputs (its compiled document, its art’s bytes, its audio, the runtime, the render settings, this machine’s browser and ffmpeg) are unchanged since a previous render is reused from the shot cache rather than rendered again, and the summary line says which were which.
Then what the render learned, grouped, each with its fix: a synthesized line that runs past its shot or into a dissolve, a stand-in or a substitution, a library pin out of date (artifacts/render_reports/<output_name>.json holds them all).
project_dir: path to an an project (must contain scene.md / ir/scene.json) output_name: filename stem under output/ (default: “main”) tts: TTS provider for EVERY line — “offline” (silent), “elevenlabs” (needs
ELEVEN_API_KEY) or “mac_say”. Omitted (the default): each voice’s own
providerspeaks its lines, a voice naming none is offline, and what a paid provider will bill is printed before the first request. A line spoken by another provider than its voice declares is a finding (an error under –strict-assets)- lipsync: lip-sync provider — “offline” (deterministic), “rhubarb”
(needs the rhubarb binary), or “whisper” (needs faster-whisper)
- parallel: per-shot concurrency. “” or “1” = serial (default); “auto” =
min(shots, cpu, 4); a number ≥ 2 caps the thread pool.
- strict_assets: fail instead of drawing a stand-in (the placeholder rig, the
default backdrop) for an asset the project’s stores don’t supply
- supersample: render at N times the resolution and resolve back with an exact
N x N block mean. Opt-in; 1 (the default) costs nothing at all. Measured on the shipped path at 1920x1080: 125.5 ms/frame at 1, 508.6 at 2 — 4.05x, which is NOT the 2.54x the research reports for the render alone
- pix_fmt: the delivered encode’s pixel format — “yuv420p” (default) or
“yuv444p”. The ONE first-order quality lever in the encoder: 4:4:4 cuts the edge-band error 11.35 -> 3.79, where a mathematically lossless 4:2:0 only reaches 10.15. It is opt-in for a PRODUCT reason and not an encoder one: High 4:4:4 Predictive is refused by many hardware decoders, browsers and platforms, so a 4:4:4 file is one some viewers cannot play
- step_hz: stepped timing for authored tweens — pose updates per second, on a
shot-wide grid (15 at 30 fps is “on twos”, 10 “on threes”). 0 (the default) uses the scene’s own
meta.step_hz, which is unset (smooth) unless the author declared one. The camera, blinks,playclips and swap channels are never stepped by this- language: the dialogue’s language (BCP-47) for providers that select
behaviour by it — Rhubarb’s recognizer today: English (the default) uses
pocketSphinxwith the transcript, anything elsephoneticwithout one (an#96)- capture: how frames leave the browser — “canvas” (the default), an in-page
read of the canvas in batches, ~7.8x faster frame stage on the corpus, ~2.3x at 1080p; or “screenshot”, a Playwright element screenshot per instant. Both write frames with the same decoded pixels
- force_render: render every shot even when the shot cache holds it (and
refresh its entry)
- no_cache: neither read nor write the shot cache — every shot is rendered
cold, as before the cache existed
- cache_frames: no longer needed, and no effect on
an render(an#260): a film with transitions or a sound layer now reuses its shots by default, caching only the frames at each transition. Kept so scripts that pass it still run
- Return type:
- an.tools.sync(project_dir, accept_measured=False)[source]#
Reconcile scene.md and ir/scene.json inside
project_dir.--accept-measuredalso writes the durations a clock-owning renderer (Manim) MEASURED into the scene — each such shot’sduration:line, patched in place, so the prose around it is kept. Without it, a measured duration lives only in the derivedmeasurementsstore and the scene says what its author wrote.- Return type:
- an.tools.validate(project_dir, fps=0.0, strict_assets=False)[source]#
Validate the scene at
project_dir. Prints findings, exit 0 on pass.project_dir: the an project fps: the frame rate
an render --fpswill use, when not the scene’s (0: the scene’s) strict_assets: judge the scene asan render --strict-assetswill: a stand-in for a missing asset is an error- Return type: