ReMapping Video Generation — Simple English Manual
This manual explains how to make a film with ReMapping. It covers the normal workflow, film structure, scenes, text, shapes, layouts, entrances, exits, stress effects, attributes, graphs, mathematics, cameras, narration, sound, rendering, commands, and common problems.
The examples in this manual match ReMapping version 0.1.0 in this repository.
Detailed topic manuals
This file is the complete quick manual. Use the smaller linked manuals when you want deeper examples about one subject:
- Film, sequences, scenes, and timing
- Things: text, shapes, groups, images, and SVG
- Text and typography: letter, word, and line spacing
- Layout, coordinates, anchors, and spacing
- Moves and animations: entrances, exits, curves, spreads, and levers
- Stress and emphasis effects
- Mathematics and semantic formula parts
- Graphs, data series, plots, and animated cursors
- 2D cameras, pan, zoom, and current 3D status
- Narration, speech beats, claims, and generated sound
- Stills, previews, rendering, verification, and commands
- Images inside Markdown manuals
- Complete property reference for all authoring controls
The topic-manual index also links every guide and suggests a
reading order. The topic manuals contain embedded screenshots from the project. The
manual-examples production renders the new
property sheets, so you can modify and regenerate them.
1. The fastest possible start
Open Terminal and go to the ReMapping repository:
cd /Users/ramiyarheydari/Documents/Remapping
Install and check the system:
uv sync --frozen --all-extras
uv run remapping doctor
Create a new film production:
uv run remapping production new productions/my-film --id my-film
Open this file and write your film:
productions/my-film/src/film.py
Check it, make one still image, and make a quick preview:
uv run remapping production validate productions/my-film
uv run remapping still productions/my-film --time 1 --output test.png
uv run remapping preview productions/my-film
The files will be in:
productions/my-film/outputs/
When the film is ready, render a normal 1080p final:
uv run remapping render productions/my-film \
--profile final-1080p60 \
--draft-timing \
--workers auto \
--no-audio
--draft-timing uses estimated narration timing. --no-audio makes a silent diagnostic
film. These two options are useful now because voice recording and approval commands are
not implemented in this build. A certified narrated final needs approved voice takes.
2. What ReMapping is
ReMapping treats a film as a Python program.
The basic structure is:
Film
└── Sequence
└── Scene
├── Things: text, math, shapes, graphs, images
├── Moves: enter, leave, stress, change
├── Timing: seconds or spoken words
├── Camera
└── Sound
A Thing is something you can see. A Move changes how its pieces are drawn. A Scene puts Things, Moves, timing, camera, and sound together. A Sequence orders scenes. A Film is the complete production.
ReMapping uses stable names, called IDs, for almost everything. An animation says “move
the Thing named title,” not “move object number 3.” This makes films easier to change
without breaking animation targets.
3. Requirements
This build expects:
- macOS 15 or newer on Apple Silicon
- Python 3.13
uv- FFmpeg 8
- TeX Live with
latexanddvisvgm - SF Pro Display, SF Mono, and SF Pro Rounded fonts
Check all requirements with:
uv run remapping doctor
For a stricter automated check:
uv run remapping doctor --ci
If doctor reports a missing tool or font, fix that first. Rendering may fail or the
layout may change if the correct tools and fonts are not available.
4. Production folders
Every film lives in its own production folder:
productions/my-film/
├── production.toml settings for this production
├── production.lock frozen environment information
├── src/film.py the film code; it must export FILM
├── styles/local.py colours and local visual style
├── assets/ images, SVG, audio, music, video, and other media
├── takes/ narration recordings and approvals
├── .remapping/ caches and render jobs
└── outputs/ stills, videos, manifests, subtitles, and stems
One production cannot read another production while it is running. Put a film's data and assets inside its own folder. Deliberate reuse between productions happens through packs.
Useful checks:
uv run remapping production inspect productions/my-film
uv run remapping production validate productions/my-film
uv run remapping production check-boundary productions/my-film
5. production.toml
A new production starts with settings like these:
[production]
id = "my-film"
entrypoint = "src/film.py"
[render]
profile = "final-1080p60"
design_size = [1920, 1080]
fps = "60"
seed = 0
[styles]
local = "styles/local.py"
[isolation]
profile = "operational-isolation"
allow_sibling_productions = false
cache = ".remapping/cache"
outputs = "outputs"
network = false
The most common values to change are:
| Setting | Meaning |
|---|---|
id |
Stable production name. Use lowercase letters, numbers, and hyphens. |
profile |
Default output size, frame rate, codec, and audio format. |
design_size |
The coordinate system used while designing scenes. |
fps |
Frames per second. |
seed |
Stable random seed for deterministic results. |
Do not turn on sibling production access. Copy an asset into the active production or use a declared pack.
6. A complete small film
Put this in productions/my-film/src/film.py:
from remapping.basics import ACCENT, INK, MUTED, Interval, Vec2
from remapping.film import Cut, Film, SceneSpec, Sequence
from remapping.ids import Ref
from remapping.moves import Enter, Stress
from remapping.scene import Scene
from remapping.things import Rectangle, Text
class Opening(Scene):
id = "opening"
DURATION = 5.0
def setup(self) -> None:
self.add(
Text(
"MY FIRST FILM",
weight="semibold",
size=96,
colour=INK,
origin=Vec2(-520.0, 40.0),
id="title",
)
)
self.add(
Text(
"made with ReMapping",
weight="light",
size=42,
colour=MUTED,
origin=Vec2(-520.0, -70.0),
id="subtitle",
),
visible_from=0.6,
)
self.add(
Rectangle(
1100.0,
8.0,
centre=Vec2(0.0, -150.0),
radius=4.0,
colour=ACCENT,
id="rule",
)
)
self.animate(
id="enter-title",
target=Ref.thing("title"),
move=Enter("rise", spread="in-order", shape="ease-out", travel=64.0),
during=Interval.of(0.0, 1.6),
)
self.animate(
id="enter-subtitle",
target=Ref.thing("subtitle"),
move=Enter("fade"),
during=Interval.of(0.6, 1.8),
)
self.animate(
id="stress-rule",
target=Ref.thing("rule"),
move=Stress(scale=1.04, tint=INK),
during=Interval.of(2.2, 3.4),
)
FILM = Film(
id="my-film",
sequences=(
Sequence(
id="main",
scenes=(
SceneSpec(id="opening", factory=Opening, transition_after=Cut()),
),
),
),
)
Important rules:
- The file must export exactly one object named
FILM. - Every Scene has a stable lowercase ID.
- Every Thing that you want to control needs an ID.
- Every animation needs its own ID.
- A silent Scene normally uses
DURATION. - A narrated Scene can get its duration from narration timing.
Check the film:
uv run remapping production validate productions/my-film
uv run remapping film inspect productions/my-film
uv run remapping lint productions/my-film
7. Coordinates and placement
The normal design frame is 1920 × 1080.
y = +540
top
↑
x = -960 left ← (0, 0) → right x = +960
↓
bottom
y = -540
Use Vec2(x, y) for a position:
from remapping.basics import Vec2
position = Vec2(-500.0, 200.0)
Many Things accept origin= or centre=. Text usually uses origin. Shapes usually use
centre.
You can also position a Thing by one of its anchors:
title = Text("TITLE", size=96, id="title")
self.add(title.moved_to(0.0, 100.0, anchor="center"), id="title")
Common anchors include center, left, right, top, bottom, baseline-start, and
bottom-left. Available anchors depend on the Thing. Inspect them with:
uv run remapping anchors list productions/my-film
Render an anchor overlay:
uv run remapping still productions/my-film --time 1 --show-anchors
Show title-safe and action-safe areas:
uv run remapping still productions/my-film --time 1 --show-safe-area
8. Things: visible objects
Import the Things you need:
from remapping.things import (
Arrow,
Circle,
CodeText,
Dot,
Ellipse,
Group,
Line,
Math,
Plot,
Polygon,
Polyline,
RasterImage,
Rectangle,
RegularPolygon,
Text,
TextBlock,
VectorImage,
)
Text
One line of normal text:
self.add(
Text(
"Clear explanation",
family="display",
weight="semibold",
size=72,
colour=INK,
tracking=-0.02,
origin=Vec2(-600.0, 200.0),
id="heading",
)
)
The available text families are:
| Family | Use |
|---|---|
display |
Normal titles, labels, and paragraphs; SF Pro Display |
mono |
Code and fixed-width values; SF Mono |
rounded |
Friendly labels; SF Pro Rounded |
Useful text attributes include:
| Attribute | Example | Meaning |
|---|---|---|
weight |
"light", "regular", "medium", "semibold", "bold" |
Font weight |
size |
72 |
Design-space font size |
colour |
INK |
Text colour |
tracking |
-0.02 or 0.08 |
Extra spacing between letters |
word_spacing |
10.0 |
Extra spacing between words |
case |
"upper", "lower", "title", "as-is" |
Letter case |
figures |
"tabular" |
Equal-width numbers |
underline |
True |
Underline text |
strike |
True |
Strike through text |
For a wrapped paragraph, use TextBlock:
self.add(
TextBlock(
"This paragraph wraps inside a fixed width and can balance its lines.",
measure=900.0,
leading=1.25,
align="left",
balance=True,
weight="light",
size=38,
colour=MUTED,
origin=Vec2(-450.0, 100.0),
id="paragraph",
)
)
For code, use CodeText, not Math:
self.add(CodeText("value = slope * x + intercept", size=38, id="code"))
Shapes
from remapping.basics import ACCENT, INK, Vec2
from remapping.things import Arrow, Circle, Dot, Line, Rectangle
self.add(Rectangle(500, 240, centre=Vec2(0, 0), radius=24, colour=ACCENT, id="card"))
self.add(Circle(80, centre=Vec2(-300, 0), colour=INK, id="circle"))
self.add(Dot(Vec2(200, 100), radius=12, colour=ACCENT, id="point"))
self.add(Line(Vec2(-400, -200), Vec2(400, -200), width=5, colour=INK, id="line"))
self.add(Arrow(Vec2(-300, 250), Vec2(300, 250), colour=ACCENT, id="arrow"))
Shapes accept visual look attributes such as colour, fill, stroke_width, and id.
Groups
Use a Group when several Things should behave as one Thing:
from remapping.things import Circle, Group, Text
badge = Group(
Circle(70, colour=ACCENT, id="background"),
Text("1", weight="bold", size=60, id="number"),
id="badge",
)
self.add(badge)
The group namespaces the parts from its children, so they can still be inspected and targeted.
Images and SVG
Put files inside the production, normally under assets/images/ or assets/svg/.
from remapping.basics import Vec2
from remapping.things import RasterImage, VectorImage
self.add(
RasterImage(
"assets/images/photo.png",
width=900.0,
height=600.0,
centre=Vec2(0.0, 0.0),
fit="cover",
id="photo",
)
)
self.add(
VectorImage(
"assets/svg/diagram.svg",
height=500.0,
centre=Vec2(0.0, 0.0),
keep_source_colours=True,
id="diagram",
)
)
Raster image formats are PNG, JPEG, TIFF, and WebP. Colour-tagged images are safest. Supported IDs inside an SVG become stable part names.
9. Layout: rows, columns, grids, and layers
Layout containers reduce manual coordinate work.
from remapping.layout import Column, Grid, Row
from remapping.things import Rectangle, Text
cards = [
Rectangle(240, 150, radius=18, colour=ACCENT.with_alpha(0.25), id=f"card-{i}")
for i in range(6)
]
card_grid = Grid(*cards, columns=3, gap=28.0, id="cards")
page = Column(
Text("A SIMPLE GRID", weight="semibold", size=54, id="heading"),
card_grid,
gap=44.0,
align="center",
id="page",
)
self.add(page.moved_to(0.0, 0.0), id="page")
Available containers:
| Container | Meaning |
|---|---|
Row |
Children from left to right |
Column |
Children from top to bottom |
Grid |
Children in a fixed number of columns |
Stack |
Children on top of each other, aligned by centre |
Overlay |
Positioned children in stable layer order |
Frame |
One child inside a fixed width and height |
Inset |
One child with padding around it |
Spacer |
Empty measured space |
Common container attributes are gap, padding, width, height, align,
distribution, overflow, and clip.
When adding independent Things, use layer= to control draw order. Larger layers draw
later and appear on top:
self.add(background, id="background", layer=0)
self.add(title, id="title", layer=10)
10. Time and visibility
For a silent Scene, set its duration:
class Diagram(Scene):
id = "diagram"
DURATION = 8.0
Use an interval for an animation:
during=Interval.of(1.0, 2.5)
This starts at 1.0 seconds and ends at 2.5 seconds.
You can show a Thing only during part of a Scene:
self.add(label, id="label", visible_from=2.0, visible_until=5.0)
Important entrance rule: if an entrance starts later than the beginning of the Scene,
set visible_from to the same start time. Otherwise the Thing is normally visible before
its entrance begins.
start = 2.0
self.add(title, id="title", visible_from=start)
self.animate(
id="enter-title",
target=Ref.thing("title"),
move=Enter("rise"),
during=Interval.of(start, start + 1.0),
)
A completed Leave animation keeps the Thing off screen.
11. Entrances and exits
Entrance styles
from remapping.moves import Enter
| Style | Effect |
|---|---|
fade |
Opacity from invisible to visible |
rise |
Move upward while fading in |
drop |
Move downward while fading in |
slide |
Move from the left while fading in |
pop |
Scale up with a small overshoot |
stroke |
Draw the outlines like a pen |
soft |
Remove blur while fading in |
wipe |
Reveal from left to right |
turn |
Rotate slightly into place |
Example:
self.animate(
id="enter-heading",
target=Ref.thing("heading"),
move=Enter(
"rise",
spread="in-order",
shape="ease-out",
travel=64.0,
),
during=Interval.of(0.0, 1.5),
)
For exits, use Leave with the same style names:
from remapping.moves import Leave
self.animate(
id="leave-heading",
target=Ref.thing("heading"),
move=Leave("fade"),
during=Interval.of(4.0, 5.0),
)
Spread: how pieces move
| Spread | Meaning |
|---|---|
together |
All pieces move at the same time |
in-order |
First piece to last piece |
backwards |
Last piece to first piece |
from-center |
Start in the middle and move outward |
Text, math, plots, groups, and many shapes contain multiple named Pieces. Spread controls how one effect travels across those Pieces.
Motion curves
| Curve | Feeling |
|---|---|
linear |
Constant speed |
smootherstep |
Very smooth start and finish |
ease-in |
Slow start, faster finish |
ease-out |
Fast start, soft finish |
ease-in-out |
Slow start and soft finish |
overshoot |
Pass the target and return |
settle |
Small damped bounce |
anticipate |
Move slightly backward before moving forward |
Use ease-out for many clean entrances. Use linear when motion represents a measured
rate. Use overshoot or settle carefully; too much bounce can distract from technical
content.
12. Stress and emphasis
Stress calls attention to something already visible, then returns it to normal.
from remapping.basics import ACCENT, Angle, Interval
from remapping.ids import Ref
from remapping.moves import Stress
self.animate(
id="stress-answer",
target=Ref.thing("answer"),
move=Stress(
scale=1.15,
tint=ACCENT,
rotation=Angle.degrees(2.0),
shape="smootherstep",
),
pivot=Ref.thing("answer").anchor("center"),
during=Interval.of(2.0, 3.2),
)
Stress attributes:
| Attribute | Meaning |
|---|---|
scale |
Peak size; 1.15 means 15% larger |
tint |
Peak colour |
alpha |
Peak opacity |
blur |
Peak blur radius |
rotation |
Peak rotation angle |
spread |
How the stress travels across Pieces |
shape |
Curve used on the way to and from the peak |
A pivot controls the point around which scale and rotation happen. Use a named anchor, especially when stressing one tagged part of a formula.
13. Animation attributes and custom combinations
Built-in Moves are made from small controls called levers:
| Lever | Attribute changed |
|---|---|
Alpha |
Opacity |
Blur |
Blur radius |
Clip |
Wipe/reveal rectangle |
Offset |
Position |
Rotation |
Rotation around pivot |
Scale |
Size around pivot |
Tint |
Colour blend |
Trace |
Amount of an outline that is drawn |
For an explicit combination, use Composite:
from remapping.moves import Alpha, Composite, Offset, Scale
custom_entrance = Composite(
[
Alpha(0.0, 1.0),
Offset((-80.0, 0.0), (0.0, 0.0)),
Scale(0.85, 1.0),
],
shape="ease-out",
spread="from-center",
)
self.animate(
id="custom-entrance",
target=Ref.thing("title"),
move=custom_entrance,
during=Interval.of(0.0, 1.5),
)
Do not run two animations that replace the same attribute channel on the same target at
the same time. Planning checks for conflicts. If an advanced composition is intentional,
use the animation's channels= and blend= controls carefully.
14. IDs, parts, and precise targets
Target a complete Thing:
target=Ref.thing("title")
Target a named part:
target=Ref.thing("equation").part("tag:slope")
Target an anchor for a pivot:
pivot=Ref.thing("equation").anchor("slope-center")
Some targets return many Pieces. Declare that explicitly:
self.animate(
id="reveal-points",
target=Ref.thing("plot").part("series:measured", many=True),
move=Enter("pop", spread="in-order"),
during=Interval.of(1.0, 2.5),
many=True,
)
Never guess a complicated part name. Ask the system:
uv run remapping ids list productions/my-film
uv run remapping ids list productions/my-film --scene opening
uv run remapping ids inspect productions/my-film --ref thing:equation/part:tag:slope
uv run remapping anchors list productions/my-film --owner equation
uv run remapping animation list productions/my-film
uv run remapping animation trace productions/my-film --animation stress-answer
15. Mathematics
Use Math for mathematics and Text for ordinary language. ReMapping does not guess.
Use a raw Python string beginning with r so LaTeX backslashes work correctly:
from remapping.basics import INK, Vec2
from remapping.things import Math
self.add(
Math(
r"y = mx + b",
size=76,
colour=INK,
origin=Vec2(-300.0, 0.0),
id="equation",
)
)
Useful attributes:
| Attribute | Meaning |
|---|---|
source |
LaTeX expression |
size |
Formula size |
colour |
Formula colour |
display=True |
Display-style formula |
display=False |
Inline-style formula |
preamble |
Extra trusted LaTeX setup when needed |
Tag a mathematical part
Use \rmtag{name}{content} to give a part a stable semantic name:
equation = Math(
r"y = \rmtag{slope}{m}x + \rmtag{shift}{b}",
size=92,
id="equation",
)
self.add(equation)
Stress only the slope:
self.animate(
id="stress-slope",
target=Ref.thing("equation").part("tag:slope"),
move=Stress(scale=1.25, tint=ACCENT),
pivot=Ref.thing("equation").anchor("slope-center"),
during=Interval.of(2.0, 3.5),
)
This is safer than targeting a glyph number. You can edit the rest of the formula without
changing the meaning of tag:slope.
Test the mathematics system:
uv run remapping math sample \
--production productions/my-film \
--output math-sample.png
16. Graphs and plots
A plot has two Axes and zero or more Series. Every data point has a stable key.
from remapping.basics import ACCENT, INK, MUTED, Interval, Vec2
from remapping.ids import Ref
from remapping.moves import Enter
from remapping.things import Axis, Plot, Series
measurements = Series.from_pairs(
"measured",
[
("sample-a", 0.0, 0.0),
("sample-b", 1.0, 1.2),
("sample-c", 2.0, 1.8),
("sample-d", 3.0, 3.1),
],
colour=ACCENT,
line=True,
markers=True,
line_width=5.0,
marker_radius=10.0,
)
plot = Plot(
x=Axis(0.0, 3.0, 0.5),
y=Axis(0.0, 4.0, 1.0),
width=1200.0,
height=600.0,
centre=Vec2(0.0, -20.0),
series=(measurements,),
grid=True,
axis_colour=INK,
grid_colour=MUTED,
id="plot",
)
self.add(plot)
self.animate(
id="draw-line",
target=Ref.thing("plot").part("series:measured/part:line"),
move=Enter("stroke"),
during=Interval.of(0.0, 1.5),
)
Series attributes:
| Attribute | Meaning |
|---|---|
id |
Stable series name |
points |
Stable keyed data points |
colour |
Line and marker colour |
line |
Draw a connected line |
markers |
Draw a marker for each point |
line_width |
Line thickness |
marker_radius |
Marker size |
Plot a mathematical function
Sample the function at stable points:
import math
def sine_series(frequency: float) -> Series:
points = []
for index in range(321):
time = 2.0 * index / 320
amplitude = math.sin(math.tau * frequency * time)
points.append((f"sample-{index:03d}", time, amplitude))
return Series.from_pairs(
"wave",
points,
colour=ACCENT,
line=True,
markers=False,
line_width=6.0,
)
Use plot.to_design(x, y) when another Thing must sit on an exact data coordinate:
marker_position = plot.to_design(2.0, 1.8)
self.add(Circle(12.0, centre=marker_position, colour=ACCENT, id="cursor"))
Graph labels are separate Text or Math Things. This gives you full control of label position, typography, and animation.
17. A moving graph cursor with exact math
For a cursor that follows a function, create a small custom Move. This example follows
the cubic ease-out curve p(t) = 1 - (1 - t)^3:
from remapping.canvas import Piece
from remapping.moves import BaseMove
GRAPH_WIDTH = 650.0
GRAPH_HEIGHT = 430.0
def cubic_ease_out(t: float) -> float:
return 1.0 - (1.0 - t) ** 3
class FollowEaseOutCurve(BaseMove):
def __init__(self) -> None:
super().__init__(shape="linear", spread="together")
def apply(self, piece: Piece, progress: float) -> Piece | None:
t = max(0.0, min(1.0, progress))
return piece.translated(
-GRAPH_WIDTH * (1.0 - t),
-GRAPH_HEIGHT * (1.0 - cubic_ease_out(t)),
)
Place the cursor at the final graph position, then the Move translates it from the start:
cursor_end = plot.to_design(1.0, 1.0)
self.add(Circle(11.0, centre=cursor_end, colour=ACCENT, id="cursor"))
self.animate(
id="follow-curve",
target=Ref.thing("cursor"),
move=FollowEaseOutCurve(),
during=Interval.of(0.0, 1.6),
)
The graph, cursor, and entrance can all call the same function. This prevents the visual proof and the motion from disagreeing.
18. Camera
Static 2D camera
Use Camera2D to pan, zoom, or rotate the world:
from remapping.basics import Angle
from remapping.camera import Camera2D
self.configure(
camera=Camera2D(
center=(200.0, 0.0),
zoom=1.25,
rotation=Angle.degrees(0.0),
id="diagram",
)
)
Camera attributes:
| Attribute | Meaning |
|---|---|
center |
World position placed at the centre of the screen |
zoom |
1.0 normal, more than 1.0 closer, less than 1.0 wider |
rotation |
Camera rotation as an Angle |
id |
Stable camera name |
pixel_snap |
Snap thin strokes to pixels; normally False for smooth pans |
scale_strokes |
Scale strokes and blur with zoom |
The supported zoom range is 0.01 to 200.0.
Animated 2D camera
The camera should be a pure function of story time. Do not base it on mutable state.
from fractions import Fraction
from remapping.basics import Angle
from remapping.camera import Camera2D
class CameraMove(Scene):
id = "camera-move"
DURATION = 5.0
def setup(self) -> None:
self.configure(camera=Camera2D(id="diagram"))
# Add wide world content here.
def draw(self, canvas, story_time): # type: ignore[override]
progress = min(1.0, float(Fraction(story_time) / Fraction(5)))
self.configure(
camera=Camera2D(
center=(-500.0 + 1000.0 * progress, 0.0),
zoom=1.0 + 0.35 * progress,
rotation=Angle.degrees(0.0),
id="diagram",
)
)
super().draw(canvas, story_time)
For a smoother blend, create start and end cameras and call blended:
start_camera = Camera2D(center=(-500.0, 0.0), zoom=1.0, id="diagram")
end_camera = Camera2D(center=(500.0, 0.0), zoom=1.35, id="diagram")
camera = start_camera.blended(end_camera, progress)
The helper pan(start, end, progress, zoom=1.0) creates a simple deterministic pan.
The helper zoom_to(bounds, viewport, margin=1.1) frames a world-space rectangle.
Camera rigs and 3D status
CameraRig can hold named cameras and blend cameras of the same type. The library also
contains Camera3D, Perspective, Orthographic3D, orbit, and dolly for projection
and camera mathematics.
Current build limitation: ordinary Scene drawing applies Camera2D to Things. The 3D
projection classes are available and tested, but mesh commands and the complete 3D render
path are not implemented. Use the 2D camera for deliverable films in this version.
The CLI commands camera sample and camera verify are also not implemented yet.
19. Narration and speech-driven timing
Declare narration with Script, Line, Speech, and Beat:
from remapping.sound import Beat, Line, Script, Speech
class EquationScene(Scene):
id = "equation"
SCRIPT = Script(
[
Line(
id="explain-line",
speech=Speech(
"This line has a ",
Beat("slope-value", "slope of three"),
".",
),
caption="This line has a slope of three.",
claims={"slope": 3},
),
]
)
def setup(self) -> None:
spoken = self.say("explain-line")
self.animate(
id="stress-spoken-slope",
target=Ref.thing("equation").part("tag:slope"),
move=Stress(scale=1.18, tint=ACCENT),
pivot=Ref.thing("equation").anchor("slope-center"),
during=spoken.beat("slope-value"),
)
Useful speech timing selections:
line = self.say("explain-line")
whole_line = line
first_half = line.through(0.0, 0.5)
named_phrase = line.beat("slope-value")
first_part_of_phrase = line.beat("slope-value").through(0.0, 0.6)
This is better than hard-coded seconds for narrated animation. If a recording changes, the animation follows the approved spoken phrase.
Claims: check spoken values against data
If narration says a computed value, declare a claim and calculate the same claim in the Scene:
def claims(self) -> dict[str, float | str]:
return {"slope": calculate_slope()}
Planning stops if the calculated claim and spoken claim disagree. This prevents a graph, formula, and narration from silently saying different things.
Inspect the narration plan:
uv run remapping timeline inspect productions/my-film
uv run remapping voice status productions/my-film
uv run remapping subtitles productions/my-film --output my-film.srt
Current build limitation: voice record, voice align, and voice approval commands are
not implemented. Use --draft-timing for estimated timing.
20. Generated sound
ReMapping can generate deterministic sound. This example adds a short tone:
from remapping.basics import Interval
from remapping.sound import Oscillator
from remapping.sound.source import generated_cue
self.sound(
generated_cue(
"title-tone",
Oscillator(
frequency=196.0,
amplitude=0.06,
duration=1.6,
id="title-tone",
),
Interval.of(0.0, 1.6),
role="stinger",
)
)
Available generated sources include oscillators, noise, sweeps, clicks, envelopes, filters, mixes, and sonification. Keep amplitudes conservative so the mix does not clip.
Use --stems during a render to export diagnostic WAV files for narration, generated
sound, effects, video audio, music, and master buses.
21. Multiple scenes and transitions
Create several Scene classes, then put them in order:
from remapping.basics import Colour
from remapping.film import Crossfade, Cut, FadeThrough, Film, SceneSpec, Sequence
FILM = Film(
id="my-film",
sequences=(
Sequence(
id="chapter-one",
scenes=(
SceneSpec(
id="opening",
factory=Opening,
transition_after=Crossfade(duration=0.5),
),
SceneSpec(
id="graph",
factory=GraphScene,
transition_after=FadeThrough(
colour=Colour.hex("#000000"),
duration=0.8,
),
),
SceneSpec(
id="credits",
factory=Credits,
transition_after=Cut(),
),
),
),
),
)
Transitions:
| Transition | Meaning |
|---|---|
Cut() |
Immediate change with no overlap |
Crossfade(duration=0.5) |
Blend two scenes during an overlap |
FadeThrough(colour=..., duration=0.8) |
Fade to a solid colour, then reveal the next scene |
22. Colours and local style
Built-in colour names include:
from remapping.basics import ACCENT, GREEN, INK, MUTED, PAPER, RED, WARN, YELLOW
Create a colour from a hex value:
from remapping.basics import Colour
BLUE = Colour.hex("#2f6bff")
SOFT_BLUE = BLUE.with_alpha(0.25)
Edit productions/my-film/styles/local.py to define the production's base look:
from remapping.basics import Colour
from remapping.production import StyleSheet
STYLE = StyleSheet(
name="local",
tokens={
"colour.paper": Colour.hex("#101014"),
"colour.ink": Colour.hex("#f2f2f7"),
"colour.accent": Colour.hex("#3d8bfd"),
},
)
Style priority is:
engine defaults
→ declared shared style packs
→ local style
→ explicit Scene or Thing attributes
→ explicit command-line flags
23. Still-image workflow
Do not render the complete film after every small change. Make stills first.
By story time:
uv run remapping still productions/my-film --time 2.5 --output scene-check.png
By frame number:
uv run remapping still productions/my-film --frame 150 --output frame-150.png
For one Scene:
uv run remapping still productions/my-film \
--scene graph \
--time 1.5 \
--show-safe-area \
--show-anchors \
--output graph-check.png
--frame and --time cannot be used together.
24. Preview, draft, and final render
Fast preview
uv run remapping preview productions/my-film
Complete low-cost draft
uv run remapping draft productions/my-film
1080p final-style render
uv run remapping render productions/my-film \
--profile final-1080p60 \
--workers auto \
--draft-timing \
--no-audio \
--output my-film.mp4
4K 10-bit HEVC
uv run remapping render productions/my-film \
--profile final-4k60-hevc10 \
--workers auto \
--gpu-workers 4 \
--draft-timing \
--no-audio \
--output my-film-4k.mp4
--gpu-workers accelerates the expensive 10/12-bit conversion on Metal. The scheduler
may choose fewer workers to stay inside memory limits. Use --gpu-workers 0 for the CPU
path.
Verify a finished video
uv run remapping verify productions/my-film/outputs/my-film.mp4
The render command also verifies the delivered file before it calls the job successful.
25. Output profiles
List profiles on your installed build:
uv run remapping profile list
Profiles in this version:
| Profile | Size and rate | Main use |
|---|---|---|
preview-540p30 |
960 × 540, 30 fps | Fast preview |
review-1080p30 |
1920 × 1080, 30 fps | Review and approval |
final-1080p60 |
1920 × 1080, 60 fps, H.264 | Standard delivery |
final-4k60-hevc10 |
3840 × 2160, 60 fps, 10-bit HEVC | High-quality 4K SDR |
master-4k60-prores |
3840 × 2160, 60 fps, ProRes | Editing/archive master |
master-4k60-prores4444-alpha |
4K, 60 fps, ProRes 4444 | Alpha master |
master-8k30-prores |
7680 × 4320, 30 fps, ProRes | 8K master |
Estimate file size and storage before a large render:
uv run remapping profile estimate productions/my-film --profile final-4k60-hevc10
26. Important render options
| Option | Meaning |
|---|---|
--profile NAME |
Output resolution, frame rate, codec, colour, and audio profile |
--workers auto |
Automatically choose safe parallel CPU workers |
--workers 1 |
Serial render; useful for deterministic comparisons |
--gpu-workers N |
Up to N Metal conversion workers for 10/12-bit output |
--seed INTEGER |
Deterministic production seed |
--output NAME.mp4 |
Output path inside the production's output root |
--overwrite |
Replace an existing file only after the new file verifies |
--stems |
Export diagnostic audio stems |
--no-audio |
Make a silent diagnostic output |
--scene ID |
Render selected Scenes only; diagnostic, not a certified full film |
--draft-timing |
Use estimated narration timing |
--resume latest |
Resume the latest compatible interrupted job |
--keep-temporary |
Keep render diagnostics and temporary files |
27. Recommended work cycle
Use this loop for every new section of a film:
- Write or edit one Scene.
- Validate the production.
- Inspect the film and IDs.
- Make stills at the start, middle, and end of each animation.
- Check safe areas and anchors.
- Make a fast preview.
- Watch the complete draft.
- Render the final profile only when timing and layout are stable.
- Verify the final file.
Commands:
uv run remapping production validate productions/my-film
uv run remapping film inspect productions/my-film
uv run remapping ids list productions/my-film
uv run remapping lint productions/my-film
uv run remapping still productions/my-film --time 0
uv run remapping still productions/my-film --time 2
uv run remapping preview productions/my-film
uv run remapping render productions/my-film --draft-timing --no-audio --workers auto
uv run remapping verify productions/my-film/outputs/my-film.mp4
28. Command cheat sheet
All commands can print structured data with the global --json option. Use --quiet to
hide progress and -v or --verbose for more detail.
System and production
uv run remapping doctor
uv run remapping doctor --ci
uv run remapping production new productions/my-film --id my-film
uv run remapping production inspect productions/my-film
uv run remapping production validate productions/my-film
uv run remapping production check-boundary productions/my-film
Environment
uv run remapping environment inspect productions/my-film
uv run remapping environment lock productions/my-film
uv run remapping environment lock productions/my-film --update
uv run remapping environment verify productions/my-film
Fonts and math
uv run remapping fonts inspect
uv run remapping fonts verify
uv run remapping fonts sample --production productions/my-film --output fonts.png
uv run remapping math sample --production productions/my-film --output math.png
Film, plan, IDs, anchors, and animation
uv run remapping film list productions/my-film
uv run remapping film inspect productions/my-film
uv run remapping plan productions/my-film --output plan.json
uv run remapping lint productions/my-film
uv run remapping ids list productions/my-film
uv run remapping ids inspect productions/my-film --ref thing:title
uv run remapping anchors list productions/my-film
uv run remapping animation list productions/my-film
uv run remapping animation trace productions/my-film --animation enter-title
uv run remapping timeline inspect productions/my-film
uv run remapping voice status productions/my-film
Rendering and verification
uv run remapping still productions/my-film --time 1
uv run remapping preview productions/my-film
uv run remapping draft productions/my-film
uv run remapping render productions/my-film --draft-timing --no-audio --workers auto
uv run remapping verify productions/my-film/outputs/my-film.mp4
uv run remapping subtitles productions/my-film --output my-film.srt
Jobs and recovery
uv run remapping job list productions/my-film
uv run remapping job status productions/my-film --job JOB-ID
uv run remapping render productions/my-film --resume latest
uv run remapping job clean productions/my-film --job JOB-ID
uv run remapping job unlock productions/my-film --output my-film.mp4
Only use job unlock when ReMapping proves the old output lock is stale. It refuses to
remove a lock belonging to a process that is still alive.
Profiles, hardware, cache, and manifests
uv run remapping profile list
uv run remapping profile validate final-1080p60
uv run remapping profile estimate productions/my-film --profile final-4k60-hevc10
uv run remapping hardware inspect
uv run remapping gpu inspect
uv run remapping cache inspect productions/my-film
uv run remapping cache clear productions/my-film --kind math
uv run remapping manifest show productions/my-film/outputs/my-film.manifest.json
Clear every cache only when you really want to rebuild disposable cached material:
uv run remapping cache clear productions/my-film --all --yes
This does not delete takes, delivered outputs, shared packs, or job checkpoints.
29. Reusing styles and Things through packs
A production stays isolated. To reuse something deliberately, promote it to a versioned pack, then add that exact version to another production.
Example for a style:
uv run remapping style promote \
--production productions/first-film \
--source styles/local.py \
--name geometric-dark \
--version 1.0.0
uv run remapping style add \
--production productions/second-film \
--name geometric-dark \
--version 1.0.0
Other pack kinds are thing, move, layout, and asset.
Check a production's declared pack digests:
uv run remapping pack verify-lock productions/second-film
Published versions are immutable. Publish a new version when the content changes.
30. Common problems
“Unknown ID” or “reference does not resolve”
The target name is wrong or the Thing was not added.
uv run remapping ids list productions/my-film
uv run remapping animation trace productions/my-film --animation ANIMATION-ID
Check spelling and use stable lowercase IDs.
A Thing is visible before its entrance
The entrance starts after time zero, but the Thing has no visible_from value. Add:
self.add(thing, id="thing", visible_from=start)
A stress scales around the wrong point
Give it a pivot:
pivot=Ref.thing("thing").anchor("center")
For tagged math, use the tag anchor such as slope-center.
Two animations conflict
They are changing the same attribute channel on the same target during overlapping time. Change their timing, combine their levers into one Composite, or use intentional channel and blend settings.
LaTeX or math fails
Check the math toolchain:
uv run remapping doctor
uv run remapping fonts verify
uv run remapping math sample --production productions/my-film --output math-test.png
Use raw strings such as r"\frac{a}{b}".
Font fails or text geometry changes
ReMapping does not silently replace fonts. Install the required SF families and run:
uv run remapping fonts inspect
uv run remapping fonts verify
Something is outside the safe area
uv run remapping lint productions/my-film
uv run remapping still productions/my-film --time 1 --show-safe-area
Move or resize important text and controls so they stay inside the title-safe area.
Final render refuses because narration is missing
This build cannot yet record and approve narration. For a diagnostic render:
uv run remapping render productions/my-film --draft-timing --no-audio
This is not a certified narrated final.
Output already exists
Choose a new output name, or use --overwrite. Replacement is atomic and only happens
after the new file passes verification.
uv run remapping render productions/my-film \
--draft-timing \
--no-audio \
--output my-film.mp4 \
--overwrite
Render stopped or the computer restarted
Inspect jobs and resume a compatible checkpoint:
uv run remapping job list productions/my-film
uv run remapping render productions/my-film --resume latest
Resume works only when input digests still match.
Render is too slow or uses too much memory
First make a 540p preview. For the final, let the scheduler choose workers:
uv run remapping preview productions/my-film
uv run remapping render productions/my-film --workers auto --draft-timing --no-audio
For a conservative render, use --workers 1. For 10/12-bit profiles on supported Apple
hardware, try --gpu-workers 2 or --gpu-workers 4.
31. Commands not implemented in this build
These command families or subcommands are present on the roadmap but do not work yet:
- all
meshcommands - all
mediaCLI commands camera sampleandcamera verifygpu verifyanchors sample- voice record, alignment, marker review, approval, comparison, QC, and script lint tools
- sound cue render, mix preview, and sound stems commands
- music inspection and mix reports
- hardware benchmark and tuning
This does not mean every related library feature is missing. For example, raster and SVG Things work through Python, generated sound works through Python, and 2D cameras work in normal Scene rendering. The list above is specifically about missing CLI or roadmap paths.
32. Working examples in this repository
Use these as copyable references:
| Production or example | What it demonstrates |
|---|---|
examples/hello.py |
Title entrance synchronized with an easing graph |
examples/text_entrance_showcase.py |
Per-letter and per-word entrances |
examples/motion_showcase.py |
Entrance styles, spreads, and curves |
examples/math_showcase.py |
LaTeX and semantic math tags |
examples/sine_frequency_showcase.py |
Function graph with changing frequency |
examples/unit_circle_sine_showcase.py |
Unit circle and sine relationship |
productions/reference/ |
Narration, claims, plots, layout, camera, sound, transitions |
productions/technical-animation-showcase/ |
Entrances, stresses, attributes, and motion proof |
productions/motion/ |
Complete motion vocabulary showcase |
productions/text-entrance-showcase/ |
Renderable text entrance production |
productions/math-showcase/ |
Renderable mathematics production |
Validate or render an example without editing it:
uv run remapping production validate productions/reference
uv run remapping still productions/reference --scene equation --time 2
uv run remapping preview productions/reference
33. Final checklist
Before a large render, check all of these:
doctoris successful.- Production validation is successful.
- Every Scene and Thing has a stable ID.
- Part targets and anchors resolve.
- Spoken numerical claims match computed values.
- Important content stays inside safe areas.
- Entrances that start late use
visible_from. - Overlapping animations do not fight over the same attribute.
- Start, middle, and end stills look correct.
- A fast preview plays correctly.
- The selected profile has enough storage and memory.
- The environment lock is current for a certified final.
- The delivered video passes
remapping verify.
The safest normal order is:
uv run remapping doctor
uv run remapping production validate productions/my-film
uv run remapping film inspect productions/my-film
uv run remapping lint productions/my-film
uv run remapping still productions/my-film --time 1
uv run remapping preview productions/my-film
uv run remapping render productions/my-film --draft-timing --no-audio --workers auto
uv run remapping verify productions/my-film/outputs/my-film.mp4