# Editing with Remotion

Thirteen original silent studies and a 50-second sequence. These are runnable
adaptations of inspected editing patterns, not recovered OpenAI projects or
product demonstrations. The source films, product data and brand assets are
deliberately absent from this download.

## Run from a fresh directory

Use Node.js 22.18 or newer and npm. Install the exact dependency graph in the
included lockfile; do not substitute `npm install remotion@latest`.

```bash
npm ci
npm run typecheck
npm test
npx remotion browser ensure
npm run preview
```

The last command opens Remotion Studio. Choose a composition in the left-hand
list and play it. The initial install and browser download require internet;
the compositions themselves make no external asset requests. Linux needs the
[Remotion system requirements](https://www.remotion.dev/docs#system-requirements).
If a supported Chrome installation is already present, use the CLI's
`--browser-executable=/path/to/chrome` option. The batch script instead accepts
`REMOTION_BROWSER_EXECUTABLE`; that variable is optional and not a required
private path.

From a second terminal in the project directory:

```bash
npm run render -- TitleCut out/title.mp4 --codec=h264 --pixel-format=yuv420p --muted
npm run render -- StudySequence out/sequence.mp4 --codec=h264 --pixel-format=yuv420p --muted
npm run render:all -- out
```

The batch command bundles once, renders all thirteen studies and the sequence,
and writes `out/render-manifest.json`. Every composition is **1280 × 720,
16:9, 30 fps, silent H.264 MP4**. CLI configuration and batch options explicitly
use PNG frame capture and BT.709 conversion, with yuv420p output. These are research settings, not approval of
final presentation delivery settings. Remotion's own
[license](https://www.remotion.dev/license) applies to the dependency; evaluate
its terms before commercial production. This bundle does not grant an upstream
software license or permission to reuse source films.

## Pick an edit

| Composition | Use it to… | Protect across the edit |
| --- | --- | --- |
| `TitleCut` | Build a premise, hold it, cut to a formed surface | Full title reading interval; no simultaneous UI action |
| `ChapterReturn` | Interrupt a task with a new chapter, then return | Exact original task state; no hidden result change |
| `PunchCut` | Make a small region immediately readable | Same artifact across the crop discontinuity |
| `CameraPush` | Move attention through an established workspace | Common transform for every workspace layer; settle before action |
| `FocusType` | Aim at a field before the request appears | Stable baseline and complete request before leaving |
| `SelectionContext` | Identify scope, reveal its action, restore context | Selected object, attached action, fixed outer frame |
| `ScrollMask` | Bring a destination into a fixed viewport | Header stays still; reading starts after travel |
| `SplitResult` | Make room for a result while retaining the request | Container arrives before content; completed content before the hold |
| `ObjectContext` | Move the same request into another container | Semantic identity; a cut is not a claimed morph |
| `CaptionCut` | Retain an identifier across a shot change | Caption stays in screen space, outside the camera transform |
| `MotifMontage` | Compare variants through a recurring silhouette | Consistent motif; holds long enough for the intended comparison |
| `DensityCollapse` | Reduce a busy wall to a sparse takeaway | Shared group transform, explicit clearing before the title |
| `QuietEnding` | Give a finished proof a quiet exit | Proof first, brief original-mark construction, stable final hold |

Read the source interval and discrepancy notes on the
[published chapter](https://openai-film-fieldnotes-100.pages.dev/videos/editing/)
before treating one of these as appropriate for a real presentation.

## Inputs and units

`src/model.ts` is the input contract. `src/Edits.tsx` implements every edit.
`src/visuals.tsx` supplies the complete original window, artwork, typography,
pointer and camera. `src/Root.tsx` registers the compositions and assembles the
sequence. The project has no omitted components or external design package.

| Input | Type / limits | Meaning |
| --- | --- | --- |
| `title` | 1–42 characters | Premise or takeaway; type-on may reflow as it grows |
| `prompt` | 1–58 characters | Same original request throughout a sequence |
| `result` | 1–52 characters | Short visible result label, not an invented product claim |
| `label` | 1–28 characters | Artifact identifier / window label |
| `accent` | Six-digit hex color | Original artwork and emphasis; check contrast after changing |
| `action` | Integer local frame | First action or departure from the established state |
| `change` | Integer local frame | Decisive cut, completed travel, or start of content reveal |
| `settle` | Integer local frame | First frame of the final quiet state |
| `hold` | Integer, at least 30 | Frames after `settle`; changes total duration without speeding motion |
| `focusX`, `focusY` | 0.2–0.8 | Target in the full 1280 × 720 design frame, normalized |
| `zoom` | 1–2 | Final workspace scale for the camera examples |

Require `action < change <= settle`. Duration is `settle + hold`, computed by
`calculateMetadata`; there is no second manually maintained duration field.
Frame ranges are half-open. For example, a cut at frame 54 leaves shot A visible
through frame 53 and starts shot B at frame 54. At 30 fps, that is 1.8 seconds.
Source films have different frame rates; convert their source PTS to seconds
before mapping into this project. Do not equate source frame 54 with output
frame 54.

### Phase meanings by composition

| Composition | `action` → `change` | `change` → `settle` | Final hold |
| --- | --- | --- | --- |
| TitleCut | Reveal title characters | Complete title remains | Hard cut at `settle`, formed workspace |
| ChapterReturn | Cut to title; reveal characters | Complete title remains | Cut back at `settle`, unchanged task |
| PunchCut | Wide view remains | Hard crop at `change` | Same crop; default `change = settle` |
| CameraPush | Chosen eased camera travel | Camera holds | Action label appears at `settle`; camera remains fixed |
| FocusType | Pointer approaches the input | Request types in | Complete request, pointer removed; no submit implied |
| SelectionContext | Selection appears; camera moves closer | Action appears; camera restores context | Same selection and action |
| ScrollMask | Content moves 360 design pixels behind mask | Destination remains | Destination remains; no new camera move |
| SplitResult | Request pane narrows; empty preview appears | Preview content fades and settles | Complete result beside request |
| ObjectContext | Isolated input holds | Cut to phone; four blank frames, then request/art return | Same request and result in phone |
| CaptionCut | Identifier appears on wide view | Crop cut at `change` | Identifier is unchanged in frame space |
| MotifMontage | First variation appears at `action` | Further cuts spaced by `change - action` | Last variant; keep `settle` after the last cut |
| DensityCollapse | Output wall moves upward slightly | Whole wall scales toward center | Wall removed, title present |
| QuietEnding | Cut from proof to sparse ending | Three bars build with four-frame stagger | Completely stable original mark |

The character caps prevent common overflow cases; they are not a proof that any
string in any language will fit. Test the longest title, widest glyphs and actual
line breaks. For more content, redesign the layout or split the beat. Do not
silently shrink essential text until it is unreadable.

## Adapt a study

Create `custom-edit.json`:

```json
{
  "title": "Make the result readable.",
  "result": "One clear result, ready for review.",
  "accent": "#365c78",
  "hold": 150
}
```

```bash
npm run render -- TitleCut out/custom-title.mp4 --props=custom-edit.json --codec=h264 --pixel-format=yuv420p --muted
```

Only supplied fields override composition defaults. Here the title cut remains
at frame 84 and the final hold becomes 150 frames: 234 frames / 7.8 seconds
total. To move the cut itself, change `settle`, not `hold`. If moving any phase
boundary, preserve phase ordering and inspect the boundary frames.

The camera uses full-frame coordinates. At zoom `z`, the target lands at the
center using translation `(640 - focusX × 1280 × z,
360 - focusY × 720 × z)`. Captions sit outside that transform. A scroll instead
translates only the document inside an `overflow: hidden` viewport. These are
different operations, with different layer ownership.

## Adapt the 50-second sequence

The eight beats in `sequenceBeats` in `src/model.ts` total 1,500 frames:

| Interval | Edit | Role |
| --- | --- | --- |
| 0–5 s | TitleCut | Premise and formed surface |
| 5–10 s | CameraPush | Identify the selected study |
| 10–17 s | FocusType | Establish the request |
| 17–24 s | SplitResult | Make room for its original result |
| 24–30 s | ChapterReturn | Pause for comparison, retain task state |
| 30–36 s | MotifMontage | Compare the same silhouette in three treatments |
| 36–42 s | ObjectContext | Carry request and artwork into another context |
| 42–50 s | QuietEnding | Return to proof, then leave a quiet tail |

Create `custom-sequence.json`:

```json
{
  "title": "A small exhibition, clearly told.",
  "prompt": "Compare three exhibition studies.",
  "result": "Three studies prepared for review.",
  "label": "EXHIBITION / REVIEW",
  "accent": "#365c78",
  "extraHold": 60
}
```

```bash
npm run render -- StudySequence out/custom-sequence.mp4 --props=custom-sequence.json --codec=h264 --pixel-format=yuv420p --muted
```

This is 1,560 frames / 52 seconds. `extraHold` accepts 0–300 frames and extends
only the final quiet tail. To change individual beats, edit `sequenceBeats` and
its matching phase overrides; its runtime assertion rejects a duration mismatch.
Keep a task's content shared across shots rather than creating unrelated fixtures.
The sequence is a composed study, not a claim that one source film uses this edit list.

## Replace neutral graphics with real footage

The original geometry is suitable for research comparisons, not for claiming
product behavior. Obtain permitted captures from the actual application. Keep
the capture, pointer and UI overlays under one camera; keep editorial captions
outside it. For a raster capture that fills the frame, start with at least
`output width × maximum zoom` pixels of capture width. Settle before reading,
and check the final MP4 at 640 × 360 meeting-view size. Never draw an invented
success over a failed interaction. Declared edits can omit waiting, but their
duration is not measured application latency.

## Verify the file, not just Studio

Use FFmpeg and ffprobe on PATH:

```bash
ffprobe -v error -show_entries stream=codec_type,codec_name,width,height,r_frame_rate,nb_frames -show_entries format=duration -of json out/custom-sequence.mp4
ffmpeg -v error -i out/custom-sequence.mp4 -f null -
```

Confirm 1280 × 720, 30/1 fps, 52 seconds for the adaptation above, and no audio
stream. Play the whole file. Inspect both frames around each hard cut, the
camera's middle frames, text at its longest state, masks at their edges, and
the last few seconds for accidental movement. A passing decoder does not prove
readability, semantic continuity or a good edit. The chapter's verification
report states which checks were actually run.

## The production approval boundary

These examples do not authorize final all-hands rendering. For a presentation,
derive timing from the supplied talking-head recording and its real pauses.
Keep the talking head in OBS and the separate, silent presentation video in
Google Slides. Do not composite them or duplicate narration. Use a recognizable
opening transition as a manual cue, then measure the actual reaction/startup
offset in rehearsal; do not assume zero delay.

If the talking head starts at presentation time `S`, recording time `h` maps to
`S + h`. For duration `H`, actual tail onset `T`, presentation end `P`, and
required remaining buffer `B`, check `S + T <= P <= S + H - B`, after all needed
speech and proof finish. The opening lead-in and ending buffer are separate.
If the wave/pause is absent, raise it in review; never manufacture it. Obtain
one consolidated storyboard/demo approval, including real captures, chosen
techniques, exact offsets, buffer and export settings, before production.

## Asset provenance

All diagrams, text, pointers and geometric artwork in `src` are original to
this study. The two bundled DejaVu font files carry their own permission notice
in `public/fonts/LICENSE.txt`. The downloadable project contains no OpenAI
font, logo, footage, source demo data, private product material or recording.
Research excerpts remain separately credited on the site. Preserve the font
notice when redistributing the project.

Implementation references: [Sequence timing](https://www.remotion.dev/docs/sequence),
[metadata from props](https://www.remotion.dev/docs/calculate-metadata),
[render CLI](https://www.remotion.dev/docs/cli/render),
[renderMedia](https://www.remotion.dev/docs/renderer/render-media).
The numerical easing, geometry, colors and output durations are chosen defaults,
not recovered source-film specifications.

For a checked schedule against an actual recording, read
[`RECORDING_ALIGNMENT.md`](RECORDING_ALIGNMENT.md). The included
`alignToRecording` helper maps proof anchors to local recipe frames, rejects
overlapping shots and inadequate reading holds, and checks both sides of the
real ending-buffer window. `npm test` includes its explicitly synthetic clock
tests; those tests do not claim that any supplied recording has been aligned.

[`API_VERIFICATION.md`](API_VERIFICATION.md) records the official API contracts,
including the important difference between CLI config and programmatic render
settings.

[`RECIPE_GEOMETRY.md`](RECIPE_GEOMETRY.md) lists each study's pixel coordinates,
layer ownership, original asset requirements, and constraints when substituting
different captures or artwork.
