Skip to main content

an

AI-driven structured animation in Python. The user is the director; an AI agent (Claude Opus 4.7) is the assistant orchestrator; existing animation libraries (a custom 2D-cutout runtime, Manim, Remotion) are the executors.

pip install an

Renamed from anima (PyPI conflict). Repo: https://github.com/thorwhalen/an.


What works today

an check                                                  # diagnose backend system deps
an init my-scene                                          # create a fresh project
# edit scene.md ...
an validate my-scene                                      # schema + semantic validation
an render my-scene                                        # → output/main.mp4 (offline defaults)
an render my-scene --tts elevenlabs --lipsync whisper     # real speech, word-aligned visemes
an render my-scene --parallel auto                        # per-shot threads (~N× speedup on N-shot scenes)
an preview my-scene                                       # live in-browser preview; reloads on edit
an iterate my-scene "make Maya laugh longer and warmer"   # free-text → IR patch → invalidate caches
an render my-scene                                        # re-renders only the affected shot

# Character authoring (Phase 11a)
an character new maya --seed maya-warm                    # generate a DiceBear-backed character
an character new bob --offline                            # offline-only: deterministic geometric fallback
an character mouths maya                                  # regenerate the 9-shape default mouth set
an character validate maya                                # check parts, mouth set, pivots
an character silhouette maya --other bob                  # silhouette test (IoU score)
an character preview maya --open-browser                  # HTML viewer cycling all 9 visemes

The defaults run without any API keys: offline TTS produces silent audio of the right length, offline lip-sync deterministically generates viseme tracks. To get real audible speech, set ELEVEN_API_KEY and pass --tts elevenlabs. For word-aligned mouth shapes, pip install faster-whisper and pass --lipsync whisper. For free-text editing via an iterate, set ANTHROPIC_API_KEY.


A 30-second tour

A project is a small directory:

my-scene/
├── scene.md            # human Markdown — what you and the agent edit
├── ir/scene.json       # Pydantic-validated SSOT (auto-synced)
├── assets/             # characters, environments, voices, styles
├── artifacts/          # audio, viseme tracks, per-shot mp4s (content-hash cached)
├── output/             # final mp4s
└── .an/                # decisions log + agent memory

Authoring happens in scene.md:

# Park Bench

```yaml meta
title: Park Bench
duration: 12
fps: 24
resolution: { width: 640, height: 360 }
```

## Shot s1 (cutout)

```yaml shot
duration: 6
camera: { move: push_in }
```

```yaml entities
- { kind: environment, id: park_bg, store: environments, ref: park }
- { kind: character,   id: charlie, store: characters,   ref: charlie-v1 }
- { kind: character,   id: maya,    store: characters,   ref: maya-v1 }
```

```dialogue
charlie [thinking]: Did you ever wonder why we always meet here?
maya [amused]: Because the pigeons trust us.
```

an render produces an mp4 with two visually-distinct characters (per-id palette: skin/clothing/hair), animated mouths over the dialogue lines, eye-blinks every ~4 seconds, eyebrows tilted by emotion, a sky/grass park background, and a slow camera push-in.


A 3-minute tour

an separates a scene into three layers:

  1. Narrativescene.md. Markdown with structured fenced blocks: yaml meta, yaml shot, yaml entities, yaml actions, dialogue. What you and the agent edit.
  2. Scene Graphir/scene.json. Pydantic-validated, renderer-agnostic. The single source of truth. Diffable. Pipeline stages (audio synthesis, lip-sync) write into the JSON; an sync keeps it consistent with the Markdown using mtime-newer-wins.
  3. Render Code — generated per-backend (cutout JSON for the JS runtime, Manim Python, Remotion TSX). Disposable; never edited by hand.

Composition

Authoring is fluent in Python and flattens to a canonical timeline:

from an import sequence, parallel, tween, delay, flatten

action = sequence(
    tween("charlie/torso", "rotation", to=10.0, duration=1.0),
    delay(0.5),
    tween("charlie/torso", "rotation", to=0.0, duration=1.0),
)
flat = flatten(action)
# [FlatAction(start=0.0, end=1.0, ...), FlatAction(start=1.5, end=2.5, ...)]

The same shape is also writable in markdown via a yaml actions block:

- { kind: tween, target: charlie/torso, property: rotation, to: 10.0, duration: 1.0 }
- { kind: tween, target: charlie/torso, property: rotation, to: 0.0,  duration: 1.0, start: 1.5 }

Persistence — the project mall

Everything long-lived (assets, artifacts, decisions, scene state) goes through a dol-backed MutableMapping mall:

from an import build_project_mall

mall = build_project_mall("my-scene", ensure=True)
mall["voices"]["maya-warm"] = {"provider": "elevenlabs", "voice_id": "..."}
mall["scenes"]["main"]  # returns a SceneIR

End-to-end orchestration

from an.orchestrate import orchestrate

report = orchestrate("my-scene")
# report.success: bool
# report.output_path: Path
# report.validation:  ValidationReport
# report.verifications: list[VerificationReport] from each verifier

The default verifier chain runs LayoutLintVerifier (pre + post) and MediaQualityVerifier (post). Pass your own list to swap in VisionLMVerifier (Claude vision QA) or HumanInTheLoopVerifier (interactive approval).

Free-text iteration (the spec's signature loop)

from an.orchestrate import iterate

result = iterate("my-scene", "Make Maya's response a bit longer and more affectionate")
# result.summary:           "Extended Maya's reply..."
# result.patches:            [Patch(op='set', path='timeline/1/dialogue/0/text', value=...), ...]
# result.affected_shots:     ['s2']
# result.success / .error / .new_scene / .validation

The patches are validated against the schema and persisted; affected shots' cached mp4s are invalidated so the next an render regenerates only those.


Architecture

Subsystem Implementation
Renderer Protocol an.adapters.Renderer — Cutout (real), Manim (real if installed), Remotion (skeleton), Whiteboard (stub)
Cutout backend an.adapters.cutout.compile_shotCutoutSceneJSON → PixiJS v7 in headless Chromium → ffmpeg mux
Character rig Ellipse head + per-id palette (skin/clothing/hair) + eyebrows (emotion-driven) + white-sclera eyes (procedural blinks) + bezier-curved mouth (9 viseme shapes)
TTS Protocol OfflineTTS (silent placeholder), ElevenLabsTTS (real, needs ELEVEN_API_KEY)
Lip-sync Protocol OfflineLipSync (char-distribution), WhisperLipSync (word-aligned via faster-whisper), RhubarbLipSync (phoneme-aligned), WordTimingsLipSync (driven by an injected WordTimingProvider — skip transcription entirely when the caller already has authoritative word timings)
Verifier Protocol LayoutLintVerifier, MediaQualityVerifier, VisionLMVerifier (Claude vision), HumanInTheLoopVerifier
Persistence dol-backed MutableMappings organized into build_project_mall(...)
CLI typer dispatch over an.tools._dispatch_funcs (init / validate / sync / check / render / iterate / bench)
Iterate loop an.iterate — Anthropic Opus 4.7 + adaptive thinking + structured JSON patches + path-based mutation + cache invalidation

For a deeper as-built reference (module-by-module map, control flows, key invariants, content-hash caching strategy), see misc/docs/architecture_as_built.md. The research reports next to it cover the design space the system was built against — they are not current state, and wave1_verification.md is (verified fact, with the URLs each licence was read at).

Injecting word timings (no whisper redundancy)

When the caller already has authoritative word-level alignment data (e.g. from a separate lyric-alignment pipeline), an can skip its own transcription pass entirely:

from an.audio import StaticWordTimings, WordTimingsLipSync
from an.orchestrate import orchestrate

timings = [("hello", 0.5, 1.0), ("world", 1.2, 1.8), ...]
lipsync = WordTimingsLipSync(StaticWordTimings(timings, label="my-aligner"))

orchestrate("my-scene", lipsync=lipsync)

orchestrate(..., tts=, lipsync=, parallel=) accepts either provider name strings or instances. Plug in any WordTimingProvider (structural protocol with name: str and words_for(audio, transcript=) returning (text, start, end) tuples). muvid uses this hook to feed lacing alignment-store timings straight into the cutout pipeline.


What's not in v0.1

  • 3D animation, generative video, interactive output, SaaS hosting, music/sound-effect generation, in-house GUI, or editing of pre-existing video footage. an synthesizes; it does not cut.
  • The Manim backend works for placeholder title cards but isn't doing real shot-to-Manim translation; Remotion + whiteboard are skeleton implementations that respond correctly to can_render but can't produce video yet.
  • Lip-sync for face-baked characters. Characters sourced from DiceBear (or any external_avatar descriptor) carry their face inside the head SVG, so the compiler suppresses both the overlay mouth and the viseme channel — they speak without moving their mouths. Hand-rigged characters lip-sync normally; see examples/promote_demo/.
  • Multi-scene projects — "main" is the only scene key supported today.

SVG character art has shipped — descriptors drive real sprites through svg_sprite visuals, not the placeholder geometry an earlier version of this list described.


Reference

  • misc/docs/architecture_as_built.md — module map, control flows, invariants, caching strategy.
  • misc/docs/ — the design-space research reports, plus wave1_verification.md, which is verified current fact rather than design space: licences with the URLs they were read at, and the silent-discard inventory.
  • misc/CHANGELOG.md — phase-by-phase what shipped when.
  • .claude/skills/ — the skills the agent uses to drive the package (an to use it, an-spec to interview a director, an-dev to work on it, plus an-dev-licensing and an-dev-runtime-assets for the two things that fail silently).
  • examples/ — the canonical demo scenes. single_character/ is the smallest thing that renders; character_gallery/build.py goes end to end from character creation.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

an-0.1.35.tar.gz (1.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

an-0.1.35-py3-none-any.whl (449.5 kB view details)

Uploaded Python 3

File details

Details for the file an-0.1.35.tar.gz.

File metadata

  • Download URL: an-0.1.35.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for an-0.1.35.tar.gz
Algorithm Hash digest
SHA256 b78e934822538d11ae5868e472b30617fe2a11ca7b88748a5a46f2d3a887be2c
MD5 6e0cee534337ab515ce4c3d094847c54
BLAKE2b-256 8e7ebb700090f7c15360f7d3a47deea9a462d8fb07dfdb1ee839b1c1371572b4

See more details on using hashes here.

File details

Details for the file an-0.1.35-py3-none-any.whl.

File metadata

  • Download URL: an-0.1.35-py3-none-any.whl
  • Upload date:
  • Size: 449.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for an-0.1.35-py3-none-any.whl
Algorithm Hash digest
SHA256 bd92482ad338c6046e91dc59edea224ea7bfd2d358e6d2b220c67ba6065242ff
MD5 b865b73afcc18ecdb74114425124b1f2
BLAKE2b-256 e81333e5f3b6b25fdd68120bf9b5a949b375958aabc498792651b490d4422962

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.42

2 files

0.1.41

2 files

0.1.40

2 files

0.1.39

2 files

0.1.38

2 files

0.1.37

2 files

0.1.36

2 files

This release

0.1.35 This release

2 files

0.1.34

2 files

0.1.33

2 files

0.1.32

2 files

0.1.31

2 files

0.1.30

2 files

0.1.29

2 files

0.1.28

2 files

0.1.27

2 files

0.1.26

2 files

0.1.25

2 files

0.1.24

2 files

0.1.23

2 files

0.1.22

2 files

0.1.21

2 files

0.1.20

2 files

0.1.19

2 files

0.1.18

2 files

0.1.17

2 files

0.1.16

2 files

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page