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:

  1. Film, sequences, scenes, and timing
  2. Things: text, shapes, groups, images, and SVG
  3. Text and typography: letter, word, and line spacing
  4. Layout, coordinates, anchors, and spacing
  5. Moves and animations: entrances, exits, curves, spreads, and levers
  6. Stress and emphasis effects
  7. Mathematics and semantic formula parts
  8. Graphs, data series, plots, and animated cursors
  9. 2D cameras, pan, zoom, and current 3D status
  10. Narration, speech beats, claims, and generated sound
  11. Stills, previews, rendering, verification, and commands
  12. Images inside Markdown manuals
  13. 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:

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:

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

Use this loop for every new section of a film:

  1. Write or edit one Scene.
  2. Validate the production.
  3. Inspect the film and IDs.
  4. Make stills at the start, middle, and end of each animation.
  5. Check safe areas and anchors.
  6. Make a fast preview.
  7. Watch the complete draft.
  8. Render the final profile only when timing and layout are stable.
  9. 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:

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:

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