an

an — AI-driven structured animation.

Public API surface (curated). See an.ir for the Scene IR, an.adapters for renderer plumbing, an.audio for TTS/lip-sync protocols, an.verify for verification, and an.stores for the project mall.

>>> import an
>>> 'SceneIR' in an.__all__
True
class an.AssetRef(*, kind: Literal['character', 'environment', 'voice', 'style', 'prop'], id: str, store: str, ref: str, overrides: dict[str, Any] | None = None, **extra_data: Any)[source]

Reference to an entry in a project store.

The IR never inlines large assets. Instead it references them by store name + key, so the same character/voice/environment is reusable across scenes. overrides lets a single shot tweak presentation without forking the asset.

>>> AssetRef(kind="character", id="maya", store="characters", ref="maya-v1").id
'maya'
model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class an.Camera(*, position: tuple[float, float, float] = (0.0, 0.0, 0.0), target: tuple[float, float, float] = (0.0, 0.0, 0.0), focal_length: float = 50.0, move: str | None = None, **extra_data: Any)[source]

Camera state for a shot. Minimal placeholder; expanded in P2.

model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class an.Dialogue(*, speaker: str, text: str, voice_ref: str | None = None, start: float | None = None, duration: float | None = None, emotion: str | None = None, viseme_track: VisemeTrack | None = None, audio_ref: str | None = None, viseme_ref: str | None = None, **extra_data: Any)[source]

One line of spoken dialogue.

timing is None until the audio pipeline runs (TTS gives us a real duration); the orchestrator fills it in then.

model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class an.FlatAction(start: float, end: float, action: Annotated[SetAction | TweenAction | PlayAction | SequenceAction | ParallelAction | DelayAction | LoopAction, FieldInfo(annotation=NoneType, required=True, discriminator='kind')])[source]

A leaf action with its absolute start and end times.

The flat-form list is the canonical representation passed to renderers and verifiers. Composition nodes (sequence/parallel/delay/loop) do not appear in the flat form — they’re collapsed into time offsets.

class an.Meta(*, title: str = '', author: str = '', duration: float = 0.0, fps: int = 30, resolution: ~an.ir.schema.Resolution = <factory>, default_style: ~typing.Literal['cutout', 'manim', 'motion_graphics', 'whiteboard'] = 'cutout', notes: str = '', **extra_data: ~typing.Any)[source]

Scene metadata.

model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class an.Project(root: Path, mall: Mapping[str, MutableMapping], scene: SceneIR)[source]

A loaded an project: directory + mall + current scene.

class an.Resolution(*, width: int = 1920, height: int = 1080, **extra_data: Any)[source]

Pixel dimensions of the rendered output.

model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class an.SceneIR(*, version: str = '0.1.0', compatible_version: str = '0.1.0', kind: ~typing.Literal['SceneIR'] = 'SceneIR', meta: ~an.ir.schema.Meta = <factory>, assets: list[~an.ir.schema.AssetRef] = <factory>, timeline: list[~an.ir.schema.Shot] = <factory>, **extra_data: ~typing.Any)[source]

Top-level Scene IR document. The SSOT.

A document is portable, diffable, and renderer-agnostic. Persisted as JSON at ir/scene.json inside an an project.

>>> doc = SceneIR(meta=Meta(title="Hello"))
>>> doc.version == '0.1.0'
True
>>> round_tripped = SceneIR.model_validate_json(doc.model_dump_json())
>>> round_tripped.meta.title
'Hello'
model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class an.Shot(*, id: str, style: ~typing.Literal['cutout', 'manim', 'motion_graphics', 'whiteboard'] = 'cutout', duration: float = 5.0, camera: ~an.ir.schema.Camera | None = None, entities: list[~an.ir.schema.AssetRef] = <factory>, actions: list[~typing.Annotated[~an.ir.schema.SetAction | ~an.ir.schema.TweenAction | ~an.ir.schema.PlayAction | ~an.ir.schema.SequenceAction | ~an.ir.schema.ParallelAction | ~an.ir.schema.DelayAction | ~an.ir.schema.LoopAction, FieldInfo(annotation=NoneType, required=True, discriminator='kind')]] = <factory>, dialogue: list[~an.ir.schema.Dialogue] = <factory>, narration: list[~an.ir.schema.Narration] = <factory>, options: dict[str, ~typing.Any] = <factory>, **extra_data: ~typing.Any)[source]

A single rendered unit. A scene is a sequence of shots.

A shot’s style selects the renderer. Every renderer must accept the same Shot fields; renderer-specific options go under options.

model_config: ClassVar[ConfigDict] = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

an.build_project_mall(project_dir: str | Path, *, ensure: bool = False, **overrides: MutableMapping) dict[str, MutableMapping][source]

Build the standard project mall over project_dir.

Pass ensure=True to create the per-store directories on disk if they don’t exist. Pass keyword overrides to swap in alternate stores (e.g. an in-memory dict for tests).

an.check_requirements() dict[str, dict][source]

Return a per-tool status dict.

The CLI subcommand an check pretty-prints this. Programmatic callers can inspect the dict directly.

an.delay(duration: float) DelayAction[source]

An empty span that consumes time. Useful inside sequence.

an.flatten(action: Annotated[SetAction | TweenAction | PlayAction | SequenceAction | ParallelAction | DelayAction | LoopAction, FieldInfo(annotation=NoneType, required=True, discriminator='kind')], *, start: float = 0.0) list[FlatAction][source]

Walk a composition tree, emitting leaf actions with absolute times.

Delays are absorbed into the timeline (they don’t appear in the output). Loops are unrolled by simple repetition — appropriate at v0.1; the cutout runtime can re-roll for efficiency later.

an.init(project_dir: str | Path, *, name: str | None = None, force: bool = False) Path[source]

Create a fresh an project at project_dir.

Idempotent unless the directory already contains a non-empty scene.md; pass force=True to overwrite. Returns the absolute project root.

an.ir_to_markdown(scene: SceneIR) str[source]

Render a SceneIR back into the structured Markdown form.

>>> from an.ir.schema import SceneIR, Meta, Shot
>>> scene = SceneIR(meta=Meta(title="Demo", duration=5.0),
...                 timeline=[Shot(id="s1", style="cutout", duration=5.0)])
>>> md = ir_to_markdown(scene)
>>> "# Demo" in md
True
>>> "## Shot s1 (cutout)" in md
True
an.load(project_dir: str | Path) Project[source]

Load an existing project. Reconciles scene.md / ir/scene.json first.

an.loop(action: Annotated[SetAction | TweenAction | PlayAction | SequenceAction | ParallelAction | DelayAction | LoopAction, FieldInfo(annotation=NoneType, required=True, discriminator='kind')], count: int) LoopAction[source]

Repeat action count times.

an.markdown_to_ir(md_text: str) SceneIR[source]

Parse the structured Markdown form of a scene into a SceneIR.

>>> md = '''# Demo
...
... ```yaml meta
... title: Demo
... duration: 5
... ```
...
... ## Shot s1 (cutout)
...
... ```yaml shot
... duration: 5
... ```
...
... ```dialogue
... charlie: hi
... ```
... '''
>>> scene = markdown_to_ir(md)
>>> scene.meta.title
'Demo'
>>> scene.timeline[0].id
's1'
>>> scene.timeline[0].dialogue[0].text
'hi'
an.parallel(*actions: Annotated[SetAction | TweenAction | PlayAction | SequenceAction | ParallelAction | DelayAction | LoopAction, FieldInfo(annotation=NoneType, required=True, discriminator='kind')]) ParallelAction[source]

Run all children at once. Total duration = max of child durations.

an.play(target: str, animation: str, *, duration: float | None = None, speed: float = 1.0, loop: bool = False) PlayAction[source]

Play a named animation clip on a target.

an.save(project: Project) None[source]

Persist a Project’s current scene back to disk (md + json).

an.sequence(*actions: Annotated[SetAction | TweenAction | PlayAction | SequenceAction | ParallelAction | DelayAction | LoopAction, FieldInfo(annotation=NoneType, required=True, discriminator='kind')]) SequenceAction[source]

Run children one after the other. Total duration = sum of child durations.

an.set_(target: str, property: str, value: Any, *, at: float = 0.0) SetAction[source]

Discrete property set at time at (relative to its enclosing scope).

an.tween(target: str, property: str, to: Any, duration: float, *, from_: Any | None = None, easing: str | tuple[float, float, float, float] | list[float] | None = 'ease_in_out') TweenAction[source]

Animate a property from from_ (or its current value) to to.

an.validate_schema(doc: Any) ValidationReport[source]

Validate that doc (dict, JSON string, or SceneIR) conforms to the schema.

>>> validate_schema({"meta": {"title": "x"}, "timeline": []}).passed
True
>>> r = validate_schema({"meta": {"title": "x"}, "timeline": [{"id": "s", "duration": "not-a-number"}]})
>>> r.passed
False
an.validate_semantic(scene: SceneIR, *, available_voices: Mapping[str, Any] | None = None, available_characters: Mapping[str, Any] | None = None) ValidationReport[source]

Cross-field semantic checks. Pass live stores in for cross-store checks.

Both available_voices and available_characters accept any mapping; only their __contains__ is consulted. Pass None to skip those checks.