an.base#
Core types, constants, and re-exports for an.
This module is the type vocabulary shared across the package: schema versions,
default render parameters, easing presets, type aliases for paths and time. Heavy
data classes (the Pydantic IR models, the Renderer/Verifier protocols) live in
their own subpackages and are re-exported from an itself.
Keep this module small and dependency-light. Everything here should import in a fraction of a second so the CLI is snappy.
Module Attributes
Current Scene IR schema version. |
|
Minimum Scene IR version this code can still read without migration. |
|
Render at this many times the declared resolution, then resolve back with an exact block mean. |
|
Put the mp4's |
|
The RGB->YUV conversion |
|
Named easing presets. |
|
The property names the cutout runtime animates NUMERICALLY. |
|
a node targeted with any of them is drawn on a plane tilted away from the camera ( |
|
The path-only properties inside |
|
|
|
a compiled transform channel, or the authored colour spelling the compiler expands. |
|
Slash-delimited node path, e.g. |
|
Time in seconds. |
|
Either an easing preset name or a 4-tuple cubic-Bézier control [cx1,cy1,cx2,cy2]. |
|
Which renderer draws a shot. |
|
The same vocabulary as |
|
The transition kinds a shot may be entered by. |
|
The ways a |
|
half a second is a conventional editor's default, short enough not to eat a line of dialogue and long enough to read as deliberate. |
|
The colour a |
|
The film mix's sample rate and channel count. |
|
how far a ducked cue drops under dialogue, and how fast it gets there and comes back. |
Functions
|
Why |
- an.base.AUTHORABLE_PROPERTIES: frozenset[str] = frozenset({'alpha', 'dash_offset', 'perspective', 'pivot_x', 'pivot_y', 'plane_fade_end', 'plane_fade_start', 'rotation', 'rotation_rad', 'rotation_x', 'scale_x', 'scale_y', 'skew_x', 'skew_y', 'tint', 'tint_b', 'tint_g', 'tint_r', 'trim_end', 'trim_start', 'x', 'y'})#
a compiled transform channel, or the authored colour spelling the compiler expands. Validate checks against this, and the compiler’s rest table against
TRANSFORM_PROPERTIES— the difference between the two sets is exactlytint, and a test pins that rather than trusting it.- Type:
What a set/tween may name
- an.base.BT709_SCALE_FILTER: str = 'scale=out_range=tv:out_color_matrix=bt709'#
The RGB->YUV conversion
anperforms, stated EXPLICITLY rather than left to the encoder flags to imply.an#34 pinned
-colorspace bt709and measured that it does not merely tag the file: it sets the matrix of the auto-inserted conversion, so the encoded planes themselves change. That measurement was made on ffmpeg 8/9 and **is not a universal ffmpeg fact** (an#148). Measured on the product’s own_ffmpeg_muxwith flat known-colour frames, decoded as rawyuv420p(planes verbatim, no conversion on read), against the analytic limited-range values:build | pure red decodes to Y | BT.601 says | BT.709 says ||---|—|---|—| | ffmpeg 9.0.1 | 63 | 81.5 | 62.6 | | ffmpeg 6.1.6 | 81 | 81.5 | 62.6 |
Over five colours and all three planes, the worst disagreement is 0.44 from BT.709 and 28.45 from BT.601 on ffmpeg 9 – and 0.48 from BT.601 against 27.63 from BT.709 on ffmpeg 6.1.6, which is the CI runner’s apt build. Both files are tagged
color_space=bt709 color_primaries=bt709 color_transfer=bt709 color_range=tv. So on 6.1 an#34’s re-baseline was metadata only:anshipped BT.601 planes wearing a BT.709 label, and a conforming player undoes a conversion that was never applied.This filter removes the build-dependence by naming the conversion instead of inferring it. It is BYTE-IDENTICAL to today’s output on ffmpeg 9.0.1 (measured: same mp4 sha256 on real corpus frames), so it re-baselines nothing where the flags already worked, and it moves ffmpeg 6.1.6’s output onto the same conversion (its residual against an analytic BT.709 reference drops from luma mean 0.788 to 0.205 – ffmpeg 9’s own number to three decimals).
**The committed ledger rows were captured on ffmpeg 8.1, which is not on this machine, so the byte-identity above was not re-measured there.** That those rows are equally unaffected rests on an#34’s own measurement — that on 8.x
-colorspace bt709already set the auto-inserted conversion, so this filter names what that build was doing anyway. It is a sound inference from a recorded measurement, not a second observation; treat it as such if an 8.x build ever contradicts a row.Here, and not beside one of the ffmpeg calls, for the same reason
MP4_FASTSTART_ARGSis: three commands must agree on it, and they are in two packages. The delivered mux (_ffmpeg_mux), the bench’s lossless reference leg and the bench’s PNG decode leg all perform this conversion, and the lossless leg’s entire contract is “these ARE the planes libx264 received”. Pin one and not the others and every encode-side metric silently measures a colour-space disagreement instead of encoder damage – the failurean/bench/imageio.py’s module docstring records CI catching once already.
- an.base.COLOUR_PROPERTY: str = 'tint'#
The property names the cutout runtime animates NUMERICALLY. Any other property on a set/tween names a swap SET declared by the target entity’s descriptor (an#87). This is the SSOT the three consumers share so the IR validator, the character validator and the compiler cannot drift: the compiler derives its rest-value table from
TransformJSONand a test asserts that derivation equals this set. Lives here becausean.baseis the one module all three layers may import. The colour multiply as an AUTHOR spells it. Not inTRANSFORM_PROPERTIESbecause nothing downstream of the compiler ever sees it —_expand_tint_actionsrewrites each leaf into the three numeric components before the swap-set dispatch, which would otherwise read it as an asset-set name (an#62).
- an.base.COMPATIBLE_VERSION: str = '0.3.0'#
Minimum Scene IR version this code can still read without migration.
- an.base.DEFAULT_DUCK_DB: float = -12.0#
how far a ducked cue drops under dialogue, and how fast it gets there and comes back. The ramps are linear in gain.
- Type:
Ducking defaults
- an.base.DEFAULT_SUPERSAMPLE: int = 1#
Render at this many times the declared resolution, then resolve back with an exact block mean. 1 is off, and off is free: the un-supersampled path keeps Chromium’s own PNG bytes and pays nothing.
Here rather than in
an.adapters.cutout.supersample, where the rest of the mechanism lives, becausean/adapters/_base.pyneeds it forRenderContext’s default and importing the cutout package from there is a real cycle (_base->cutout->render->_base), not a hypothetical one.an/base.pyimports nothing froman.The default stays 1 deliberately. Supersampling ships OPT-IN with its A/B committed (an#58, discussion #52), per the standing rule that a default chosen by taste ships opt-in and the flip is its own one-line change.
- an.base.DEFAULT_TRANSITION_COLOR: str = '#000000'#
The colour a
fadepasses through when it names none.
- an.base.DEFAULT_TRANSITION_DURATION: float = 0.5#
half a second is a conventional editor’s default, short enough not to eat a line of dialogue and long enough to read as deliberate.
- Type:
Seconds, when a
fadeordissolvenames no duration
- an.base.EASING_PRESETS: tuple[str, ...] = ('linear', 'ease', 'ease_in', 'ease_out', 'ease_in_out', 'step')#
Named easing presets. Renderers should accept these and the cubic-Bézier 4-tuple form
[cx1, cy1, cx2, cy2]. They arean’s LEGACY family (an.timing.easing):easeandease_in_outare the quadratic ease-in-out, not CSSease; the CSS curves are the hyphenated names (ease-in-out,cubic-bezier(...)) of thecssfamily.
- an.base.EasingSpec: TypeAlias = str | tuple[float, float, float, float] | list[float]#
Either an easing preset name or a 4-tuple cubic-Bézier control [cx1,cy1,cx2,cy2].
- an.base.FILM_AUDIO_SAMPLE_RATE: int = 44100#
The film mix’s sample rate and channel count. The SAME values the per-shot audio mux (
an.stage.render._ffmpeg_add_audio) writes, so a film assembled from sources and one concatenated from shot mp4s carry the same audio format.
- an.base.MP4_FASTSTART_ARGS: tuple[str, ...] = ('-movflags', '+faststart')#
Put the mp4’s
moovatom in front ofmdat, so a player can start before the file has finished downloading.Here, and not beside one of the ffmpeg calls, because **three separate commands build the one file a user receives** – the frame mux (
_ffmpeg_mux), the audio mux (_ffmpeg_add_audio) and the concat (_ffmpeg_concat) – and each of the last two re-lays the container with-c copy, which writesmoovLAST. The flag was on the first of those alone, which reads as done and delivers nothing: it applied only tosilent.mp4, a per-shot intermediate that is never handed to anyone. Measured on a local example render (these mp4s are gitignored build products;git ls-filestracks exactly one, and it is moov-last too):.an/render_work/shot_s1/silent.mp4isftyp moov free mdat, while the per-shot mp4,artifacts/shots/*.mp4andoutput/main.mp4are allftyp free mdat moov. Single-shot and multi-shot alike; an#57’s “single-shot ones keep it” is wrong, because theshutil.copybranch copies a file that already lost it.Deliberately NOT part of
DETERMINISTIC_X264_ARGS. That tuple is anENCODE_ENV_PATHScomparability key (an/bench/compare.py:98), and this flag moves no number the panel reads: with-c copyit is a container rewrite, not a re-encode. Verified on ffmpeg 8.1 – elementary-stream sha256 identical, video/audio packet totals identical, file size identical, decoded YUV sha256 identical, wall time unchanged.
- an.base.PLANE_REST_VALUES: dict[str, float] = {'perspective': 1.0, 'plane_fade_end': 0.0, 'plane_fade_start': 0.0, 'rotation_x': 0.0}#
a node targeted with any of them is drawn on a plane tilted away from the camera (
rotation_x, radians, positive = the top recedes;perspective, the eye’s distance in frame heights;plane_fade_start/plane_fade_end, the far fade, scene px up the plane from the hinge, off while the end is<= 0). Seeruntime.js“Planes” andan.stage.timeline.Transform2D. Their rest values — the untilted plane, the eye one frame height away, no far fade — the one table the compiler’s rest table, the Python projection (an.stage.timeline) andruntime.js’sPLANE_PROPERTIES(pinned by a test) agree on.- Type:
The plane properties inside
TRANSFORM_PROPERTIES(an#314)
- an.base.PathStr#
Slash-delimited node path, e.g.
"charlie/head/mouth".The example matters: an unknown target now RAISES rather than being skipped, and the long-standing
"charlie/torso/left_arm"illustration names a node no rig actually builds — the cutout rigs are flat, so arms are siblings of the torso, not children of it.
- an.base.RendererName#
Which renderer draws a shot. The orchestrator uses this to pick an adapter.
alias of
Literal[‘cutout’, ‘stage’, ‘manim’, ‘motion_graphics’, ‘whiteboard’]
- an.base.SCHEMA_VERSION: str = '0.3.0'#
Current Scene IR schema version. Bump on additive changes; on breaking changes, also bump COMPATIBLE_VERSION and add a migration in
ir.migrate. 0.2.0 renamedShot.style->Shot.rendererandMeta.default_style->Meta.default_renderer, and retiredAssetRef(kind="style")(an#106). 0.3.0 removedCamera.position/.target/.focal_length, which described a 3D camera this package never had, and gaveCameraakeyslist so it can translate (an#109).
- an.base.SUPPORTED_RENDERERS: tuple[str, ...] = ('cutout', 'stage', 'manim', 'motion_graphics', 'whiteboard')#
The same vocabulary as
RendererName, as a runtime tuple — DERIVED from it, because a hand-typed second copy is a second SSOT that drifts on the day a renderer is added and nothing fails.
- an.base.SWAP_SET_NAME_FORBIDDEN_SUBSTRINGS: tuple[str, ...] = ('/', '::')#
/would read as a path segment and::is the runtime’s pose-key separator.- Type:
Characters within which a swap-set name is not addressable
- an.base.Seconds#
Time in seconds. Floats at the IR boundary; rational time is used internally only inside the audio pipeline where drift matters.
- an.base.TRANSITION_KINDS: tuple[str, ...] = ('cut', 'fade', 'dissolve', 'wipe')#
The transition kinds a shot may be entered by.
cutis the default and what every document written before transitions existed means.
- an.base.TRIM_PROPERTIES: frozenset[str] = frozenset({'dash_offset', 'trim_end', 'trim_start'})#
The path-only properties inside
TRANSFORM_PROPERTIES(an#160; the name predatesdash_offset, an#161): trim and the dash phase. A node that draws no path refuses all of them.
- an.base.WIPE_DIRECTIONS: tuple[str, ...] = ('left', 'right', 'up', 'down')#
The ways a
wipe’s edge can travel across the frame (an#390).
- an.base.swap_set_name_problem(name)[source]#
Why
namecannot be a swap-set name, orNoneif it can.>>> swap_set_name_problem("hands") is None True >>> swap_set_name_problem("alpha") "'alpha' is a transform property; the runtime's static switch would shadow the set" >>> swap_set_name_problem("a::b") "'a::b' contains '::', which is reserved"