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.
overrideslets 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.
timingis 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.jsoninside 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
styleselects the renderer. Every renderer must accept the same Shot fields; renderer-specific options go underoptions.- 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=Trueto 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 checkpretty-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; passforce=Trueto 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
actioncounttimes.
- 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.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) toto.
- 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_voicesandavailable_charactersaccept any mapping; only their__contains__is consulted. PassNoneto skip those checks.